Example #1
0
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
Example #2
0
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
Example #3
0
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
Example #4
0
 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"])
Example #5
0
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
Example #6
0
File: boot.py Project: Kryndex/maas
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
Example #7
0
    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
Example #8
0
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
Example #9
0
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
Example #10
0
    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
Example #11
0
    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