def test_validate_hwe_kern_fails_with_old_release_and_newer_hwe_kern(self): exception_raised = False try: validate_hwe_kernel( None, 'hwe-v', 'amd64/generic', 'ubuntu', 'trusty') except ValidationError as e: self.assertEqual( 'trusty has no kernels available which meet' + ' min_hwe_kernel(hwe-v).', e.message) exception_raised = True self.assertEqual(True, exception_raised)
def test_validate_hwe_kernel_fails_with_nongeneric_arch_and_kernel(self): exception_raised = False try: validate_hwe_kernel( 'hwe-v', None, 'armfh/hardbank', 'ubuntu', 'trusty') except ValidationError as e: self.assertEqual( 'Subarchitecture(hardbank) must be generic when setting ' + 'hwe_kernel.', e.message) exception_raised = True self.assertEqual(True, exception_raised)
def test_validate_hwe_kernel_fails_with_old_kernel_and_newer_release(self): exception_raised = False self.patch(BootResource.objects, "get_usable_hwe_kernels").return_value = ("hwe-t", "hwe-v") try: validate_hwe_kernel("hwe-t", None, "amd64/generic", "ubuntu", "vivid") except ValidationError as e: self.assertEqual("hwe-t is too old to use on ubuntu/vivid.", e.message) exception_raised = True self.assertEqual(True, exception_raised)
def test_validate_hwe_kernel_fails_with_old_kernel_and_newer_release(self): exception_raised = False self.patch(BootResource.objects, 'get_usable_hwe_kernels').return_value = ('hwe-t', 'hwe-v') try: validate_hwe_kernel('hwe-t', None, 'amd64/generic', 'ubuntu', 'vivid') except ValidationError as e: self.assertEqual('hwe-t is too old to use on ubuntu/vivid.', e.message) exception_raised = True self.assertEqual(True, exception_raised)
def test_validate_hwe_kernel_fails_with_no_avalible_kernels(self): exception_raised = False self.patch(BootResource.objects, 'get_usable_hwe_kernels').return_value = ('hwe-t', 'hwe-v') try: validate_hwe_kernel('hwe-t', 'hwe-v', 'amd64/generic', 'ubuntu', 'precise') except ValidationError as e: self.assertEqual( 'hwe_kernel(hwe-t) is older than min_hwe_kernel(hwe-v).', e.message) exception_raised = True self.assertEqual(True, exception_raised)
def test_validate_hwe_kernel_fails_with_nongeneric_arch_and_kernel(self): exception_raised = False try: validate_hwe_kernel("hwe-v", None, "armfh/hardbank", "ubuntu", "trusty") except ValidationError as e: self.assertEqual( "Subarchitecture(hardbank) must be generic when setting " + "hwe_kernel.", e.message, ) exception_raised = True self.assertEqual(True, exception_raised)
def test_validate_hwe_kernel_fails_with_missing_hwe_kernel(self): exception_raised = False self.patch(BootResource.objects, 'get_usable_hwe_kernels').return_value = ('hwe-t', 'hwe-u') try: validate_hwe_kernel('hwe-v', None, 'amd64/generic', 'ubuntu', 'trusty') except ValidationError as e: self.assertEqual( 'hwe-v is not available for ubuntu/trusty on amd64/generic.', e.message) exception_raised = True self.assertEqual(True, exception_raised)
def test_validate_hwe_kern_fails_with_old_release_and_newer_hwe_kern(self): exception_raised = False try: validate_hwe_kernel(None, "hwe-v", "amd64/generic", "ubuntu", "trusty") except ValidationError as e: self.assertEqual( "trusty has no kernels available which meet" + " min_hwe_kernel(hwe-v).", e.message, ) exception_raised = True self.assertEqual(True, exception_raised)
def test_validate_hwe_kernel_fails_with_no_avalible_kernels(self): exception_raised = False self.patch(BootResource.objects, "get_usable_hwe_kernels").return_value = ("hwe-t", "hwe-v") try: validate_hwe_kernel("hwe-t", "hwe-v", "amd64/generic", "ubuntu", "precise") except ValidationError as e: self.assertEqual( "hwe_kernel(hwe-t) is older than min_hwe_kernel(hwe-v).", e.message, ) exception_raised = True self.assertEqual(True, exception_raised)
def test_validate_hwe_kernel_fails_with_missing_hwe_kernel(self): exception_raised = False self.patch(BootResource.objects, "get_usable_hwe_kernels").return_value = ("hwe-t", "hwe-u") try: validate_hwe_kernel("hwe-v", None, "amd64/generic", "ubuntu", "trusty") except ValidationError as e: self.assertEqual( "hwe-v is not available for ubuntu/trusty on amd64/generic.", e.message, ) exception_raised = True self.assertEqual(True, exception_raised)
def execute(self, osystem=None, distro_series=None, hwe_kernel=None): """See `NodeAction.execute`.""" if self.node.owner is None: with locks.node_acquire: self.node.acquire(self.user, token=None) if osystem and distro_series: try: self.node.osystem, self.node.distro_series = ( validate_osystem_and_distro_series(osystem, distro_series)) self.node.save() except ValidationError as e: raise NodeActionError(e) try: self.node.hwe_kernel = validate_hwe_kernel( hwe_kernel, self.node.min_hwe_kernel, self.node.architecture, self.node.osystem, self.node.distro_series) self.node.save() except ValidationError as e: raise NodeActionError(e) try: get_curtin_config(self.node) except Exception as e: raise NodeActionError("Failed to retrieve curtin config: %s" % e) try: self.node.start(self.user) except StaticIPAddressExhaustion: raise NodeActionError( "%s: Failed to start, static IP addresses are exhausted." % self.node.hostname) except RPC_EXCEPTIONS + (ExternalProcessError, ) as exception: raise NodeActionError(exception)
def test_validate_hwe_kernel_set_kernel(self): self.patch( BootResource.objects, 'get_usable_hwe_kernels').return_value = ('hwe-t', 'hwe-v') hwe_kernel = validate_hwe_kernel( 'hwe-v', None, 'amd64/generic', 'ubuntu', 'trusty') self.assertEqual(hwe_kernel, 'hwe-v')
def test_validate_hwe_kernel_accepts_ga_kernel(self): self.patch( BootResource.objects, 'get_usable_hwe_kernels').return_value = ('ga-16.04',) hwe_kernel = validate_hwe_kernel( 'ga-16.04', None, 'amd64/generic', 'ubuntu', 'xenial') self.assertEqual(hwe_kernel, 'ga-16.04')
def test_validate_hwe_kernel_accepts_ga_kernel(self): self.patch( BootResource.objects, "get_usable_hwe_kernels" ).return_value = ("ga-16.04",) hwe_kernel = validate_hwe_kernel( "ga-16.04", None, "amd64/generic", "ubuntu", "xenial" ) self.assertEqual(hwe_kernel, "ga-16.04")
def test_validate_hwe_kernel_set_kernel(self): self.patch( BootResource.objects, "get_usable_hwe_kernels" ).return_value = ("hwe-t", "hwe-v") hwe_kernel = validate_hwe_kernel( "hwe-v", None, "amd64/generic", "ubuntu", "trusty" ) self.assertEqual(hwe_kernel, "hwe-v")
def test_validate_hwe_kern_always_sets_kern_with_commissionable_os(self): self.patch(BootResource.objects, 'get_usable_hwe_kernels').return_value = ('hwe-t', 'hwe-v') mock_get_config = self.patch(Config.objects, "get_config") mock_get_config.return_value = 'trusty' kernel = validate_hwe_kernel(None, 'hwe-v', '%s/generic' % factory.make_name('arch'), factory.make_name("osystem"), factory.make_name("distro")) self.assertThat(mock_get_config, MockAnyCall('commissioning_osystem')) self.assertThat(mock_get_config, MockAnyCall('commissioning_distro_series')) self.assertEqual('hwe-v', kernel)
def get_boot_filenames( arch, subarch, osystem, series, commissioning_osystem=undefined, commissioning_distro_series=undefined, ): """Return the filenames of the kernel, initrd, and boot_dtb for the boot resource.""" if subarch == "generic": # MAAS doesn't store in the BootResource table what subarch is the # generic subarch so lookup what the generic subarch maps to. try: boot_resource_subarch = validate_hwe_kernel( subarch, None, "%s/%s" % (arch, subarch), osystem, series, commissioning_osystem=commissioning_osystem, commissioning_distro_series=commissioning_distro_series, ) except ValidationError: # It's possible that no kernel's exist at all for this arch, # subarch, osystem, series combination. In that case just fallback # to 'generic'. boot_resource_subarch = "generic" else: boot_resource_subarch = subarch try: # Get the filename for the kernel, initrd, and boot_dtb the rack should # use when booting. boot_resource = BootResource.objects.get( architecture="%s/%s" % (arch, boot_resource_subarch), name="%s/%s" % (osystem, series), ) boot_resource_set = boot_resource.get_latest_complete_set() boot_resource_files = { bfile.filetype: bfile.filename for bfile in boot_resource_set.files.all() } except ObjectDoesNotExist: # If a filename can not be found return None to allow the rack to # figure out what todo. return None, None, None kernel = boot_resource_files.get(BOOT_RESOURCE_FILE_TYPE.BOOT_KERNEL) initrd = boot_resource_files.get(BOOT_RESOURCE_FILE_TYPE.BOOT_INITRD) boot_dtb = boot_resource_files.get(BOOT_RESOURCE_FILE_TYPE.BOOT_DTB) return kernel, initrd, boot_dtb
def test_validate_hwe_kern_sets_hwe_kern_to_min_hwe_kern_for_edge(self): # Regression test for LP:1654412 mock_get_usable_hwe_kernels = self.patch(BootResource.objects, 'get_usable_hwe_kernels') mock_get_usable_hwe_kernels.return_value = ('hwe-16.04', 'hwe-16.04-edge') arch = factory.make_name('arch') kernel = validate_hwe_kernel(None, 'hwe-16.04-edge', '%s/generic' % arch, 'ubuntu', 'xenial') self.assertEquals('hwe-16.04-edge', kernel) self.assertThat(mock_get_usable_hwe_kernels, MockCalledOnceWith('ubuntu/xenial', arch, 'generic'))
def test_validate_hwe_kern_always_sets_kern_with_commissionable_os(self): self.patch(BootResource.objects, "get_usable_hwe_kernels").return_value = ("hwe-t", "hwe-v") mock_get_config = self.patch(Config.objects, "get_config") mock_get_config.return_value = "trusty" kernel = validate_hwe_kernel( None, "hwe-v", "%s/generic" % factory.make_name("arch"), factory.make_name("osystem"), factory.make_name("distro"), ) self.assertThat(mock_get_config, MockAnyCall("commissioning_osystem")) self.assertThat(mock_get_config, MockAnyCall("commissioning_distro_series")) self.assertEqual("hwe-v", kernel)
def execute(self, osystem=None, distro_series=None, hwe_kernel=None): """See `NodeAction.execute`.""" if self.node.owner is None: with locks.node_acquire: try: self.node.acquire(self.user, token=None) except ValidationError as e: raise NodeActionError(e) if osystem and distro_series: try: self.node.osystem, self.node.distro_series = ( validate_osystem_and_distro_series(osystem, distro_series)) self.node.save() except ValidationError as e: raise NodeActionError(e) try: self.node.hwe_kernel = validate_hwe_kernel( hwe_kernel, self.node.min_hwe_kernel, self.node.architecture, self.node.osystem, self.node.distro_series) self.node.save() except ValidationError as e: raise NodeActionError(e) request = self.request if request is None: # Being called from the websocket, just to ensure that the curtin # configuration is valid. The request object does not need to be # an actual request. 'SERVER_NAME' and 'SERVER_PORT' are required # so `build_absolure_uri` can create an actual absolute URI. request = HttpRequest() request.META['SERVER_NAME'] = 'localhost' request.META['SERVER_PORT'] = 5248 try: get_curtin_config(request, self.node) except Exception as e: raise NodeActionError("Failed to retrieve curtin config: %s" % e) try: self.node.start(self.user) except StaticIPAddressExhaustion: raise NodeActionError( "%s: Failed to start, static IP addresses are exhausted." % self.node.hostname) except RPC_EXCEPTIONS + (ExternalProcessError, ) as exception: raise NodeActionError(exception)
def get_boot_filenames(arch, subarch, osystem, series): """Return the filenames of the kernel, initrd, and boot_dtb for the boot resource.""" if subarch == 'generic': # MAAS doesn't store in the BootResource table what subarch is the # generic subarch so lookup what the generic subarch maps to. try: boot_resource_subarch = validate_hwe_kernel( subarch, None, "%s/%s" % (arch, subarch), osystem, series) except ValidationError: # It's possible that no kernel's exist at all for this arch, # subarch, osystem, series combination. In that case just fallback # to 'generic'. boot_resource_subarch = 'generic' else: boot_resource_subarch = subarch try: # Get the filename for the kernel, initrd, and boot_dtb the rack should # use when booting. boot_resource = BootResource.objects.get( architecture="%s/%s" % (arch, boot_resource_subarch), name="%s/%s" % (osystem, series) ) boot_resource_set = boot_resource.get_latest_complete_set() kernel = boot_resource_set.files.get( filetype=BOOT_RESOURCE_FILE_TYPE.BOOT_KERNEL).filename except ObjectDoesNotExist: # If a filename can not be found return None to allow the rack to # figure out what todo. return None, None, None try: initrd = boot_resource_set.files.get( filetype=BOOT_RESOURCE_FILE_TYPE.BOOT_INITRD).filename except ObjectDoesNotExist: # An initrd is not needed to boot if the kernel contains all driver # support. initrd = None try: boot_dtb = boot_resource_set.files.get( filetype=BOOT_RESOURCE_FILE_TYPE.BOOT_DTB).filename except ObjectDoesNotExist: # Not all archs use boot_dtb so allow just this to fail boot_dtb = None return kernel, initrd, boot_dtb
def test_validate_hwe_kern_sets_hwe_kern_to_min_hwe_kern_for_edge(self): # Regression test for LP:1654412 mock_get_usable_hwe_kernels = self.patch(BootResource.objects, "get_usable_hwe_kernels") mock_get_usable_hwe_kernels.return_value = ( "hwe-16.04", "hwe-16.04-edge", ) arch = factory.make_name("arch") kernel = validate_hwe_kernel(None, "hwe-16.04-edge", "%s/generic" % arch, "ubuntu", "xenial") self.assertEquals("hwe-16.04-edge", kernel) self.assertThat( mock_get_usable_hwe_kernels, MockCalledOnceWith("ubuntu/xenial", arch, "generic"), )
def _execute(self, osystem=None, distro_series=None, hwe_kernel=None, install_kvm=False): """See `NodeAction.execute`.""" if install_kvm: if not self.user.is_superuser: raise NodeActionError( "You must be a MAAS administrator to deploy a machine " "as a MAAS-managed KVM Pod.") if self.node.owner is None: with locks.node_acquire: try: bridge_all = True if install_kvm else False self.node.acquire(self.user, token=None, bridge_all=bridge_all) except ValidationError as e: raise NodeActionError(e) if install_kvm: try: # KVM Pod installation should default to ubuntu/bionic, since # that was the release it was tested on. if osystem is None: osystem = 'ubuntu' if distro_series is None: distro_series = 'bionic' self.node.osystem, self.node.distro_series = ( validate_osystem_and_distro_series(osystem, distro_series)) self.node.install_kvm = True self.node.save() except ValidationError as e: raise NodeActionError(e) elif osystem and distro_series: try: self.node.osystem, self.node.distro_series = ( validate_osystem_and_distro_series(osystem, distro_series)) self.node.save() except ValidationError as e: raise NodeActionError(e) try: self.node.hwe_kernel = validate_hwe_kernel( hwe_kernel, self.node.min_hwe_kernel, self.node.architecture, self.node.osystem, self.node.distro_series) self.node.save() except ValidationError as e: raise NodeActionError(e) request = self.request if request is None: # `compile_node_actions` is the path by which the node # actions are instantiated. There are other places within the # code that call compile_node_actions without a request object. # In this event, and for future uses of these node actions without # a request being passed in, we need to create one here. # 'SERVER_NAME' and 'SERVER_PORT' are required so # `build_absolure_uri` can create an actual absolute URI so that # the curtin configuration is valid. request = HttpRequest() request.META['SERVER_NAME'] = 'localhost' request.META['SERVER_PORT'] = 5248 try: get_curtin_config(request, self.node) except Exception as e: raise NodeActionError("Failed to retrieve curtin config: %s" % e) try: self.node.start(self.user) except StaticIPAddressExhaustion: raise NodeActionError( "%s: Failed to start, static IP addresses are exhausted." % self.node.hostname) except RPC_EXCEPTIONS + (ExternalProcessError, ) as exception: raise NodeActionError(exception)
def get_config( system_id, local_ip, remote_ip, arch=None, subarch=None, mac=None, bios_boot_method=None): """Get the booting configration for the a machine. Returns a structure suitable for returning in the response for :py:class:`~provisioningserver.rpc.region.GetBootConfig`. Raises BootConfigNoResponse when booting machine should fail to next file. """ rack_controller = RackController.objects.get(system_id=system_id) region_ip = None if remote_ip is not None: region_ip = get_source_address(remote_ip) machine = get_node_from_mac_string(mac) # Fail with no response early so no extra work is performed. if machine is None and arch is None and mac is not None: # Request was pxelinux.cfg/01-<mac> for a machine MAAS does not know # about. So attempt fall back to pxelinux.cfg/default-<arch>-<subarch> # for arch detection. raise BootConfigNoResponse() if machine is not None: # Update the last interface, last access cluster IP address, and # the last used BIOS boot method. Only saving the fields that have # changed on the machine. update_fields = [] if (machine.boot_interface is None or machine.boot_interface.mac_address != mac): machine.boot_interface = PhysicalInterface.objects.get( mac_address=mac) update_fields.append("boot_interface") if (machine.boot_cluster_ip is None or machine.boot_cluster_ip != local_ip): machine.boot_cluster_ip = local_ip update_fields.append("boot_cluster_ip") if machine.bios_boot_method != bios_boot_method: machine.bios_boot_method = bios_boot_method update_fields.append("bios_boot_method") if len(update_fields) > 0: machine.save(update_fields=update_fields) # Update the VLAN of the boot interface to be the same VLAN for the # interface on the rack controller that the machine communicated with, # unless the VLAN is being relayed. rack_interface = rack_controller.interface_set.filter( ip_addresses__ip=local_ip).first() if (rack_interface is not None and machine.boot_interface.vlan != rack_interface.vlan): # Rack controller and machine is not on the same VLAN, with DHCP # relay this is possible. Lets ensure that the VLAN on the # interface is setup to relay through the identified VLAN. if not VLAN.objects.filter( id=machine.boot_interface.vlan_id, relay_vlan=rack_interface.vlan).exists(): # DHCP relay is not being performed for that VLAN. Set the VLAN # to the VLAN of the rack controller. machine.boot_interface.vlan = rack_interface.vlan machine.boot_interface.save() arch, subarch = machine.split_arch() preseed_url = compose_preseed_url( machine, rack_controller, default_region_ip=region_ip) hostname = machine.hostname domain = machine.domain.name purpose = machine.get_boot_purpose() # Log the request into the event log for that machine. if (machine.status == NODE_STATUS.ENTERING_RESCUE_MODE and purpose == 'commissioning'): event_log_pxe_request(machine, 'rescue') else: event_log_pxe_request(machine, purpose) # Get the correct operating system and series based on the purpose # of the booting machine. if purpose == "commissioning": osystem = Config.objects.get_config('commissioning_osystem') series = Config.objects.get_config('commissioning_distro_series') else: osystem = machine.get_osystem() series = machine.get_distro_series() if purpose == "xinstall" and osystem != "ubuntu": # Use only the commissioning osystem and series, for operating # systems other than Ubuntu. As Ubuntu supports HWE kernels, # and needs to use that kernel to perform the installation. osystem = Config.objects.get_config('commissioning_osystem') series = Config.objects.get_config( 'commissioning_distro_series') # Pre MAAS-1.9 the subarchitecture defined any kernel the machine # needed to be able to boot. This could be a hardware enablement # kernel(e.g hwe-t) or something like highbank. With MAAS-1.9 any # hardware enablement kernel must be specifed in the hwe_kernel field, # any other kernel, such as highbank, is still specifed as a # subarchitecture. Since Ubuntu does not support architecture specific # hardware enablement kernels(i.e a highbank hwe-t kernel on precise) # we give precedence to any kernel defined in the subarchitecture field if subarch == "generic" and machine.hwe_kernel: subarch = machine.hwe_kernel elif(subarch == "generic" and purpose == "commissioning" and machine.min_hwe_kernel): try: subarch = validate_hwe_kernel( None, machine.min_hwe_kernel, machine.architecture, osystem, series) except ValidationError: subarch = "no-such-kernel" # We don't care if the kernel opts is from the global setting or a tag, # just get the options _, effective_kernel_opts = machine.get_effective_kernel_options() # Add any extra options from a third party driver. use_driver = Config.objects.get_config('enable_third_party_drivers') if use_driver: driver = get_third_party_driver(machine) driver_kernel_opts = driver.get('kernel_opts', '') combined_opts = ('%s %s' % ( '' if effective_kernel_opts is None else effective_kernel_opts, driver_kernel_opts)).strip() if len(combined_opts): extra_kernel_opts = combined_opts else: extra_kernel_opts = None else: extra_kernel_opts = effective_kernel_opts kparams = BootResource.objects.get_kparams_for_node(machine) extra_kernel_opts = merge_kparams_with_extra(kparams, extra_kernel_opts) else: purpose = "commissioning" # enlistment preseed_url = compose_enlistment_preseed_url( rack_controller, default_region_ip=region_ip) hostname = 'maas-enlist' domain = 'local' osystem = Config.objects.get_config('commissioning_osystem') series = Config.objects.get_config('commissioning_distro_series') min_hwe_kernel = Config.objects.get_config('default_min_hwe_kernel') # When no architecture is defined for the enlisting machine select # the best boot resource for the operating system and series. If # none exists fallback to the default architecture. LP #1181334 if arch is None: resource = ( BootResource.objects.get_default_commissioning_resource( osystem, series)) if resource is None: arch = DEFAULT_ARCH else: arch, _ = resource.split_arch() # The subarch defines what kernel is booted. With MAAS 2.1 this changed # from hwe-<letter> to hwe-<version> or ga-<version>. Validation # converts between the two formats to make sure a bootable subarch is # selected. if subarch is None: min_hwe_kernel = validate_hwe_kernel( None, min_hwe_kernel, '%s/generic' % arch, osystem, series) else: min_hwe_kernel = validate_hwe_kernel( None, min_hwe_kernel, '%s/%s' % (arch, subarch), osystem, series) # If no hwe_kernel was found set the subarch to the default, 'generic.' if min_hwe_kernel is None: subarch = 'generic' else: subarch = min_hwe_kernel # Global kernel options for enlistment. extra_kernel_opts = Config.objects.get_config("kernel_opts") # Set the final boot purpose. if machine is None and arch == DEFAULT_ARCH: # If the machine is enlisting and the arch is the default arch (i386), # use the dedicated enlistment template which performs architecture # detection. boot_purpose = "enlist" elif purpose == 'poweroff': # In order to power the machine off, we need to get it booted in the # commissioning environment and issue a `poweroff` command. boot_purpose = 'commissioning' else: boot_purpose = purpose # Get the service address to the region for that given rack controller. server_host = get_maas_facing_server_host( rack_controller=rack_controller, default_region_ip=region_ip) kernel, initrd, boot_dtb = get_boot_filenames( arch, subarch, osystem, series) # Return the params to the rack controller. Include the system_id only # if the machine was known. params = { "arch": arch, "subarch": subarch, "osystem": osystem, "release": series, "kernel": kernel, "initrd": initrd, "boot_dtb": boot_dtb, "purpose": boot_purpose, "hostname": hostname, "domain": domain, "preseed_url": preseed_url, "fs_host": local_ip, "log_host": server_host, "extra_opts": '' if extra_kernel_opts is None else extra_kernel_opts, # As of MAAS 2.4 only HTTP boot is supported. This ensures MAAS 2.3 # rack controllers use HTTP boot as well. "http_boot": True, } if machine is not None: params["system_id"] = machine.system_id return params
def _execute( self, osystem=None, distro_series=None, hwe_kernel=None, install_kvm=False, user_data=None, ): """See `NodeAction.execute`.""" if install_kvm: if not self.user.is_superuser: raise NodeActionError( "You must be a MAAS administrator to deploy a machine " "as a MAAS-managed KVM Pod.") if self.node.owner is None: with locks.node_acquire: try: self.node.acquire(self.user) except ValidationError as e: raise NodeActionError(e) if osystem and distro_series: try: ( self.node.osystem, self.node.distro_series, ) = validate_osystem_and_distro_series(osystem, distro_series) self.node.save() except ValidationError as e: raise NodeActionError(e) else: configs = Config.objects.get_configs( ["default_osystem", "default_distro_series"]) self.node.osystem = configs["default_osystem"] self.node.distro_series = configs["default_distro_series"] self.node.save() try: self.node.hwe_kernel = validate_hwe_kernel( hwe_kernel, self.node.min_hwe_kernel, self.node.architecture, self.node.osystem, self.node.distro_series, ) self.node.save() except ValidationError as e: raise NodeActionError(e) user_data = user_data.encode() if user_data else None request = self.request if request is None: # `compile_node_actions` is the path by which the node # actions are instantiated. There are other places within the # code that call compile_node_actions without a request object. # In this event, and for future uses of these node actions without # a request being passed in, we need to create one here. # 'SERVER_NAME' and 'SERVER_PORT' are required so # `build_absolure_uri` can create an actual absolute URI so that # the curtin configuration is valid. request = HttpRequest() request.META["SERVER_NAME"] = "localhost" request.META["SERVER_PORT"] = 5248 try: get_curtin_config(request, self.node) except Exception as e: raise NodeActionError("Failed to retrieve curtin config: %s" % e) try: self.node.start(self.user, user_data=user_data, install_kvm=install_kvm) except StaticIPAddressExhaustion: raise NodeActionError( "%s: Failed to start, static IP addresses are exhausted." % self.node.hostname) except IPAddressCheckFailed: raise NodeActionError( f"{self.node.hostname}: Failed to start, IP addresses check failed." ) except RPC_EXCEPTIONS + (ExternalProcessError, ) as exception: raise NodeActionError(exception)
def get_config( system_id, local_ip, remote_ip, arch=None, subarch=None, mac=None, bios_boot_method=None): """Get the booting configration for the a machine. Returns a structure suitable for returning in the response for :py:class:`~provisioningserver.rpc.region.GetBootConfig`. Raises BootConfigNoResponse when booting machine should fail to next file. """ rack_controller = RackController.objects.get(system_id=system_id) region_ip = None if remote_ip is not None: region_ip = get_source_address(remote_ip) machine = get_node_from_mac_string(mac) # Get the service address to the region for that given rack controller. server_host = get_maas_facing_server_host( rack_controller=rack_controller, default_region_ip=region_ip) # Fail with no response early so no extra work is performed. if machine is None and arch is None and mac is not None: # Request was pxelinux.cfg/01-<mac> for a machine MAAS does not know # about. So attempt fall back to pxelinux.cfg/default-<arch>-<subarch> # for arch detection. raise BootConfigNoResponse() configs = Config.objects.get_configs([ 'commissioning_osystem', 'commissioning_distro_series', 'enable_third_party_drivers', 'default_min_hwe_kernel', 'default_osystem', 'default_distro_series', 'kernel_opts', 'use_rack_proxy', 'maas_internal_domain', ]) if machine is not None: # Update the last interface, last access cluster IP address, and # the last used BIOS boot method. if (machine.boot_interface is None or machine.boot_interface.mac_address != mac): machine.boot_interface = PhysicalInterface.objects.get( mac_address=mac) if (machine.boot_cluster_ip is None or machine.boot_cluster_ip != local_ip): machine.boot_cluster_ip = local_ip if machine.bios_boot_method != bios_boot_method: machine.bios_boot_method = bios_boot_method # Reset the machine's status_expires whenever the boot_config is called # on a known machine. This allows a machine to take up to the maximum # timeout status to POST. machine.reset_status_expires() # Does nothing if the machine hasn't changed. machine.save() # Update the VLAN of the boot interface to be the same VLAN for the # interface on the rack controller that the machine communicated with, # unless the VLAN is being relayed. rack_interface = rack_controller.interface_set.filter( ip_addresses__ip=local_ip).select_related('vlan').first() if (rack_interface is not None and machine.boot_interface.vlan_id != rack_interface.vlan_id): # Rack controller and machine is not on the same VLAN, with DHCP # relay this is possible. Lets ensure that the VLAN on the # interface is setup to relay through the identified VLAN. if not VLAN.objects.filter( id=machine.boot_interface.vlan_id, relay_vlan=rack_interface.vlan_id).exists(): # DHCP relay is not being performed for that VLAN. Set the VLAN # to the VLAN of the rack controller. machine.boot_interface.vlan = rack_interface.vlan machine.boot_interface.save() arch, subarch = machine.split_arch() if configs['use_rack_proxy']: preseed_url = compose_preseed_url( machine, base_url=get_base_url_for_local_ip( local_ip, configs['maas_internal_domain'])) else: preseed_url = compose_preseed_url( machine, base_url=rack_controller.url, default_region_ip=region_ip) hostname = machine.hostname domain = machine.domain.name purpose = machine.get_boot_purpose() # Early out if the machine is booting local. if purpose == 'local': return { "system_id": machine.system_id, "arch": arch, "subarch": subarch, "osystem": machine.osystem, "release": machine.distro_series, "kernel": '', "initrd": '', "boot_dtb": '', "purpose": purpose, "hostname": hostname, "domain": domain, "preseed_url": preseed_url, "fs_host": local_ip, "log_host": server_host, "extra_opts": '', "http_boot": True, } # Log the request into the event log for that machine. if (machine.status in [ NODE_STATUS.ENTERING_RESCUE_MODE, NODE_STATUS.RESCUE_MODE] and purpose == 'commissioning'): event_log_pxe_request(machine, 'rescue') else: event_log_pxe_request(machine, purpose) osystem, series, subarch = get_boot_config_for_machine( machine, configs, purpose) # We don't care if the kernel opts is from the global setting or a tag, # just get the options _, effective_kernel_opts = machine.get_effective_kernel_options( default_kernel_opts=configs['kernel_opts']) # Add any extra options from a third party driver. use_driver = configs['enable_third_party_drivers'] if use_driver: driver = get_third_party_driver(machine) driver_kernel_opts = driver.get('kernel_opts', '') combined_opts = ('%s %s' % ( '' if effective_kernel_opts is None else effective_kernel_opts, driver_kernel_opts)).strip() if len(combined_opts): extra_kernel_opts = combined_opts else: extra_kernel_opts = None else: extra_kernel_opts = effective_kernel_opts kparams = BootResource.objects.get_kparams_for_node( machine, default_osystem=configs['default_osystem'], default_distro_series=configs['default_distro_series']) extra_kernel_opts = merge_kparams_with_extra(kparams, extra_kernel_opts) else: purpose = "commissioning" # enlistment if configs['use_rack_proxy']: preseed_url = compose_enlistment_preseed_url( base_url=get_base_url_for_local_ip( local_ip, configs['maas_internal_domain'])) else: preseed_url = compose_enlistment_preseed_url( rack_controller=rack_controller, default_region_ip=region_ip) hostname = 'maas-enlist' domain = 'local' osystem = configs['commissioning_osystem'] series = configs['commissioning_distro_series'] min_hwe_kernel = configs['default_min_hwe_kernel'] # When no architecture is defined for the enlisting machine select # the best boot resource for the operating system and series. If # none exists fallback to the default architecture. LP #1181334 if arch is None: resource = ( BootResource.objects.get_default_commissioning_resource( osystem, series)) if resource is None: arch = DEFAULT_ARCH else: arch, _ = resource.split_arch() # The subarch defines what kernel is booted. With MAAS 2.1 this changed # from hwe-<letter> to hwe-<version> or ga-<version>. Validation # converts between the two formats to make sure a bootable subarch is # selected. if subarch is None: min_hwe_kernel = validate_hwe_kernel( None, min_hwe_kernel, '%s/generic' % arch, osystem, series) else: min_hwe_kernel = validate_hwe_kernel( None, min_hwe_kernel, '%s/%s' % (arch, subarch), osystem, series) # If no hwe_kernel was found set the subarch to the default, 'generic.' if min_hwe_kernel is None: subarch = 'generic' else: subarch = min_hwe_kernel # Global kernel options for enlistment. extra_kernel_opts = configs["kernel_opts"] # Set the final boot purpose. if machine is None and arch == DEFAULT_ARCH: # If the machine is enlisting and the arch is the default arch (i386), # use the dedicated enlistment template which performs architecture # detection. boot_purpose = "enlist" elif purpose == 'poweroff': # In order to power the machine off, we need to get it booted in the # commissioning environment and issue a `poweroff` command. boot_purpose = 'commissioning' else: boot_purpose = purpose kernel, initrd, boot_dtb = get_boot_filenames( arch, subarch, osystem, series, commissioning_osystem=configs['commissioning_osystem'], commissioning_distro_series=configs['commissioning_distro_series']) # Return the params to the rack controller. Include the system_id only # if the machine was known. params = { "arch": arch, "subarch": subarch, "osystem": osystem, "release": series, "kernel": kernel, "initrd": initrd, "boot_dtb": boot_dtb, "purpose": boot_purpose, "hostname": hostname, "domain": domain, "preseed_url": preseed_url, "fs_host": local_ip, "log_host": server_host, "extra_opts": '' if extra_kernel_opts is None else extra_kernel_opts, # As of MAAS 2.4 only HTTP boot is supported. This ensures MAAS 2.3 # rack controllers use HTTP boot as well. "http_boot": True, } if machine is not None: params["system_id"] = machine.system_id return params
def get_boot_config_for_machine(machine, configs, purpose): """Get the correct operating system and series based on the purpose of the booting machine. """ _, subarch = machine.split_arch() precise = False if purpose == "commissioning": # LP: #1768321 - Fix a regression introduced by, and really fix # the issue that LP: #1730525 was meant to fix. This ensures that # when DISK_ERASING, or when ENTERING_RESCUE_MODE on a deployed # machine it uses the OS from the deployed system for the # ephemeral environment. if machine.osystem == "ubuntu" and ( machine.status == NODE_STATUS.DISK_ERASING or ( machine.status in [ NODE_STATUS.ENTERING_RESCUE_MODE, NODE_STATUS.RESCUE_MODE, ] and machine.previous_status == NODE_STATUS.DEPLOYED)): osystem = machine.get_osystem(default=configs['default_osystem']) series = machine.get_distro_series( default=configs['default_distro_series']) else: osystem = configs['commissioning_osystem'] series = configs['commissioning_distro_series'] else: osystem = machine.get_osystem(default=configs['default_osystem']) series = machine.get_distro_series( default=configs['default_distro_series']) # XXX: roaksoax LP: #1739761 - Since the switch to squashfs (and # drop of iscsi), precise is no longer deployable. To address a # squashfs image is made available allowing it to be deployed in # the commissioning ephemeral environment. precise = True if series == "precise" else False if purpose == "xinstall" and (osystem != "ubuntu" or precise): # Use only the commissioning osystem and series, for operating # systems other than Ubuntu. As Ubuntu supports HWE kernels, # and needs to use that kernel to perform the installation. osystem = configs['commissioning_osystem'] series = configs['commissioning_distro_series'] # Pre MAAS-1.9 the subarchitecture defined any kernel the machine # needed to be able to boot. This could be a hardware enablement # kernel(e.g hwe-t) or something like highbank. With MAAS-1.9 any # hardware enablement kernel must be specifed in the hwe_kernel field, # any other kernel, such as highbank, is still specifed as a # subarchitecture. Since Ubuntu does not support architecture specific # hardware enablement kernels(i.e a highbank hwe-t kernel on precise) # we give precedence to any kernel defined in the subarchitecture field # If the machine is deployed, hardcode the use of the Minimum HWE Kernel # This is to ensure that machines can always do testing regardless of # what they were deployed with, using the defaults from the settings testing_from_deployed = ( machine.previous_status == NODE_STATUS.DEPLOYED and machine.status == NODE_STATUS.TESTING and purpose == "commissioning") if testing_from_deployed: subarch = (subarch if not configs['default_min_hwe_kernel'] else configs['default_min_hwe_kernel']) # XXX: roaksoax LP: #1739761 - Do not override the subarch (used for # the deployment ephemeral env) when deploying precise, provided that # it uses the commissioning distro_series and hwe kernels are not # needed. elif subarch == "generic" and machine.hwe_kernel and not precise: subarch = machine.hwe_kernel elif(subarch == "generic" and purpose == "commissioning" and machine.min_hwe_kernel): try: subarch = validate_hwe_kernel( None, machine.min_hwe_kernel, machine.architecture, osystem, series) except ValidationError: subarch = "no-such-kernel" return osystem, series, subarch
def get_config( system_id, local_ip, remote_ip, arch=None, subarch=None, mac=None, hardware_uuid=None, bios_boot_method=None, ): """Get the booting configration for a machine. Returns a structure suitable for returning in the response for :py:class:`~provisioningserver.rpc.region.GetBootConfig`. Raises BootConfigNoResponse when booting machine should fail to next file. """ rack_controller = RackController.objects.get(system_id=system_id) region_ip = None if remote_ip is not None: region_ip = get_source_address(remote_ip) machine = get_node_from_mac_or_hardware_uuid(mac, hardware_uuid) # Fail with no response early so no extra work is performed. if machine is None and arch is None and (mac or hardware_uuid): # PXELinux requests boot configuration in the following order: # 1. pxelinux.cfg/<hardware uuid> # 2. pxelinux.cfg/01-<mac> # 3. pxelinux.cfg/default-<arch>-<subarch> # If mac and/or hardware_uuid was given but no Node was found fail the # request so PXELinux will move onto the next request. raise BootConfigNoResponse() # Get all required configuration objects in a single query. configs = Config.objects.get_configs([ "commissioning_osystem", "commissioning_distro_series", "enable_third_party_drivers", "default_min_hwe_kernel", "default_osystem", "default_distro_series", "kernel_opts", "use_rack_proxy", "maas_internal_domain", "remote_syslog", "maas_syslog_port", ]) # Compute the syslog server. log_host, log_port = ( local_ip, (configs["maas_syslog_port"] if configs["maas_syslog_port"] else RSYSLOG_PORT), ) if configs["remote_syslog"]: log_host, log_port = splithost(configs["remote_syslog"]) if log_port is None: log_port = 514 # Fallback to default UDP syslog port. if machine is not None: # Update the last interface, last access cluster IP address, and # the last used BIOS boot method. if machine.boot_cluster_ip != local_ip: machine.boot_cluster_ip = local_ip if machine.bios_boot_method != bios_boot_method: machine.bios_boot_method = bios_boot_method try: machine.boot_interface = machine.interface_set.get( type=INTERFACE_TYPE.PHYSICAL, mac_address=mac) except ObjectDoesNotExist: # MAC is unknown or wasn't sent. Determine the boot_interface using # the boot_cluster_ip. subnet = Subnet.objects.get_best_subnet_for_ip(local_ip) boot_vlan = getattr(machine.boot_interface, "vlan", None) if subnet and subnet.vlan != boot_vlan: # This might choose the wrong interface, but we don't # have enough information to decide which interface is # the boot one. machine.boot_interface = machine.interface_set.filter( vlan=subnet.vlan).first() else: # Update the VLAN of the boot interface to be the same VLAN for the # interface on the rack controller that the machine communicated # with, unless the VLAN is being relayed. rack_interface = (rack_controller.interface_set.filter( ip_addresses__ip=local_ip).select_related("vlan").first()) if (rack_interface is not None and machine.boot_interface.vlan_id != rack_interface.vlan_id): # Rack controller and machine is not on the same VLAN, with # DHCP relay this is possible. Lets ensure that the VLAN on the # interface is setup to relay through the identified VLAN. if not VLAN.objects.filter( id=machine.boot_interface.vlan_id, relay_vlan=rack_interface.vlan_id, ).exists(): # DHCP relay is not being performed for that VLAN. Set the # VLAN to the VLAN of the rack controller. machine.boot_interface.vlan = rack_interface.vlan machine.boot_interface.save() # Reset the machine's status_expires whenever the boot_config is called # on a known machine. This allows a machine to take up to the maximum # timeout status to POST. machine.reset_status_expires() # Does nothing if the machine hasn't changed. machine.save() arch, subarch = machine.split_arch() if configs["use_rack_proxy"]: preseed_url = compose_preseed_url( machine, base_url=get_base_url_for_local_ip( local_ip, configs["maas_internal_domain"]), ) else: preseed_url = compose_preseed_url( machine, base_url=rack_controller.url, default_region_ip=region_ip, ) hostname = machine.hostname domain = machine.domain.name purpose = machine.get_boot_purpose() # Ephemeral deployments will have 'local' boot # purpose on power cycles. Set purpose back to # 'xinstall' so that the system can be re-deployed. if purpose == "local" and machine.ephemeral_deployment: purpose = "xinstall" # Early out if the machine is booting local. if purpose == "local": if machine.is_device: # Log that we are setting to local boot for a device. maaslog.warning( "Device %s with MAC address %s is PXE booting; " "instructing the device to boot locally." % (machine.hostname, mac)) # Set the purpose to 'local-device' so we can log a message # on the rack. purpose = "local-device" return { "system_id": machine.system_id, "arch": arch, "subarch": subarch, "osystem": machine.osystem, "release": machine.distro_series, "kernel": "", "initrd": "", "boot_dtb": "", "purpose": purpose, "hostname": hostname, "domain": domain, "preseed_url": preseed_url, "fs_host": local_ip, "log_host": log_host, "log_port": log_port, "extra_opts": "", "http_boot": True, } # Log the request into the event log for that machine. if (machine.status in [NODE_STATUS.ENTERING_RESCUE_MODE, NODE_STATUS.RESCUE_MODE] and purpose == "commissioning"): event_log_pxe_request(machine, "rescue") else: event_log_pxe_request(machine, purpose) osystem, series, subarch = get_boot_config_for_machine( machine, configs, purpose) # We don't care if the kernel opts is from the global setting or a tag, # just get the options _, effective_kernel_opts = machine.get_effective_kernel_options( default_kernel_opts=configs["kernel_opts"]) # Add any extra options from a third party driver. use_driver = configs["enable_third_party_drivers"] if use_driver: driver = get_third_party_driver(machine) driver_kernel_opts = driver.get("kernel_opts", "") combined_opts = ("%s %s" % ( "" if effective_kernel_opts is None else effective_kernel_opts, driver_kernel_opts, )).strip() if len(combined_opts): extra_kernel_opts = combined_opts else: extra_kernel_opts = None else: extra_kernel_opts = effective_kernel_opts kparams = BootResource.objects.get_kparams_for_node( machine, default_osystem=configs["default_osystem"], default_distro_series=configs["default_distro_series"], ) extra_kernel_opts = merge_kparams_with_extra(kparams, extra_kernel_opts) else: purpose = "commissioning" # enlistment if configs["use_rack_proxy"]: preseed_url = compose_enlistment_preseed_url( base_url=get_base_url_for_local_ip( local_ip, configs["maas_internal_domain"])) else: preseed_url = compose_enlistment_preseed_url( rack_controller=rack_controller, default_region_ip=region_ip) hostname = "maas-enlist" domain = "local" osystem = configs["commissioning_osystem"] series = configs["commissioning_distro_series"] min_hwe_kernel = configs["default_min_hwe_kernel"] # When no architecture is defined for the enlisting machine select # the best boot resource for the operating system and series. If # none exists fallback to the default architecture. LP #1181334 if arch is None: resource = BootResource.objects.get_default_commissioning_resource( osystem, series) if resource is None: arch = DEFAULT_ARCH else: arch, _ = resource.split_arch() # The subarch defines what kernel is booted. With MAAS 2.1 this changed # from hwe-<letter> to hwe-<version> or ga-<version>. Validation # converts between the two formats to make sure a bootable subarch is # selected. if subarch is None: min_hwe_kernel = validate_hwe_kernel(None, min_hwe_kernel, "%s/generic" % arch, osystem, series) else: min_hwe_kernel = validate_hwe_kernel( None, min_hwe_kernel, "%s/%s" % (arch, subarch), osystem, series, ) # If no hwe_kernel was found set the subarch to the default, 'generic.' if min_hwe_kernel is None: subarch = "generic" else: subarch = min_hwe_kernel # Global kernel options for enlistment. extra_kernel_opts = configs["kernel_opts"] boot_purpose = get_final_boot_purpose(machine, arch, purpose) kernel, initrd, boot_dtb = get_boot_filenames( arch, subarch, osystem, series, commissioning_osystem=configs["commissioning_osystem"], commissioning_distro_series=configs["commissioning_distro_series"], ) # Return the params to the rack controller. Include the system_id only # if the machine was known. params = { "arch": arch, "subarch": subarch, "osystem": osystem, "release": series, "kernel": kernel, "initrd": initrd, "boot_dtb": boot_dtb, "purpose": boot_purpose, "hostname": hostname, "domain": domain, "preseed_url": preseed_url, "fs_host": local_ip, "log_host": log_host, "log_port": log_port, "extra_opts": "" if extra_kernel_opts is None else extra_kernel_opts, # As of MAAS 2.4 only HTTP boot is supported. This ensures MAAS 2.3 # rack controllers use HTTP boot as well. "http_boot": True, } if machine is not None: params["system_id"] = machine.system_id return params
def get_boot_config_for_machine(machine, configs): """Get the correct operating system and series based on the purpose of the booting machine. """ purpose = machine.get_boot_purpose() _, subarch = machine.split_arch() precise = False if purpose == "commissioning": osystem = configs['commissioning_osystem'] series = configs['commissioning_distro_series'] else: osystem = machine.get_osystem(default=configs['default_osystem']) series = machine.get_distro_series( default=configs['default_distro_series']) # XXX: roaksoax LP: #1739761 - Since the switch to squashfs (and # drop of iscsi), precise is no longer deployable. To address a # squashfs image is made available allowing it to be deployed in # the commissioning ephemeral environment. precise = True if series == "precise" else False if purpose == "xinstall" and (osystem != "ubuntu" or precise): # Use only the commissioning osystem and series, for operating # systems other than Ubuntu. As Ubuntu supports HWE kernels, # and needs to use that kernel to perform the installation. osystem = configs['commissioning_osystem'] series = configs['commissioning_distro_series'] # Pre MAAS-1.9 the subarchitecture defined any kernel the machine # needed to be able to boot. This could be a hardware enablement # kernel(e.g hwe-t) or something like highbank. With MAAS-1.9 any # hardware enablement kernel must be specifed in the hwe_kernel field, # any other kernel, such as highbank, is still specifed as a # subarchitecture. Since Ubuntu does not support architecture specific # hardware enablement kernels(i.e a highbank hwe-t kernel on precise) # we give precedence to any kernel defined in the subarchitecture field # XXX: roaksoax LP: #1739761 - Do not override the subarch (used for # the deployment ephemeral env) when deploying precise, provided that # it uses the commissioning distro_series and hwe kernels are not # needed. kernel_validated = False if subarch == "generic" and machine.hwe_kernel and not precise: subarch = machine.hwe_kernel elif (subarch == "generic" and purpose == "commissioning" and machine.min_hwe_kernel): try: subarch = validate_hwe_kernel(None, machine.min_hwe_kernel, machine.architecture, osystem, series) except ValidationError: subarch = "no-such-kernel" kernel_validated = True # LP:1730525 - If entering the ephemeral environment from a deployed # OS subarch will be machine.hwe_kernel. If a newer version of Ubuntu # was deployed then the commissioning OS hwe_kernel will not be # available. Use the hwe kernel instead of the ga. if purpose == "commissioning" and not kernel_validated: try: subarch = validate_hwe_kernel(subarch, machine.min_hwe_kernel, machine.architecture, osystem, series) except ValidationError: try: # Get the default kernel. subarch = validate_hwe_kernel(None, machine.min_hwe_kernel, machine.architecture, osystem, series) except ValidationError: subarch = "no-such-kernel" else: # Switch to hwe- subarch = subarch.replace('ga-', 'hwe-') return osystem, series, subarch