def get_usable_pool(self, disk, default_pool=None): """Return the pool that has enough space for `disk.size`.""" pools = self.get_pod_storage_pools(with_available=True) filtered_pools = [pool for pool in pools if pool.name in disk.tags] if filtered_pools: for pool in filtered_pools: if disk.size <= pool.available: return pool.name raise PodInvalidResources( "Not enough storage space on storage pools: %s" % (', '.join([pool.name for pool in filtered_pools]))) if default_pool: filtered_pools = [ pool for pool in pools if pool.id == default_pool ] if not filtered_pools: filtered_pools = [ pool for pool in pools if pool.name == default_pool ] if filtered_pools: default_pool = filtered_pools[0] if disk.size <= default_pool.available: return default_pool.name raise PodInvalidResources( "Not enough space in default storage pool: %s" % (default_pool.name)) raise VirshError("Default storage pool '%s' doesn't exist." % default_pool) for pool in pools: if disk.size <= pool.available: return pool.name raise PodInvalidResources( "Not enough storage space on any storage pools: %s" % (', '.join([pool.name for pool in pools])))
def _get_usable_storage_pool(self, disk, storage_pools, default_storage_pool=None): """Return the storage pool and type that has enough space for `disk.size`.""" # Filter off of tags. filtered_storage_pools = [ storage_pool for storage_pool in storage_pools if storage_pool.name in disk.tags ] if filtered_storage_pools: for storage_pool in filtered_storage_pools: resources = storage_pool.resources.get() available = resources.space["total"] - resources.space["used"] if disk.size <= available: return storage_pool raise PodInvalidResources( "Not enough storage space on storage pools: %s" % (", ".join([ storage_pool.name for storage_pool in filtered_storage_pools ]))) # Filter off of default storage pool name. if default_storage_pool: filtered_storage_pools = [ storage_pool for storage_pool in storage_pools if storage_pool.name == default_storage_pool ] if filtered_storage_pools: default_storage_pool = filtered_storage_pools[0] resources = default_storage_pool.resources.get() available = resources.space["total"] - resources.space["used"] if disk.size <= available: return default_storage_pool raise PodInvalidResources( f"Not enough space in default storage pool: {default_storage_pool.name}" ) raise LXDPodError( f"Default storage pool '{default_storage_pool}' doesn't exist." ) # No filtering, just find a storage pool with enough space. for storage_pool in storage_pools: resources = storage_pool.resources.get() available = resources.space["total"] - resources.space["used"] if disk.size <= available: return storage_pool raise PodInvalidResources( "Not enough storage space on any storage pools: %s" % (", ".join([storage_pool.name for storage_pool in storage_pools])))
def create_domain(self, request, default_pool=None): """Create a domain based on the `request` with hostname. For now this just uses `get_best_network` to connect the interfaces of the domain to the network. """ # Create all the block devices first. If cannot complete successfully # then fail early. The driver currently doesn't do any tag matching # for block devices, and is not really required for Virsh. created_disks = [] for idx, disk in enumerate(request.block_devices): try: disk_info = self.create_local_volume(disk, default_pool) except Exception: self.cleanup_disks(created_disks) raise else: if disk_info is None: raise PodInvalidResources( "not enough space for disk %d." % idx) else: created_disks.append(disk_info) # Construct the domain XML. domain_params = self.get_domain_capabilities() domain_params['name'] = request.hostname domain_params['uuid'] = str(uuid.uuid4()) domain_params['arch'] = ARCH_FIX_REVERSE[request.architecture] domain_params['cores'] = str(request.cores) domain_params['memory'] = str(request.memory) domain_xml = DOM_TEMPLATE.format(**domain_params) # Define the domain in virsh. with NamedTemporaryFile() as f: f.write(domain_xml.encode('utf-8')) f.write(b'\n') f.flush() self.run(['define', f.name]) # Attach the created disks in order. for idx, (pool, volume) in enumerate(created_disks): block_name = self.get_block_name_from_idx(idx) self.attach_local_volume( request.hostname, pool, volume, block_name) # Attach new interfaces to the best possible network. best_network = self.get_best_network() for _ in request.interfaces: self.attach_interface(request.hostname, best_network) # Set machine to autostart. self.set_machine_autostart(request.hostname) # Setup the domain to PXE boot. self.configure_pxe_boot(request.hostname) # Return the result as a discovered machine. return self.get_discovered_machine(request.hostname, request=request)
def get_pod_local_storage(self): """Gets the total local storage for the pod.""" pools = self.get_pod_pool_size_map("Capacity") if len(pools) == 0: maaslog.error("Failed to get pod local storage") raise PodInvalidResources( "Pod does not have a storage pool defined. " "Please add a storage pool.") return sum(pools.values())
def get_pod_arch(self): """Gets architecture of the pod.""" output = self.run(['nodeinfo']).strip() arch = self.get_key_value(output, "CPU model") if arch is None: maaslog.error("Failed to get pod architecture") raise PodInvalidResources("Pod architecture is not supported: %s" % arch) return ARCH_FIX.get(arch, arch)
def get_best_network(self): """Return the best possible network.""" networks = self.get_network_list() if 'maas' in networks: return 'maas' elif 'default' in networks: return 'default' elif not networks: raise PodInvalidResources( "Pod does not have a network defined. " "Please add a 'default' or 'maas' network.") return networks[0]
def convert(result): """Convert the result to send over RPC.""" if result is None: # None is allowed when a machine could not be composed with the # driver. This means it could not match the request. Returning None # allows the region to try another pod if available to compose # that machine. raise PodInvalidResources() else: if (isinstance(result, tuple) and len(result) == 2 and isinstance(result[0], DiscoveredMachine) and isinstance(result[1], DiscoveredPodHints)): return {"machine": result[0], "hints": result[1]} else: raise PodActionFail("bad pod driver '%s'; 'compose' returned " "invalid result." % pod_type)
def create_domain(self, request, default_pool=None): """Create a domain based on the `request` with hostname. For now this just uses `get_best_network` to connect the interfaces of the domain to the network. """ # Create all the block devices first. If cannot complete successfully # then fail early. The driver currently doesn't do any tag matching # for block devices, and is not really required for Virsh. created_disks = [] for idx, disk in enumerate(request.block_devices): try: disk_info = self.create_local_volume(disk, default_pool) except Exception: self.cleanup_disks(created_disks) raise else: if disk_info is None: raise PodInvalidResources("not enough space for disk %d." % idx) else: created_disks.append(disk_info) # Construct the domain XML. domain_params = self.get_domain_capabilities() domain_params['name'] = request.hostname domain_params['uuid'] = str(uuid4()) domain_params['arch'] = ARCH_FIX_REVERSE[request.architecture] domain_params['cores'] = str(request.cores) domain_params['memory'] = str(request.memory) # Set the template. if domain_params['arch'] == 'aarch64': # LP: #1775728 - Changes in the template are required due to # libvirt validation issues on the XML template. However, this # causes lint issues. We work around these issue by using # template variables instead. domain_params['loader'] = '/usr/share/AAVMF/AAVMF_CODE.fd' domain_params['nvram_path'] = '/var/lib/libvirt/qemu/nvram' domain_xml = DOM_TEMPLATE_ARM64.format(**domain_params) elif domain_params['arch'] in ('ppc64', 'ppc64le'): domain_xml = DOM_TEMPLATE_PPC64.format(**domain_params) elif domain_params['arch'] == 's390x': domain_xml = DOM_TEMPLATE_S390X.format(**domain_params) else: domain_xml = DOM_TEMPLATE_AMD64.format(**domain_params) # Define the domain in virsh. with NamedTemporaryFile() as f: f.write(domain_xml.encode('utf-8')) f.write(b'\n') f.flush() self.run(['define', f.name]) # Attach the created disks in order. for idx, (pool, volume) in enumerate(created_disks): block_name = self.get_block_name_from_idx(idx) self.attach_local_volume(request.hostname, pool, volume, block_name) # Attach new interfaces to the best possible network. best_network = self.get_best_network() for interface in request.interfaces: self.attach_interface(interface, request.hostname, best_network) # Set machine to autostart. self.set_machine_autostart(request.hostname) # Setup the domain to PXE boot. self.configure_pxe_boot(request.hostname) # Return the result as a discovered machine. return self.get_discovered_machine(request.hostname, request=request)