def get_discovered_machine(self, machine, request=None): """Gets the discovered machine.""" # Discovered machine. discovered_machine = DiscoveredMachine(architecture="", cores=0, cpu_speed=0, memory=0, interfaces=[], block_devices=[], tags=['virtual']) discovered_machine.hostname = machine discovered_machine.architecture = self.get_machine_arch(machine) discovered_machine.cores = self.get_machine_cpu_count(machine) discovered_machine.memory = self.get_machine_memory(machine) state = self.get_machine_state(machine) discovered_machine.power_state = VM_STATE_TO_POWER_STATE[state] discovered_machine.power_parameters = { 'power_id': machine, } # Discover block devices. block_devices = [] devices = self.list_machine_block_devices(machine) for idx, device in enumerate(devices): # Block device. # When request is provided map the tags from the request block # devices to the discovered block devices. This ensures that # composed machine has the requested tags on the block device. tags = [] if request is not None: tags = request.block_devices[idx].tags size = self.get_machine_local_storage(machine, device) if size is None: # Bug lp:1690144 - When a domain has a block device where its # storage path is no longer available. The domain cannot be # started when the storage path is missing, so we don't add it # to MAAS. maaslog.error( "Unable to discover machine '%s' in virsh pod: storage " "device '%s' is missing its storage backing." % (machine, device)) return None block_devices.append( DiscoveredMachineBlockDevice(model=None, serial=None, size=size, id_path="/dev/%s" % device, tags=tags)) discovered_machine.block_devices = block_devices # Discover interfaces. interfaces = [] mac_addresses = self.list_machine_mac_addresses(machine) boot = True for mac in mac_addresses: interfaces.append( DiscoveredMachineInterface(mac_address=mac, boot=boot)) boot = False discovered_machine.interfaces = interfaces return discovered_machine
class TestDiscoveredMachine(MAASTestCase): example = DiscoveredMachine( hostname=factory.make_name("hostname"), architecture="amd64/generic", cores=random.randint(1, 8), cpu_speed=random.randint(1000, 2000), memory=random.randint(1024, 8192), power_state=factory.make_name("unknown"), power_parameters={"power_id": factory.make_name("power_id")}, interfaces=[ DiscoveredMachineInterface(mac_address=factory.make_mac_address()) for _ in range(3) ], block_devices=[ DiscoveredMachineBlockDevice( model=factory.make_name("model"), serial=factory.make_name("serial"), size=random.randint(512, 1024), id_path=factory.make_name("/dev/vda"), ) for _ in range(3) ], tags=[factory.make_name("tag") for _ in range(3)], ) def test_round_trip(self): argument = arguments.AmpDiscoveredMachine() encoded = argument.toString(self.example) self.assertThat(encoded, IsInstance(bytes)) decoded = argument.fromString(encoded) self.assertThat(decoded, Equals(self.example))
def test_machine(self): hostname = factory.make_name("hostname") cores = random.randint(1, 8) cpu_speed = random.randint(1000, 2000) memory = random.randint(4096, 8192) power_state = factory.make_name("unknown") interfaces = [ DiscoveredMachineInterface(mac_address=factory.make_mac_address()) for _ in range(3) ] block_devices = [ DiscoveredMachineBlockDevice( model=factory.make_name("model"), serial=factory.make_name("serial"), size=random.randint(512, 1024), ) for _ in range(3) ] tags = [factory.make_name("tag") for _ in range(3)] machine = DiscoveredMachine( hostname=hostname, architecture="amd64/generic", cores=cores, cpu_speed=cpu_speed, memory=memory, power_state=power_state, interfaces=interfaces, block_devices=block_devices, tags=tags, ) self.assertEquals(cores, machine.cores) self.assertEquals(cpu_speed, machine.cpu_speed) self.assertEquals(memory, machine.memory) self.assertEquals(interfaces, machine.interfaces) self.assertEquals(block_devices, machine.block_devices) self.assertEquals(tags, machine.tags)
def test_handles_driver_resolving_to_tuple_of_discovered(self): fake_driver = MagicMock() fake_driver.name = factory.make_name("pod") fake_request = self.make_requested_machine() pod_id = random.randint(1, 10) pod_name = factory.make_name("pod") machine = DiscoveredMachine( hostname=factory.make_name("hostname"), architecture="amd64/generic", cores=random.randint(1, 8), cpu_speed=random.randint(1000, 3000), memory=random.randint(1024, 8192), block_devices=[], interfaces=[], ) hints = DiscoveredPodHints( cores=random.randint(1, 8), cpu_speed=random.randint(1000, 2000), memory=random.randint(1024, 8192), local_storage=0, ) fake_driver.compose.return_value = succeed((machine, hints)) self.patch(PodDriverRegistry, "get_item").return_value = fake_driver result = yield pods.compose_machine( fake_driver.name, {}, fake_request, pod_id=pod_id, name=pod_name ) self.assertEqual({"machine": machine, "hints": hints}, result)
def make_compose_machine_result(self, pod): composed_machine = DiscoveredMachine( hostname=factory.make_name('hostname'), architecture=pod.architectures[0], cores=1, memory=1024, cpu_speed=300, block_devices=[], interfaces=[]) pod_hints = DiscoveredPodHints( cores=random.randint(0, 10), memory=random.randint(1024, 4096), cpu_speed=random.randint(1000, 3000), local_storage=0) return composed_machine, pod_hints
def make_compose_machine_result(pod): composed_machine = DiscoveredMachine( hostname=factory.make_name("hostname"), architecture=pod.architectures[0], cores=1, memory=1024, cpu_speed=300, power_parameters={"instance_name": factory.make_name("instance")}, block_devices=[], interfaces=[], ) pod_hints = DiscoveredPodHints( cores=random.randint(0, 10), memory=random.randint(1024, 4096), cpu_speed=random.randint(1000, 3000), local_storage=0, ) return composed_machine, pod_hints
def test_pod_asdict(self): hostname = factory.make_name("hostname") cores = random.randint(1, 8) cpu_speed = random.randint(1000, 2000) memory = random.randint(4096, 8192) local_storage = random.randint(4096, 8192) local_disks = random.randint(1, 8) iscsi_storage = random.randint(4096, 8192) hints = DiscoveredPodHints( cores=random.randint(1, 8), cpu_speed=random.randint(1000, 2000), memory=random.randint(4096, 8192), local_storage=random.randint(4096, 8192), local_disks=random.randint(1, 8), iscsi_storage=random.randint(4096, 8192), ) machines = [] tags = [factory.make_name("tag") for _ in range(3)] storage_pools = [ DiscoveredPodStoragePool( id=factory.make_name("id"), name=factory.make_name("name"), storage=random.randint(4096, 8192), type="dir", path="/var/%s" % factory.make_name("dir"), ) for _ in range(3) ] for _ in range(3): cores = random.randint(1, 8) cpu_speed = random.randint(1000, 2000) memory = random.randint(4096, 8192) power_state = factory.make_name("unknown") power_parameters = {"power_id": factory.make_name("power_id")} interfaces = [ DiscoveredMachineInterface( mac_address=factory.make_mac_address()) for _ in range(3) ] block_devices = [ DiscoveredMachineBlockDevice( model=factory.make_name("model"), serial=factory.make_name("serial"), size=random.randint(512, 1024), id_path=factory.make_name("/dev/vda"), ) for _ in range(3) ] for _ in range(3): block_devices.append( DiscoveredMachineBlockDevice( model=None, serial=None, size=random.randint(512, 1024), type=BlockDeviceType.ISCSI, iscsi_target=self.make_iscsi_target(), storage_pool=factory.make_name("pool"), )) tags = [factory.make_name("tag") for _ in range(3)] machines.append( DiscoveredMachine( hostname=hostname, architecture="amd64/generic", cores=cores, cpu_speed=cpu_speed, memory=memory, power_state=power_state, power_parameters=power_parameters, interfaces=interfaces, block_devices=block_devices, tags=tags, )) pod = DiscoveredPod( architectures=["amd64/generic"], cores=cores, cpu_speed=cpu_speed, memory=memory, local_storage=local_storage, local_disks=local_disks, iscsi_storage=iscsi_storage, hints=hints, machines=machines, tags=tags, storage_pools=storage_pools, ) self.assertThat( pod.asdict(), MatchesDict({ "architectures": Equals(["amd64/generic"]), "name": Equals(None), "cores": Equals(cores), "cpu_speed": Equals(cpu_speed), "memory": Equals(memory), "local_storage": Equals(local_storage), "local_disks": Equals(local_disks), "iscsi_storage": Equals(iscsi_storage), "mac_addresses": Equals([]), "capabilities": Equals(pod.capabilities), "hints": MatchesDict({ "cores": Equals(hints.cores), "cpu_speed": Equals(hints.cpu_speed), "memory": Equals(hints.memory), "local_storage": Equals(hints.local_storage), "local_disks": Equals(hints.local_disks), "iscsi_storage": Equals(hints.iscsi_storage), }), "machines": MatchesListwise([ MatchesDict({ "hostname": Equals(machine.hostname), "architecture": Equals("amd64/generic"), "cores": Equals(machine.cores), "cpu_speed": Equals(machine.cpu_speed), "memory": Equals(machine.memory), "power_state": Equals(machine.power_state), "power_parameters": Equals(machine.power_parameters), "interfaces": MatchesListwise([ MatchesDict({ "mac_address": Equals(interface.mac_address), "vid": Equals(interface.vid), "tags": Equals(interface.tags), "boot": Equals(False), "attach_type": Is(None), "attach_name": Is(None), }) for interface in machine.interfaces ]), "block_devices": MatchesListwise([ MatchesDict({ "model": Equals(block_device.model), "serial": Equals(block_device.serial), "size": Equals(block_device.size), "block_size": Equals(block_device.block_size), "tags": Equals(block_device.tags), "id_path": Equals(block_device.id_path), "type": Equals(block_device.type), "iscsi_target": Equals(block_device.iscsi_target), "storage_pool": Equals(block_device.storage_pool), }) for block_device in machine.block_devices ]), "tags": Equals(machine.tags), }) for machine in machines ]), "tags": Equals(tags), "storage_pools": MatchesListwise([ MatchesDict({ "id": Equals(pool.id), "name": Equals(pool.name), "storage": Equals(pool.storage), "type": Equals(pool.type), "path": Equals(pool.path), }) for pool in storage_pools ]), }), )
def test_pod(self): hostname = factory.make_name("hostname") cores = random.randint(1, 8) cpu_speed = random.randint(1000, 2000) memory = random.randint(4096, 8192) local_storage = random.randint(4096, 8192) iscsi_storage = random.randint(4096, 8192) hints = DiscoveredPodHints( cores=random.randint(1, 8), cpu_speed=random.randint(1000, 2000), memory=random.randint(4096, 8192), local_storage=random.randint(4096, 8192), iscsi_storage=random.randint(4096, 8192), ) machines = [] for _ in range(3): cores = random.randint(1, 8) cpu_speed = random.randint(1000, 2000) memory = random.randint(4096, 8192) power_state = factory.make_name("unknown") power_parameters = {"power_id": factory.make_name("power_id")} interfaces = [ DiscoveredMachineInterface( mac_address=factory.make_mac_address()) for _ in range(3) ] block_devices = [ DiscoveredMachineBlockDevice( model=factory.make_name("model"), serial=factory.make_name("serial"), size=random.randint(512, 1024), ) for _ in range(3) ] for _ in range(3): block_devices.append( DiscoveredMachineBlockDevice( model=None, serial=None, size=random.randint(512, 1024), type=BlockDeviceType.ISCSI, iscsi_target=self.make_iscsi_target(), )) tags = [factory.make_name("tag") for _ in range(3)] machines.append( DiscoveredMachine( hostname=hostname, architecture="amd64/generic", cores=cores, cpu_speed=cpu_speed, memory=memory, power_state=power_state, power_parameters=power_parameters, interfaces=interfaces, block_devices=block_devices, tags=tags, )) pod = DiscoveredPod( architectures=["amd64/generic"], cores=cores, cpu_speed=cpu_speed, memory=memory, local_storage=local_storage, iscsi_storage=iscsi_storage, hints=hints, machines=machines, ) self.assertEquals(cores, pod.cores) self.assertEquals(cpu_speed, pod.cpu_speed) self.assertEquals(memory, pod.memory) self.assertEquals(local_storage, pod.local_storage) self.assertEquals(iscsi_storage, pod.iscsi_storage) self.assertEquals(machines, pod.machines)
def _get_discovered_machine(self, client, machine, storage_pools, request=None): """Get the discovered machine.""" # Check the power state first. state = machine.status_code try: power_state = LXD_VM_POWER_STATE[state] except KeyError: maaslog.error( f"{machine.name}: Unknown power status code: {state}") power_state = "unknown" def _get_discovered_block_device(name, device, requested_device=None): tags = requested_device.tags if requested_device else [] # When LXD creates a QEMU disk the serial is always lxd_{device # name}. The device name is commonly "root" for the first disk. The # model and serial must be correctly defined here otherwise MAAS # will delete the disk created during composition which results in # losing the storage pool link. Without the storage pool link MAAS # can't determine how much of the storage pool has been used. serial = f"lxd_{name}" source = device.get("source") if source: pool = client.storage_pools.get(device["pool"]) volume = pool.volumes.get("custom", source) size = volume.config.get("size") else: size = device.get("size") # Default disk size is 10GB in LXD size = convert_lxd_byte_suffixes(size or "10GB") return DiscoveredMachineBlockDevice( model="QEMU HARDDISK", serial=serial, id_path=f"/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_{serial}", size=size, tags=tags, storage_pool=device.get("pool"), ) expanded_config = machine.expanded_config iface_to_mac = { key.split(".")[1]: value for key, value in expanded_config.items() if key.endswith("hwaddr") } def _get_discovered_interface(name, device, boot): if "network" in device: # Try finding the nictype from the networks. # XXX: This should work for "bridge" networks, # but will most likely produce weird results for the # other types. network = client.networks.get(device["network"]) attach_type = network.type attach_name = network.name else: attach_name = device["parent"] nictype = device["nictype"] attach_type = (InterfaceAttachType.BRIDGE if nictype == "bridged" else nictype) mac = device.get("hwaddr") if mac is None: mac = iface_to_mac.get(name) return DiscoveredMachineInterface( mac_address=mac, vid=int(device.get("vlan", 0)), boot=boot, attach_type=attach_type, attach_name=attach_name, ) extra_block_devices = 0 block_devices = [] interfaces = [] for name, device in machine.expanded_devices.items(): if device["type"] == "disk": requested_device = None if request: # for composed VMs, the root disk is always the first # one. Adjust the index so that it matches the requested # device if name == "root": index = 0 else: extra_block_devices += 1 index = extra_block_devices requested_device = request.block_devices[index] block_devices.append( _get_discovered_block_device( name, device, requested_device=requested_device)) elif device["type"] == "nic": interfaces.append( _get_discovered_interface(name, device, not interfaces)) # LXD uses different suffixes to store memory so make # sure we convert to MiB, which is what MAAS uses. memory = expanded_config.get("limits.memory") if memory is not None: memory = convert_lxd_byte_suffixes(memory, divisor=1024**2) else: memory = 1024 hugepages_backed = _get_bool( expanded_config.get("limits.memory.hugepages")) cores, pinned_cores = _parse_cpu_cores( expanded_config.get("limits.cpu")) return DiscoveredMachine( hostname=machine.name, architecture=kernel_to_debian_architecture(machine.architecture), # 1 core and 1GiB of memory (we need it in MiB) is default for # LXD if not specified. cores=cores, memory=memory, cpu_speed=0, interfaces=interfaces, block_devices=block_devices, power_state=power_state, power_parameters={ "instance_name": machine.name, "project": client.project, }, tags=[], hugepages_backed=hugepages_backed, pinned_cores=pinned_cores, # LXD VMs use only UEFI. bios_boot_method="uefi", )
def fromString(self, inString): # Circular imports. from provisioningserver.drivers.pod import DiscoveredMachine data = super(AmpDiscoveredMachine, self).fromString(inString) return DiscoveredMachine.fromdict(data)
async def get_discovered_machine(self, client, machine, storage_pools, request=None): """Get the discovered machine.""" # Check the power state first. state = machine.status_code try: power_state = LXD_VM_POWER_STATE[state] except KeyError: maaslog.error( f"{machine.name}: Unknown power status code: {state}") power_state = "unknown" expanded_config = machine.expanded_config expanded_devices = machine.expanded_devices # Discover block devices. block_devices = [] for idx, device in enumerate(expanded_devices): # Block device. # When request is provided map the tags from the request block # devices to the discovered block devices. This ensures that # composed machine has the requested tags on the block device. tags = [] if (request is not None and expanded_devices[device]["type"] == "disk"): tags = request.block_devices[0].tags device_info = expanded_devices[device] if device_info["type"] == "disk": # When LXD creates a QEMU disk the serial is always # lxd_{device name}. The device_name is defined by # the LXD profile or when adding a device. This is # commonly "root" for the first disk. The model and # serial must be correctly defined here otherwise # MAAS will delete the disk created during composition # which results in losing the storage pool link. Without # the storage pool link MAAS can't determine how much # of the storage pool has been used. serial = f"lxd_{device}" # Default disk size is 10GB. size = convert_lxd_byte_suffixes( device_info.get("size", "10GB")) storage_pool = device_info.get("pool") block_devices.append( DiscoveredMachineBlockDevice( model="QEMU HARDDISK", serial=serial, id_path= f"/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_{serial}", size=size, tags=tags, storage_pool=storage_pool, )) # Discover interfaces. interfaces = [] boot = True config_mac_address = {} for configuration in expanded_config: if configuration.endswith("hwaddr"): mac = expanded_config[configuration] name = configuration.split(".")[1] config_mac_address[name] = mac for name, device in expanded_devices.items(): if device["type"] != "nic": continue device = expanded_devices[name] if "network" in device: # Try finding the nictype from the networks. # XXX: This should work for "bridge" networks, # but will most likely produce weird results for the # other types. network = await deferToThread(client.networks.get, device["network"]) attach_type = network.type attach_name = network.name else: attach_name = device["parent"] nictype = device["nictype"] attach_type = (InterfaceAttachType.BRIDGE if nictype == "bridged" else nictype) mac = device.get("hwaddr") if mac is None: mac = config_mac_address.get(name) interfaces.append( DiscoveredMachineInterface( mac_address=mac, vid=int(device.get("vlan", get_vid_from_ifname(name))), boot=boot, attach_type=attach_type, attach_name=attach_name, )) boot = False # LXD uses different suffixes to store memory so make # sure we convert to MiB, which is what MAAS uses. memory = expanded_config.get("limits.memory") if memory is not None: memory = convert_lxd_byte_suffixes(memory, divisor=1024**2) else: memory = 1024 hugepages_backed = _get_bool( expanded_config.get("limits.memory.hugepages")) cores, pinned_cores = _parse_cpu_cores( expanded_config.get("limits.cpu")) return DiscoveredMachine( hostname=machine.name, architecture=kernel_to_debian_architecture(machine.architecture), # 1 core and 1GiB of memory (we need it in MiB) is default for # LXD if not specified. cores=cores, memory=memory, cpu_speed=0, interfaces=interfaces, block_devices=block_devices, power_state=power_state, power_parameters={"instance_name": machine.name}, tags=[], hugepages_backed=hugepages_backed, pinned_cores=pinned_cores, # LXD VMs use only UEFI. bios_boot_method="uefi", )