--- /dev/null
+# Generated by Django 2.2 on 2021-06-11 20:42
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('resource_inventory', '0017_auto_20201218_1516'),
+ ('booking', '0008_auto_20201109_1947'),
+ ('api', '0016_auto_20201109_2149'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='CloudInitFile',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('resource_id', models.CharField(max_length=200)),
+ ('booking', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='booking.Booking')),
+ ('rconfig', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.ResourceConfiguration')),
+ ],
+ ),
+ ]
import json
import uuid
+import yaml
from booking.models import Booking
from resource_inventory.models import (
RemoteInfo,
OPNFVConfig,
ConfigState,
- ResourceQuery
+ ResourceQuery,
+ ResourceConfiguration
)
from resource_inventory.idf_templater import IDFTemplater
from resource_inventory.pdf_templater import PDFTemplater
profile_ser.append(p)
return profile_ser
+class CloudInitFile(models.Model):
+ resource_id = models.CharField(max_length=200)
+ booking = models.ForeignKey(Booking, on_delete=models.CASCADE)
+ rconfig = models.ForeignKey(ResourceConfiguration, on_delete=models.CASCADE)
+
+ def _normalize_username(self, username: str) -> str:
+ # TODO: make usernames posix compliant
+ return username
+
+ def _get_ssh_string(self, username: str) -> str:
+ user = User.objects.get(username=username)
+ uprofile = user.userprofile
+
+ ssh_file = uprofile.ssh_public_key
+
+ escaped_file = ssh_file.open().read().decode(encoding="UTF-8").replace("\n", " ")
+
+ return escaped_file
+
+ def _serialize_users(self):
+ """
+ returns the dictionary to be placed behind the `users` field of the toplevel c-i dict
+ """
+ user_array = ["default"]
+ users = list(self.booking.collaborators.all())
+ users.append(self.booking.owner.userprofile)
+ for collaborator in users:
+ userdict = {}
+
+ # TODO: validate if usernames are valid as linux usernames (and provide an override potentially)
+ userdict['name'] = self._normalize_username(collaborator.user.username)
+
+ userdict['groups'] = "sudo"
+ userdict['sudo'] = "ALL=(ALL) NOPASSWD:ALL"
+
+ userdict['ssh_authorized_keys'] = [self._get_ssh_string(collaborator.user.username)]
+
+ user_array.append(userdict)
+
+ return user_array
+
+ def _serialize_netconf_v1(self):
+ config_arr = []
+
+ for interface in self._resource().interfaces.all():
+ interface_name = interface.profile.name
+ interface_mac = interface.mac_address
+
+ for vlan in interface.config.all():
+ vlan_dict_entry = {'type': 'vlan'}
+ vlan_dict_entry['name'] = str(interface_name) + "." + str(vlan.vlan_id)
+ vlan_dict_entry['link'] = str(interface_name)
+ vlan_dict_entry['vlan_id'] = int(vlan.vlan_id)
+ vlan_dict_entry['mac_address'] = str(interface_mac)
+ #vlan_dict_entry['mtu'] = # TODO, determine override MTU if needed
+
+ config_arr.append(vlan_dict_entry)
+
+ ns_dict = {
+ 'type': 'nameserver',
+ 'address': ['10.64.0.1', '8.8.8.8']
+ }
+
+ config_arr.append(ns_dict)
+
+ full_dict = {'version': 1, 'config': config_arr}
+
+ return full_dict
+
+ @classmethod
+ def get(booking_id: int, resource_lab_id: str):
+ return CloudInitFile.objects.get(resource_id=resource_lab_id, booking__id=booking_id)
+
+ def _resource(self):
+ return ResourceQuery.get(labid=self.resource_id, lab=self.booking.lab)
+
+ def _get_facts(self):
+ resource = self._resource()
+
+ hostname = self.rconfig.name
+ iface_configs = for_config.interface_configs.all()
+
+ def _to_dict(self):
+ main_dict = {}
+
+ main_dict['users'] = self._serialize_users()
+ main_dict['network'] = self._serialize_netconf_v1()
+ main_dict['hostname'] = self.rconfig.name
+
+ return main_dict
+
+ def serialize(self) -> str:
+ return yaml.dump(self._to_dict())
class Job(models.Model):
"""
return self.get_delta()
def get_delta(self):
+ # TODO: grab the CloudInitFile urls from self.hosthardwarerelation.get_resource()
return self.format_delta(
self.hosthardwarerelation.get_resource().get_configuration(self.state),
+ self.cloudinit_file.get_delta_url(),
self.hosthardwarerelation.lab_token)
booking=booking,
job=job
)
+ cls.makeCloudInitFiles(
+ resources=resources,
+ job=job
+ )
all_users = list(booking.collaborators.all())
all_users.append(booking.owner)
cls.makeAccessConfig(
except Exception:
continue
+ @classmethod
+ def makeCloudInitFiles(cls, resources=[], job=Job()):
+ for res in resources:
+ cif = CloudInitFile.objects.create(resource_id=res.labid, booking=job.booking, rconfig=res.config)
+ cif.save()
+
@classmethod
def makeHardwareConfigs(cls, resources=[], job=Job()):
"""
lab_users,
lab_user,
GenerateTokenView,
- analytics_job
+ analytics_job,
+ resource_cidata,
)
urlpatterns = [
path('labs/<slug:lab_name>/booking/<int:booking_id>/idf', get_idf, name="get-idf"),
path('labs/<slug:lab_name>/jobs/<int:job_id>', specific_job),
path('labs/<slug:lab_name>/jobs/<int:job_id>/<slug:task_id>', specific_task),
+ path('labs/<slug:lab_name>/jobs/<int:job_id>/cidata/<slug:resource_id', resource_cidata),
path('labs/<slug:lab_name>/jobs/new', new_jobs),
path('labs/<slug:lab_name>/jobs/current', current_jobs),
path('labs/<slug:lab_name>/jobs/done', done_jobs),
from api.forms import DowntimeForm
from account.models import UserProfile
from booking.models import Booking
-from api.models import LabManagerTracker, get_task
+from api.models import LabManagerTracker, get_task, CloudInitFile
from notifier.manager import NotificationHandler
from analytics.models import ActiveVPNUser
import json
return JsonResponse(lab_manager.update_job(job_id, request.POST), safe=False)
return JsonResponse(lab_manager.get_job(job_id), safe=False)
+@csrf_exempt
+def resource_cidata(request, lab_name="", job_id="", resource_id=""):
+ lab_token = request.META.get('HTTP_AUTH_TOKEN')
+ lab_manager = LabManagerTracker.get(lab_name, lab_token)
+
+ job = lab_manager.get_job(job_id)
+
+ cifile = None
+ try:
+ cifile = CloudInitFile.get(job.booking.id, resource_id)
+ except ObjectDoesNotExist:
+ return HttpResponseNotFound("Could not find a matching resource by id " + str(resource_id))
+
+ return HttpResponse(cifile.serialize(), status=200)
+
def new_jobs(request, lab_name=""):
lab_token = request.META.get('HTTP_AUTH_TOKEN')
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
-from django.core.exceptions import ObjectDoesNotExist
+from django.core.exceptions import (ObjectDoesNotExist, MultipleObjectsReturned)
class AbstractModelQuery():
@classmethod
def get(cls, *args, **kwargs):
+ """
+ Gets a single matching resource
+ Throws ObjectDoesNotExist if none found matching, or MultipleObjectsReturned if
+ the query does not narrow to a single object
+ """
try:
+ ls = cls.filter(*args, **kwargs)
+ if len(ls) > 1:
+ raise MultipleObjectsReturned()
return cls.filter(*args, **kwargs)[0]
except IndexError:
raise ObjectDoesNotExist()