def test_kernel_to_debian_architecture(self): self.assertEquals(self.kernel, debian_to_kernel_architecture(self.debian))
def test_compose_multiple_interface_constraints(self): pod_id = factory.make_name("pod_id") context = self.make_parameters_context() request = make_requested_machine() request.interfaces = [ RequestedMachineInterface( ifname=factory.make_name("ifname"), attach_name=factory.make_name("bridge_name"), attach_type="bridge", attach_options=None, ) for _ in range(3) ] # LXD uses 'bridged' while MAAS uses 'bridge' so convert # the nictype as this is what we expect from LXDPodDriver.compose. expected_interfaces = [ { "name": request.interfaces[i].ifname, "parent": request.interfaces[i].attach_name, "nictype": "bridged", "type": "nic", } for i in range(3) ] expected_interfaces[0]["boot.priority"] = "1" driver = lxd_module.LXDPodDriver() Client = self.patch(driver, "get_client") client = Client.return_value mock_profile = Mock() mock_profile.name = random.choice(["maas", "default"]) profile_devices = { "eth0": { "name": "eth0", "nictype": "bridged", "parent": "lxdbr0", "type": "nic", }, "eth1": { "boot.priority": "1", "name": "eth1", "nictype": "bridged", "parent": "virbr1", "type": "nic", }, "root": { "boot.priority": "0", "path": "/", "pool": "default", "type": "disk", "size": "20GB", }, } mock_profile.devices = profile_devices client.profiles.get.return_value = mock_profile mock_storage_pools = Mock() client.storage_pools.all.return_value = mock_storage_pools mock_get_usable_storage_pool = self.patch( driver, "get_usable_storage_pool" ) usable_pool = factory.make_name("pool") mock_get_usable_storage_pool.return_value = usable_pool mock_get_best_nic_from_profile = self.patch( driver, "get_best_nic_from_profile" ) mock_get_best_nic_from_profile.return_value = ( "eth1", profile_devices["eth1"], ) mock_machine = Mock() client.virtual_machines.create.return_value = mock_machine mock_get_discovered_machine = self.patch( driver, "get_discovered_machine" ) mock_get_discovered_machine.return_value = sentinel.discovered_machine definition = { "name": request.hostname, "architecture": debian_to_kernel_architecture( request.architecture ), "config": { "limits.cpu": str(request.cores), "limits.memory": str(request.memory * 1024 ** 2), "security.secureboot": "false", }, "profiles": [mock_profile.name], "source": {"type": "none"}, "devices": { "root": { "path": "/", "type": "disk", "pool": usable_pool, "size": str(request.block_devices[0].size), "boot.priority": "0", }, expected_interfaces[0]["name"]: expected_interfaces[0], expected_interfaces[1]["name"]: expected_interfaces[1], expected_interfaces[2]["name"]: expected_interfaces[2], "eth1": {"type": "none"}, "eth0": {"type": "none"}, }, } discovered_machine, empty_hints = yield driver.compose( pod_id, context, request ) self.assertThat( client.virtual_machines.create, MockCalledOnceWith(definition, wait=True), ) self.assertEquals(sentinel.discovered_machine, discovered_machine) self.assertThat( empty_hints, MatchesAll( IsInstance(DiscoveredPodHints), MatchesStructure( cores=Equals(-1), cpu_speed=Equals(-1), memory=Equals(-1), local_storage=Equals(-1), local_disks=Equals(-1), iscsi_storage=Equals(-1), ), ), )
def compose(self, pod_id: str, context: dict, request: RequestedMachine): """Compose a virtual machine.""" client = yield self.get_client(pod_id, context) # Check to see if there is a maas profile. If not, use the default. try: profile = yield deferToThread(client.profiles.get, "maas") except NotFound: # Fall back to default try: profile = yield deferToThread(client.profiles.get, "default") except NotFound: raise LXDPodError( f"Pod {pod_id}: MAAS needs LXD to have either a 'maas' " "profile or a 'default' profile, defined.") resources = yield deferToThread(lambda: client.resources) definition = { "name": request.hostname, "architecture": debian_to_kernel_architecture(request.architecture), "config": { "limits.cpu": str(request.cores), "limits.memory": str(request.memory * 1024**2), # LP: 1867387 - Disable secure boot until its fixed in MAAS "security.secureboot": "false", }, "profiles": [profile.name], # Image source is empty as we get images # from MAAS when netbooting. "source": { "type": "none" }, } # Add disk to the definition. # XXX: LXD VMs currently only support one virtual block device. # Loop will need to be modified once LXD has multiple virtual # block device support. devices = {} storage_pools = yield deferToThread(client.storage_pools.all) default_storage_pool = context.get("default_storage_pool_id", context.get("default_storage_pool")) for idx, disk in enumerate(request.block_devices): usable_pool = self.get_usable_storage_pool(disk, storage_pools, default_storage_pool) devices["root"] = { "path": "/", "type": "disk", "pool": usable_pool, "size": str(disk.size), "boot.priority": "0", } # Create and attach interfaces to the machine. # The reason we are doing this after the machine is created # is because pylxd doesn't have a way to override the devices # that are defined in the profile. Since the profile is provided # by the user, we have no idea how many interfaces are defined. # # Currently, only the bridged type is supported with virtual machines. # https://lxd.readthedocs.io/en/latest/instances/#device-types nic_devices = {} profile_devices = profile.devices device_names = [] boot = True for interface in request.interfaces: if interface.ifname is None: # No interface constraints sent so use the best # nic device from the profile's devices. device_name, device = self.get_best_nic_device_from_profile( profile_devices) nic_devices[device_name] = device if "boot.priority" not in device and boot: nic_devices[device_name]["boot.priority"] = "1" boot = False device_names.append(device_name) else: nic_devices[interface.ifname] = get_lxd_nic_device(interface) # Set to boot from the first nic if boot: nic_devices[interface.ifname]["boot.priority"] = "1" boot = False device_names.append(interface.ifname) # Iterate over all of the profile's devices with type=nic # and set to type=none if not nic_device. This overrides # the device settings on the profile used by the machine. for dk, dv in profile_devices.items(): if dk not in device_names and dv["type"] == "nic": nic_devices[dk] = {"type": "none"} # Merge the devices and attach the devices to the defintion. for k, v in nic_devices.items(): devices[k] = v definition["devices"] = devices # Create the machine. machine = yield deferToThread(client.virtual_machines.create, definition, wait=True) # Pod hints are updated on the region after the machine # is composed. discovered_machine = yield ensureDeferred( self.get_discovered_machine(client, machine, storage_pools, request=request)) # Update the machine cpu speed. discovered_machine.cpu_speed = lxd_cpu_speed(resources) return discovered_machine, DiscoveredPodHints()
def test_compose_no_interface_constraints(self): pod_id = factory.make_name("pod_id") context = self.make_parameters_context() request = make_requested_machine() driver = lxd_module.LXDPodDriver() Client = self.patch(driver, "get_client") client = Client.return_value mock_profile = Mock() mock_profile.name = random.choice(["maas", "default"]) profile_devices = { "eth0": { "name": "eth0", "nictype": "bridged", "parent": "lxdbr0", "type": "nic", }, "eth1": { "boot.priority": "1", "name": "eth1", "nictype": "bridged", "parent": "virbr1", "type": "nic", }, "root": { "boot.priority": "0", "path": "/", "pool": "default", "type": "disk", "size": "20GB", }, } mock_profile.devices = profile_devices client.profiles.get.return_value = mock_profile mock_storage_pools = Mock() client.storage_pools.all.return_value = mock_storage_pools mock_get_usable_storage_pool = self.patch( driver, "get_usable_storage_pool" ) usable_pool = factory.make_name("pool") mock_get_usable_storage_pool.return_value = usable_pool mock_get_best_nic_from_profile = self.patch( driver, "get_best_nic_from_profile" ) mock_get_best_nic_from_profile.return_value = ( "eth1", profile_devices["eth1"], ) mock_machine = Mock() client.virtual_machines.create.return_value = mock_machine mock_get_discovered_machine = self.patch( driver, "get_discovered_machine" ) mock_get_discovered_machine.side_effect = async_succeed( sentinel.discovered_machine ) definition = { "name": request.hostname, "architecture": debian_to_kernel_architecture( request.architecture ), "config": { "limits.cpu": str(request.cores), "limits.memory": str(request.memory * 1024 ** 2), "limits.memory.hugepages": "false", "security.secureboot": "false", }, "profiles": [mock_profile.name], "source": {"type": "none"}, "devices": { "root": { "path": "/", "type": "disk", "pool": usable_pool, "size": str(request.block_devices[0].size), "boot.priority": "0", }, "eth1": profile_devices["eth1"], "eth0": {"type": "none"}, }, } discovered_machine, empty_hints = yield driver.compose( pod_id, context, request ) self.assertThat( client.virtual_machines.create, MockCalledOnceWith(definition, wait=True), ) self.assertEquals(sentinel.discovered_machine, discovered_machine) self.assertThat( empty_hints, MatchesAll( IsInstance(DiscoveredPodHints), MatchesStructure( cores=Equals(-1), cpu_speed=Equals(-1), memory=Equals(-1), local_storage=Equals(-1), local_disks=Equals(-1), iscsi_storage=Equals(-1), ), ), )