Beispiel #1
0
 def test_connectionMade_sets_the_request(self):
     protocol, factory = self.make_protocol(patch_authenticate=False)
     self.patch_autospec(protocol, "authenticate")
     # Be sure the request field is populated by the time that
     # processMessages() is called.
     processMessages_mock = self.patch_autospec(protocol, "processMessages")
     processMessages_mock.side_effect = lambda: self.assertThat(
         protocol.request.user, Equals(protocol.user)
     )
     protocol.authenticate.return_value = defer.succeed(sentinel.user)
     protocol.connectionMade()
     self.addCleanup(protocol.connectionLost, "")
     self.assertEqual(protocol.user, protocol.request.user)
     self.assertEqual(
         protocol.request.META["HTTP_USER_AGENT"],
         protocol.transport.user_agent,
     )
     self.assertEqual(
         protocol.request.META["REMOTE_ADDR"], protocol.transport.ip_address
     )
     self.assertEqual(
         protocol.request.META["SERVER_NAME"],
         splithost(protocol.transport.host)[0],
     )
     self.assertEqual(
         protocol.request.META["SERVER_PORT"],
         splithost(protocol.transport.host)[1],
     )
Beispiel #2
0
 def test_connectionMade_sets_the_request(self):
     protocol, factory = self.make_protocol(patch_authenticate=False)
     self.patch_autospec(protocol, "authenticate")
     self.patch_autospec(protocol, "processMessages")
     protocol.authenticate.return_value = defer.succeed(sentinel.user)
     protocol.connectionMade()
     self.addCleanup(protocol.connectionLost, "")
     self.assertEquals(protocol.user, protocol.request.user)
     self.assertEquals(protocol.request.META['HTTP_USER_AGENT'],
                       protocol.transport.user_agent)
     self.assertEquals(protocol.request.META['REMOTE_ADDR'],
                       protocol.transport.ip_address)
     self.assertEquals(protocol.request.META['SERVER_NAME'],
                       splithost(protocol.transport.host)[0])
     self.assertEquals(protocol.request.META['SERVER_PORT'],
                       splithost(protocol.transport.host)[1])
Beispiel #3
0
        def authenticated(user):
            if user is None:
                # This user could not be authenticated. No further interaction
                # should take place. The connection is already being dropped.
                pass
            else:
                # This user is a keeper. Record it and process any message
                # that have already been received.
                self.user = user

                # XXX newell 2018-10-17 bug=1798479:
                # Check that 'SERVER_NAME' and 'SERVER_PORT' are set.
                # 'SERVER_NAME' and 'SERVER_PORT' are required so
                # `build_absolure_uri` can create an actual absolute URI so
                # that the curtin configuration is valid.  See the bug and
                # maasserver.node_actions for more details.
                #
                # `splithost` will split the host and port from either an
                # ipv4 or an ipv6 address.
                host, port = splithost(str(self.transport.host))

                # Create the request for the handlers for this connection.
                self.request = HttpRequest()
                self.request.user = self.user
                self.request.META.update({
                    "HTTP_USER_AGENT": self.transport.user_agent,
                    "REMOTE_ADDR": self.transport.ip_address,
                    "SERVER_NAME": host or "localhost",
                    "SERVER_PORT": port or 5248,
                })

                # Be sure to process messages after the metadata is populated,
                # in order to avoid bug #1802390.
                self.processMessages()
                self.factory.clients.append(self)
Beispiel #4
0
 def clean(self, value):
     value = super().clean(value)
     if not value:
         return None
     host, port = splithost(value)
     if not port:
         port = 514
     return "%s:%d" % (host, port)
Beispiel #5
0
 def clean(self, value):
     value = super(RemoteSyslogField, self).clean(value)
     if not value:
         return None
     host, port = splithost(value)
     if not port:
         port = 514
     return '%s:%d' % (host, port)
Beispiel #6
0
        def authenticated(user):
            if user is None:
                # This user could not be authenticated. No further interaction
                # should take place. The connection is already being dropped.
                pass
            else:
                # This user is a keeper. Record it and process any message
                # that have already been received.
                self.user = user
                self.processMessages()
                self.factory.clients.append(self)

                # Create the request for the handlers for this connection.
                self.request = HttpRequest()
                self.request.user = self.user
                self.request.META['HTTP_USER_AGENT'] = (
                    self.transport.user_agent)
                self.request.META['REMOTE_ADDR'] = self.transport.ip_address

                # XXX newell 2018-10-17 bug=1798479:
                # Check that 'SERVER_NAME' and 'SERVER_PORT' are set.
                # 'SERVER_NAME' and 'SERVER_PORT' are required so
                # `build_absolure_uri` can create an actual absolute URI so
                # that the curtin configuration is valid.  See the bug and
                # maasserver.node_actions for more details.
                #
                # `splithost` will split the host and port from either an
                # ipv4 or an ipv6 address.
                host, port = splithost(str(self.transport.host))
                if host:
                    self.request.META['SERVER_NAME'] = host
                else:
                    self.request.META['SERVER_NAME'] = 'localhost'
                if port:
                    self.request.META['SERVER_PORT'] = port
                else:
                    self.request.META['SERVER_PORT'] = 5248
Beispiel #7
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
Beispiel #8
0
 def test__result(self):
     self.assertEqual(self.result, splithost(self.host))
Beispiel #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 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_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(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)
            if subnet:
                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()

        # 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": 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"]

    # 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": 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