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], )
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])
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)
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)
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)
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
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 test__result(self): self.assertEqual(self.result, splithost(self.host))
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