def get_default_dns_servers(rack_controller, subnet, use_rack_proxy=True): """Calculates the DNS servers on a per-subnet basis, to make sure we choose the best possible IP addresses for each subnet. :param rack_controller: The RackController to be used as the DHCP server. :param subnet: The DHCP-managed subnet. :param use_rack_proxy: Whether to proxy DNS through the rack controller or not. """ ip_version = subnet.get_ip_version() try: default_region_ip = get_source_address(subnet.get_ipnetwork()) dns_servers = get_dns_server_addresses( rack_controller, ipv4=(ip_version == 4), ipv6=(ip_version == 6), include_alternates=True, default_region_ip=default_region_ip, ) except UnresolvableHost: dns_servers = None if use_rack_proxy: # Add the IP address for the rack controllers on the subnet before the # region DNS servers. rack_ips = get_dns_server_addresses_for_rack(rack_controller, subnet) if dns_servers: dns_servers = rack_ips + [ server for server in dns_servers if server not in rack_ips ] elif rack_ips: dns_servers = rack_ips return dns_servers
def get_default_region_ip(request): """Returns the default reply address for the given HTTP request.""" remote_ip = get_remote_ip(request) default_region_ip = None if remote_ip is not None: default_region_ip = get_source_address(remote_ip) return default_region_ip
def get_default_dns_servers(rack_controller, subnet, use_rack_proxy=True): """Calculates the DNS servers on a per-subnet basis, to make sure we choose the best possible IP addresses for each subnet. :param rack_controller: The RackController to be used as the DHCP server. :param subnet: The DHCP-managed subnet. :param use_rack_proxy: Whether to proxy DNS through the rack controller or not. """ if not subnet.allow_dns: # This subnet isn't allowed to use region or rack addresses for dns return [] ip_version = subnet.get_ip_version() default_region_ip = get_source_address(subnet.get_ipnetwork()) try: dns_servers = get_dns_server_addresses( rack_controller, ipv4=(ip_version == 4), ipv6=(ip_version == 6), include_alternates=True, default_region_ip=default_region_ip, ) except UnresolvableHost: dns_servers = [] if default_region_ip: default_region_ip = IPAddress(default_region_ip) if use_rack_proxy: # Add the IP address for the rack controllers on the subnet before the # region DNS servers. rack_ips = get_dns_server_addresses_for_rack(rack_controller, subnet) if dns_servers: dns_servers = rack_ips + [ server for server in dns_servers if server not in rack_ips and server != default_region_ip ] elif rack_ips: dns_servers = rack_ips elif default_region_ip in dns_servers: # Make sure the region DNS server comes last dns_servers = [ server for server in dns_servers if server != default_region_ip ] + [default_region_ip] # If no DNS servers were found give the region IP. This won't go through # the rack but its better than nothing. if not dns_servers: if default_region_ip: log.warn("No DNS servers found, DHCP defaulting to region IP.") dns_servers = [default_region_ip] else: log.warn("No DNS servers found.") return dns_servers
def test__has_enlistment_preseed_url_for_default(self): rack_controller = factory.make_RackController() local_ip = factory.make_ip_address() remote_ip = factory.make_ip_address() factory.make_default_ubuntu_release_bootable() observed_config = get_config(rack_controller.system_id, local_ip, remote_ip) self.assertEqual( compose_enlistment_preseed_url( default_region_ip=get_source_address(remote_ip)), observed_config["preseed_url"])
def get_default_dns_servers(rack_controller, subnet): """Calculates the DNS servers on a per-subnet basis, to make sure we choose the best possible IP addresses for each subnet. :param rack_controller: The RackController to be used as the DHCP server. :param subnet: The DHCP-managed subnet. """ ip_version = subnet.get_ip_version() try: default_region_ip = get_source_address(subnet.get_ipnetwork()) maas_dns_servers = get_dns_server_addresses( rack_controller, ipv4=(ip_version == 4), ipv6=(ip_version == 6), include_alternates=True, default_region_ip=default_region_ip) except UnresolvableHost: maas_dns_servers = None return maas_dns_servers
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 __init__(self, node, version=1, source_routing=False): """Create the YAML network configuration for the specified node, and store it in the `config` ivar. """ self.node = node self.matching_routes = set() self.v1_config = [] self.v2_config = [ ("version", 2), ] self.v2_ethernets = {} self.v2_vlans = {} self.v2_bonds = {} self.v2_bridges = {} # Reserved routing tables in Linux are 0, 253, 254, and 255. self.next_routing_table_id = 1 self.gateway_ipv4_set = False self.gateway_ipv6_set = False self.source_routing = source_routing # The default value is False: expected keys are 4 and 6. self.addr_family_present = defaultdict(bool) # Ensure the machine's primary domain always comes first in the list. self.default_search_list = [self.node.domain.name] + [ name for name in sorted(get_dns_search_paths()) if name != self.node.domain.name ] self.gateways = self.node.get_default_gateways() if self.gateways.ipv4 is not None: dest_ip = self.gateways.ipv4.gateway_ip elif self.gateways.ipv6 is not None: dest_ip = self.gateways.ipv6.gateway_ip else: dest_ip = None if dest_ip is not None: default_source_ip = get_source_address(dest_ip) else: default_source_ip = None self.routes = StaticRoute.objects.all() interfaces = Interface.objects.all_interfaces_parents_first(self.node) for iface in interfaces: if not iface.is_enabled(): continue generator = InterfaceConfiguration( iface, self, version=version, source_routing=self.source_routing) self.matching_routes.update(generator.matching_routes) self.addr_family_present.update(generator.addr_family_present) if version == 1: self.v1_config.append(generator.config) elif version == 2: v2_config = {generator.name: generator.config} if generator.type == INTERFACE_TYPE.PHYSICAL: self.v2_ethernets.update(v2_config) elif generator.type == INTERFACE_TYPE.VLAN: self.v2_vlans.update(v2_config) elif generator.type == INTERFACE_TYPE.BOND: self.v2_bonds.update(v2_config) elif generator.type == INTERFACE_TYPE.BRIDGE: self.v2_bridges.update(v2_config) # If we have no IPv6 addresses present, make sure we claim IPv4, so # that we at least get some address. if not self.addr_family_present[6]: self.addr_family_present[4] = True self.default_dns_servers = self.node.get_default_dns_servers( ipv4=self.addr_family_present[4], ipv6=self.addr_family_present[6], default_region_ip=default_source_ip) self.v1_config.append({ "type": "nameserver", "address": self.default_dns_servers, "search": self.default_search_list, }) if version == 1: network_config = { "network": { "version": 1, "config": self.v1_config, }, } else: if len(self.v2_ethernets) > 0: self.v2_config.append(('ethernets', self.v2_ethernets)) if len(self.v2_vlans) > 0: self.v2_config.append(('vlans', self.v2_vlans)) if len(self.v2_bonds) > 0: self.v2_config.append(('bonds', self.v2_bonds)) if len(self.v2_bridges) > 0: self.v2_config.append(('bridges', self.v2_bridges)) self.set_v2_default_dns() network_config = { "network": OrderedDict(self.v2_config), } self.config = network_config
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_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 __init__(self, node, version=1): """Create the YAML network configuration for the specified node, and store it in the `config` ivar. """ self.node = node self.matching_routes = set() self.v1_config = [] self.v2_config = {"version": 2} self.v2_ethernets = {} self.v2_vlans = {} self.v2_bonds = {} self.v2_bridges = {} self.gateway_ipv4_set = False self.gateway_ipv6_set = False # The default value is False: expected keys are 4 and 6. self.addr_family_present = defaultdict(bool) # Ensure the machine's primary domain always comes first in the list. self.default_search_list = [self.node.domain.name] + [ name for name in sorted(get_dns_search_paths()) if name != self.node.domain.name ] self.gateways = self.node.get_default_gateways() if self.gateways.ipv4 is not None: dest_ip = self.gateways.ipv4.gateway_ip elif self.gateways.ipv6 is not None: dest_ip = self.gateways.ipv6.gateway_ip else: dest_ip = None if dest_ip is not None: default_source_ip = get_source_address(dest_ip) else: default_source_ip = None self.routes = StaticRoute.objects.all() interfaces = Interface.objects.all_interfaces_parents_first(self.node) for iface in interfaces: if not iface.is_enabled(): continue generator = InterfaceConfiguration(iface, self, version=version) self.matching_routes.update(generator.matching_routes) self.addr_family_present.update(generator.addr_family_present) if version == 1: self.v1_config.append(generator.config) elif version == 2: v2_config = {generator.name: generator.config} if generator.type == INTERFACE_TYPE.PHYSICAL: self.v2_ethernets.update(v2_config) elif generator.type == INTERFACE_TYPE.VLAN: self.v2_vlans.update(v2_config) elif generator.type == INTERFACE_TYPE.BOND: self.v2_bonds.update(v2_config) elif generator.type == INTERFACE_TYPE.BRIDGE: self.v2_bridges.update(v2_config) # If we have no IPv6 addresses present, make sure we claim IPv4, so # that we at least get some address. if not self.addr_family_present[6]: self.addr_family_present[4] = True default_dns_servers = self.node.get_default_dns_servers( ipv4=self.addr_family_present[4], ipv6=self.addr_family_present[6], default_region_ip=default_source_ip) self.v1_config.append({ "type": "nameserver", "address": default_dns_servers, "search": self.default_search_list, }) if version == 1: network_config = { "network": { "version": 1, "config": self.v1_config, }, } else: network_config = { "network": self.v2_config, } v2_config = network_config['network'] if len(self.v2_ethernets) > 0: v2_config.update({"ethernets": self.v2_ethernets}) if len(self.v2_vlans) > 0: v2_config.update({"vlans": self.v2_vlans}) if len(self.v2_bonds) > 0: v2_config.update({"bonds": self.v2_bonds}) if len(self.v2_bridges) > 0: v2_config.update({"bridges": self.v2_bridges}) # XXX mpontillo 2017-02-17: netplan has no concept of "default" # DNS servers. Need to define how to convey this. # See launchpad bug #1664806. # if len(default_dns_servers) > 0 or len(search_list) > 0: # nameservers = {} # if len(search_list) > 0: # nameservers.update({"search": search_list}) # if len(default_dns_servers) > 0: # nameservers.update({"addresses": default_dns_servers}) # v2_config.update({"nameservers": nameservers}) self.config = network_config
def __init__(self, node, version=1, source_routing=False): """Create the YAML network configuration for the specified node, and store it in the `config` ivar. """ self.node = node self.matching_routes = set() self.v1_config = [] self.v2_config = [("version", 2)] self.v2_ethernets = {} self.v2_vlans = {} self.v2_bonds = {} self.v2_bridges = {} # Reserved routing tables in Linux are 0, 253, 254, and 255. self.next_routing_table_id = 1 self.gateway_ipv4_set = False self.gateway_ipv6_set = False self.source_routing = source_routing # The default value is False: expected keys are 4 and 6. self.addr_family_present = defaultdict(bool) # Ensure the machine's primary domain always comes first in the list. self.default_search_list = [self.node.domain.name] + [ name for name in sorted(get_dns_search_paths()) if name != self.node.domain.name ] self.gateways = self.node.get_default_gateways() if self.gateways.ipv4 is not None: dest_ip = self.gateways.ipv4.gateway_ip elif self.gateways.ipv6 is not None: dest_ip = self.gateways.ipv6.gateway_ip else: dest_ip = None if dest_ip is not None: default_source_ip = get_source_address(dest_ip) else: default_source_ip = None self.routes = StaticRoute.objects.all() interfaces = Interface.objects.all_interfaces_parents_first(self.node) for iface in interfaces: if not iface.is_enabled(): continue generator = InterfaceConfiguration( iface, self, version=version, source_routing=self.source_routing, ) self.matching_routes.update(generator.matching_routes) self.addr_family_present.update(generator.addr_family_present) if version == 1: self.v1_config.append(generator.config) elif version == 2: v2_config = {generator.name: generator.config} if generator.type == INTERFACE_TYPE.PHYSICAL: self.v2_ethernets.update(v2_config) elif generator.type == INTERFACE_TYPE.VLAN: self.v2_vlans.update(v2_config) elif generator.type == INTERFACE_TYPE.BOND: self.v2_bonds.update(v2_config) elif generator.type == INTERFACE_TYPE.BRIDGE: self.v2_bridges.update(v2_config) # If we have no IPv6 addresses present, make sure we claim IPv4, so # that we at least get some address. if not self.addr_family_present[6]: self.addr_family_present[4] = True self.default_dns_servers = self.node.get_default_dns_servers( ipv4=self.addr_family_present[4], ipv6=self.addr_family_present[6], default_region_ip=default_source_ip, ) # LP:1847537 - V1 network config only allows global DNS configuration # while V1 allows DNS configuration per interface. If interfaces are # only being manually configured or use DHCP do not include DNS servers # in the configuration. The DHCP server will provide them. dhcp_only = True for i in self.v1_config: for subnet in i.get("subnets", []): if subnet.get("type", "manual") not in [ "manual", "dhcp", "dhcp4", "dhcp6", ]: dhcp_only = False break if not dhcp_only: break if self.default_dns_servers and not dhcp_only: self.v1_config.append({ "type": "nameserver", "address": self.default_dns_servers, "search": self.default_search_list, }) if version == 1: network_config = { "network": { "version": 1, "config": self.v1_config } } else: if len(self.v2_ethernets) > 0: self.v2_config.append(("ethernets", self.v2_ethernets)) if len(self.v2_vlans) > 0: self.v2_config.append(("vlans", self.v2_vlans)) if len(self.v2_bonds) > 0: self.v2_config.append(("bonds", self.v2_bonds)) if len(self.v2_bridges) > 0: self.v2_config.append(("bridges", self.v2_bridges)) self.set_v2_default_dns() network_config = {"network": OrderedDict(self.v2_config)} self.config = network_config