Add API tests
authorSawyer Bergeron <sawyerbergeron@gmail.com>
Tue, 5 Mar 2019 20:28:51 +0000 (15:28 -0500)
committerSawyer Bergeron <sawyerbergeron@gmail.com>
Thu, 14 Mar 2019 16:21:45 +0000 (12:21 -0400)
Change-Id: Ic26d0b6de63405d239a9260b862158962c3140ac
Signed-off-by: Sawyer Bergeron <sawyerbergeron@gmail.com>
src/api/tests/test_models_unittest.py [new file with mode: 0644]
src/dashboard/testing_utils.py

diff --git a/src/api/tests/test_models_unittest.py b/src/api/tests/test_models_unittest.py
new file mode 100644 (file)
index 0000000..971f757
--- /dev/null
@@ -0,0 +1,278 @@
+##############################################################################
+# Copyright (c) 2019 Sawyer Bergeron, Parker Berberian, and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from datetime import timedelta
+from django.utils import timezone
+
+from booking.models import Booking
+from api.models import (
+    Job,
+    JobStatus,
+    JobFactory,
+    AccessRelation,
+    HostNetworkRelation,
+    HostHardwareRelation,
+    SoftwareRelation,
+)
+
+from resource_inventory.models import (
+    OPNFVRole,
+    HostProfile,
+)
+
+from django.test import TestCase, Client
+
+from dashboard.testing_utils import (
+    instantiate_host,
+    instantiate_user,
+    instantiate_userprofile,
+    instantiate_lab,
+    instantiate_installer,
+    instantiate_image,
+    instantiate_scenario,
+    instantiate_os,
+    make_hostprofile_set,
+    instantiate_opnfvrole,
+    instantiate_publicnet,
+    instantiate_booking,
+)
+
+
+class ValidBookingCreatesValidJob(TestCase):
+    @classmethod
+    def setUpTestData(cls):
+        cls.loginuser = instantiate_user(False, username="newtestuser", password="testpassword")
+        cls.userprofile = instantiate_userprofile(cls.loginuser)
+
+        lab_user = instantiate_user(True)
+        cls.lab = instantiate_lab(lab_user)
+
+        cls.host_profile = make_hostprofile_set(cls.lab)
+        cls.scenario = instantiate_scenario()
+        cls.installer = instantiate_installer([cls.scenario])
+        os = instantiate_os([cls.installer])
+        cls.image = instantiate_image(cls.lab, 1, cls.loginuser, os, cls.host_profile)
+        for i in range(30):
+            instantiate_host(cls.host_profile, cls.lab, name="host" + str(i), labid="host" + str(i))
+        cls.role = instantiate_opnfvrole("Jumphost")
+        cls.computerole = instantiate_opnfvrole("Compute")
+        instantiate_publicnet(10, cls.lab)
+        instantiate_publicnet(12, cls.lab)
+        instantiate_publicnet(14, cls.lab)
+
+        cls.lab_selected = 'lab_' + str(cls.lab.lab_user.id) + '_selected'
+        cls.host_selected = 'host_' + str(cls.host_profile.id) + '_selected'
+
+        cls.post_data = cls.build_post_data()
+
+        cls.client = Client()
+
+    def setUp(self):
+        self.client.login(
+            username=self.loginuser.username, password="testpassword")
+        self.booking, self.compute_hostnames, self.jump_hostname = self.create_multinode_generic_booking()
+
+    @classmethod
+    def build_post_data(cls):
+        post_data = {}
+        post_data['filter_field'] = '{"hosts":[{"host_' + str(cls.host_profile.id) + '":"true"}], "labs": [{"lab_' + str(cls.lab.lab_user.id) + '":"true"}]}'
+        post_data['purpose'] = 'purposefieldcontentstring'
+        post_data['project'] = 'projectfieldcontentstring'
+        post_data['length'] = '3'
+        post_data['ignore_this'] = 1
+        post_data['users'] = ''
+        post_data['hostname'] = 'hostnamefieldcontentstring'
+        post_data['image'] = str(cls.image.id)
+        post_data['installer'] = str(cls.installer.id)
+        post_data['scenario'] = str(cls.scenario.id)
+        return post_data
+
+    def post(self, changed_fields={}):
+        payload = self.post_data.copy()
+        payload.update(changed_fields)
+        response = self.client.post('/booking/quick/', payload)
+        return response
+
+    def generate_booking(self):
+        self.post()
+        return Booking.objects.first()
+
+    def test_valid_access_configs(self):
+        job = Job.objects.get(booking=self.booking)
+        self.assertIsNotNone(job)
+
+        access_configs = [r.config for r in AccessRelation.objects.filter(job=job).all()]
+
+        vpnconfigs = []
+        sshconfigs = []
+
+        for config in access_configs:
+            if config.access_type == "vpn":
+                vpnconfigs.append(config)
+            elif config.access_type == "ssh":
+                sshconfigs.append(config)
+            else:
+                self.fail(msg="Undefined accessconfig: " + config.access_type + " found")
+
+        user_set = []
+        user_set.append(self.booking.owner)
+        user_set += self.booking.collaborators.all()
+
+        for configs in [vpnconfigs, sshconfigs]:
+            for user in user_set:
+                configusers = [c.user for c in configs]
+                self.assertTrue(user in configusers)
+
+    def test_valid_network_configs(self):
+        job = Job.objects.get(booking=self.booking)
+        self.assertIsNotNone(job)
+
+        booking_hosts = self.booking.resource.hosts.all()
+
+        netrelation_set = HostNetworkRelation.objects.filter(job=job)
+        netconfig_set = [r.config for r in netrelation_set]
+
+        netrelation_hosts = [r.host for r in netrelation_set]
+
+        for config in netconfig_set:
+            for interface in config.interfaces.all():
+                self.assertTrue(interface.host in booking_hosts)
+
+        # if no interfaces are referenced that shouldn't have vlans,
+        # and no vlans exist outside those accounted for in netconfigs,
+        # then the api is faithfully representing networks
+        # as netconfigs reference resource_inventory models directly
+
+        # this test relies on the assumption that
+        # every interface is configured, whether it does or does not have vlans
+        # if this is not true, the  test fails
+
+        for host in booking_hosts:
+            self.assertTrue(host in netrelation_hosts)
+            relation = HostNetworkRelation.objects.filter(job=job).get(host=host)
+
+            # do 2 direction matching that interfaces are one to one
+            config = relation.config
+            for interface in config.interfaces.all():
+                self.assertTrue(interface in host.interfaces)
+            for interface in host.interfaces.all():
+                self.assertTrue(interface in config.interfaces)
+
+        for host in netrelation_hosts:
+            self.assertTrue(host in booking_hosts)
+
+    def test_valid_hardware_configs(self):
+        job = Job.objects.get(booking=self.booking)
+        self.assertIsNotNone(job)
+
+        hrelations = HostHardwareRelation.objects.filter(job=job).all()
+
+        job_hosts = [r.host for r in hrelations]
+
+        booking_hosts = self.booking.resource.hosts.all()
+
+        self.assertEqual(len(booking_hosts), len(job_hosts))
+
+        for relation in hrelations:
+            self.assertTrue(relation.host in booking_hosts)
+            self.assertEqual(relation.status, JobStatus.NEW)
+            config = relation.config
+            host = relation.host
+            self.assertEqual(config.hostname, host.template.resource.name)
+
+    def test_valid_software_configs(self):
+        job = Job.objects.get(booking=self.booking)
+        self.assertIsNotNone(job)
+
+        srelation = SoftwareRelation.objects.filter(job=job).first()
+        self.assertIsNotNone(srelation)
+
+        sconfig = srelation.config
+        self.assertIsNotNone(sconfig)
+
+        oconfig = sconfig.opnfv
+        self.assertIsNotNone(oconfig)
+
+        # not onetoone in models, but first() is safe here based on how ConfigBundle and a matching OPNFVConfig are created
+        # this should, however, be made explicit
+        self.assertEqual(oconfig.installer, self.booking.config_bundle.opnfv_config.first().installer.name)
+        self.assertEqual(oconfig.scenario, self.booking.config_bundle.opnfv_config.first().scenario.name)
+
+        for host in oconfig.roles.all():
+            role_name = host.config.opnfvRole.name
+            if str(role_name) == "Jumphost":
+                self.assertEqual(host.template.resource.name, self.jump_hostname)
+            elif str(role_name) == "Compute":
+                self.assertTrue(host.template.resource.name in self.compute_hostnames)
+            else:
+                self.fail(msg="Host with non-configured role name related to job: " + str(role_name))
+
+    def create_multinode_generic_booking(self):
+        topology = {}
+
+        compute_hostnames = ["cmp01", "cmp02", "cmp03"]
+
+        host_type = HostProfile.objects.first()
+
+        universal_networks = [
+            {"name": "public", "tagged": False, "public": True},
+            {"name": "admin", "tagged": True, "public": False}]
+        just_compute_networks = [{"name": "private", "tagged": True, "public": False}]
+        just_jumphost_networks = [{"name": "external", "tagged": True, "public": True}]
+
+        # generate a bunch of extra networks
+        for i in range(10):
+            net = {"tagged": False, "public": False}
+            net["name"] = "u_net" + str(i)
+            universal_networks.append(net)
+
+        jhost_info = {}
+        jhost_info["type"] = host_type
+        jhost_info["role"] = OPNFVRole.objects.get(name="Jumphost")
+        jhost_info["nets"] = self.make_networks(host_type, list(just_jumphost_networks + universal_networks))
+        jhost_info["image"] = self.image
+        topology["jump"] = jhost_info
+
+        for hostname in compute_hostnames:
+            host_info = {}
+            host_info["type"] = host_type
+            host_info["role"] = OPNFVRole.objects.get(name="Compute")
+            host_info["nets"] = self.make_networks(host_type, list(just_compute_networks + universal_networks))
+            host_info["image"] = self.image
+            topology[hostname] = host_info
+
+        booking = instantiate_booking(self.loginuser,
+                                      timezone.now(),
+                                      timezone.now() + timedelta(days=1),
+                                      "demobooking",
+                                      self.lab,
+                                      topology=topology,
+                                      installer=self.installer,
+                                      scenario=self.scenario)
+
+        if not booking.resource:
+            raise Exception("Booking does not have a resource when trying to pass to makeCompleteJob")
+        JobFactory.makeCompleteJob(booking)
+
+        return booking, compute_hostnames, "jump"
+
+    """
+    evenly distributes networks given across a given profile's interfaces
+    """
+    def make_networks(self, hostprofile, nets):
+        network_struct = []
+        count = hostprofile.interfaceprofile.all().count()
+        for i in range(count):
+            network_struct.append([])
+        while(nets):
+            index = len(nets) % count
+            network_struct[index].append(nets.pop())
+
+        return network_struct
index e98b5e6..558031d 100644 (file)
@@ -8,9 +8,15 @@
 ##############################################################################
 
 from django.contrib.auth.models import User
+from django.core.files.base import ContentFile
 
 import json
+import re
 
+from dashboard.exceptions import (
+    InvalidHostnameException
+)
+from booking.models import Booking
 from account.models import UserProfile, Lab, LabStatus, VlanManager, PublicNetwork
 from resource_inventory.models import (
     Host,
@@ -24,13 +30,23 @@ from resource_inventory.models import (
     Installer,
     OPNFVRole,
     RamProfile,
+    Network,
+    Vlan,
+    GenericResourceBundle,
+    GenericResource,
+    GenericHost,
+    ConfigBundle,
+    GenericInterface,
+    HostConfiguration,
+    OPNFVConfig,
 )
+from resource_inventory.resource_manager import ResourceManager
 
 
 class BookingContextData(object):
     def prepopulate(self, *args, **kwargs):
         self.loginuser = instantiate_user(False, username=kwargs.get("login_username", "newtestuser"), password="testpassword")
-        instantiate_userprofile(self.loginuser, True)
+        instantiate_userprofile(self.loginuser)
 
         lab_user = kwargs.get("lab_user", instantiate_user(True))
         self.lab = instantiate_lab(lab_user)
@@ -45,6 +61,179 @@ class BookingContextData(object):
         self.pubnet = instantiate_publicnet(10, self.lab)
 
 
+"""
+Info for instantiate_booking() function:
+[topology] argument structure:
+    the [topology] argument should describe the structure of the pod
+    the top level should be a dictionary, with each key being a hostname
+    each value in the top level should be a dictionary with two keys:
+        "type" should map to a host profile instance
+        "nets" should map to a list of interfaces each with a list of
+            dictionaries each defining a network in the format
+            { "name": "netname", "tagged": True|False, "public": True|False }
+            each network is defined if a matching name is not found
+
+    sample argument structure:
+        topology={
+            "host1": {
+                      "type": instanceOf HostProfile,
+                      "role": instanceOf OPNFVRole
+                      "image": instanceOf Image
+                      "nets": [
+                                0: [
+                                        0: { "name": "public", "tagged": True, "public": True },
+                                        1: { "name": "private", "tagged": False, "public": False },
+                                   ]
+                                1: []
+                              ]
+                  }
+        }
+"""
+
+
+def instantiate_booking(owner,
+                        start,
+                        end,
+                        booking_identifier,
+                        lab=Lab.objects.first(),
+                        purpose="purposetext",
+                        project="projecttext",
+                        collaborators=[],
+                        topology={},
+                        installer=None,
+                        scenario=None):
+    (grb, host_set) = instantiate_grb(topology, owner, lab, booking_identifier)
+    cb = instantiate_cb(grb, owner, booking_identifier, topology, host_set, installer, scenario)
+
+    resource = ResourceManager.getInstance().convertResourceBundle(grb, lab, cb)
+
+    booking = Booking()
+
+    booking.resource = resource
+    if not resource:
+        raise Exception("Resource not created")
+    booking.config_bundle = cb
+    booking.start = start
+    booking.end = end
+    booking.owner = owner
+    booking.purpose = purpose
+    booking.project = project
+    booking.lab = lab
+    booking.save()
+
+    return booking
+
+
+def instantiate_cb(grb,
+                   owner,
+                   booking_identifier,
+                   topology={},
+                   host_set={},
+                   installer=None,
+                   scenario=None):
+    cb = ConfigBundle()
+    cb.owner = owner
+    cb.name = str(booking_identifier) + "_cb"
+    cb.description = "cb generated by instantiate_cb() method"
+    cb.save()
+
+    opnfvconfig = OPNFVConfig()
+    opnfvconfig.installer = installer
+    opnfvconfig.scenario = scenario
+    opnfvconfig.bundle = cb
+    opnfvconfig.save()
+
+    # generate host configurations based on topology and host set
+    for hostname, host_info in topology.items():
+        hconf = HostConfiguration()
+        hconf.bundle = cb
+        hconf.host = host_set[hostname]
+        hconf.image = host_info["image"]
+        hconf.opnfvRole = host_info["role"]
+        hconf.save()
+    return cb
+
+
+def instantiate_grb(topology,
+                    owner,
+                    lab,
+                    booking_identifier):
+
+    grb = GenericResourceBundle(owner=owner, lab=lab)
+    grb.name = str(booking_identifier) + "_grb"
+    grb.description = "grb generated by instantiate_grb() method"
+    grb.save()
+
+    networks = {}
+    host_set = {}
+
+    for hostname in topology.keys():
+        info = topology[hostname]
+        host_profile = info["type"]
+
+        # need to construct host from hostname and type
+        ghost = instantiate_ghost(grb, host_profile, hostname)
+        host_set[hostname] = ghost
+
+        ghost.save()
+
+        # set up networks
+        nets = info["nets"]
+        for interface_index, interface_profile in enumerate(host_profile.interfaceprofile.all()):
+            generic_interface = GenericInterface()
+            generic_interface.host = ghost
+            generic_interface.profile = interface_profile
+            generic_interface.save()
+
+            netconfig = nets[interface_index]
+            for network_index, network_info in enumerate(netconfig):
+                network_name = network_info["name"]
+                network = None
+                if network_name in networks:
+                    network = networks[network_name]
+                else:
+                    network = Network()
+                    network.name = network_name
+                    network.vlan_id = lab.vlan_manager.get_vlan()
+                    network.save()
+                    networks[network_name] = network
+                    if network_info["public"]:
+                        public_net = lab.vlan_manager.get_public_vlan()
+                        if not public_net:
+                            raise Exception("No more public networks available")
+                        lab.vlan_manager.reserve_public_vlan(public_net.vlan)
+                        network.vlan_id = public_net.vlan
+                    else:
+                        private_net = lab.vlan_manager.get_vlan()
+                        if not private_net:
+                            raise Exception("No more generic vlans are available")
+                        lab.vlan_manager.reserve_vlans([private_net])
+                        network.vlan_id = private_net
+
+                vlan = Vlan()
+                vlan.vlan_id = network.vlan_id
+                vlan.public = network_info["public"]
+                vlan.tagged = network_info["tagged"]
+                vlan.save()
+                generic_interface.vlans.add(vlan)
+
+    return (grb, host_set)
+
+
+def instantiate_ghost(grb, host_profile, hostname):
+    if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})$", hostname):
+        raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
+    gresource = GenericResource(bundle=grb, name=hostname)
+    gresource.save()
+
+    ghost = GenericHost()
+    ghost.resource = gresource
+    ghost.profile = host_profile
+    ghost.save()
+
+    return ghost
+
+
 def instantiate_user(is_superuser,
                      username="testuser",
                      password="testpassword",
@@ -58,16 +247,17 @@ def instantiate_user(is_superuser,
     return user
 
 
-def instantiate_userprofile(user=None, can_book_multiple=False):
-    if not user:
-        user = instantiate_user(True, 'test_user', 'test_pass', 'test_user@test_site.org')
-    userprofile = UserProfile()
-    userprofile.user = user
-    userprofile.booking_privledge = can_book_multiple
+def instantiate_userprofile(user, email_addr="email@email.com", company="company", full_name="John Doe", booking_privledge=True, ssh_file=None):
+    up = UserProfile()
+    up.email_address = email_addr
+    up.company = company
+    up.full_name = full_name
+    up.booking_privledge = booking_privledge
+    up.user = user
+    up.save()
+    up.ssh_public_key.save("user_ssh_key", ssh_file if ssh_file else ContentFile("public key content string"))
 
-    userprofile.save()
-
-    return user
+    return up
 
 
 def instantiate_vlanmanager(vlans=None,