def test_write_dhcp_config_invokes_script_correctly(self): mocked_proc = Mock() mocked_proc.returncode = 0 mocked_proc.communicate = Mock(return_value=('output', 'error output')) mocked_popen = self.patch(utils, "Popen", Mock(return_value=mocked_proc)) config_params = self.make_dhcp_config_params() write_dhcp_config(**config_params) # It should construct Popen with the right parameters. mocked_popen.assert_any_call([ "sudo", "-n", "maas-provision", "atomic-write", "--filename", celery_config.DHCP_CONFIG_FILE, "--mode", "0644" ], stdin=PIPE) # It should then pass the content to communicate(). content = config.get_config(**config_params).encode("ascii") mocked_proc.communicate.assert_any_call(content) # Similarly, it also writes the DHCPD interfaces to # /var/lib/maas/dhcpd-interfaces. mocked_popen.assert_any_call([ "sudo", "-n", "maas-provision", "atomic-write", "--filename", celery_config.DHCP_INTERFACES_FILE, "--mode", "0644" ], stdin=PIPE)
def test_renders_without_ntp_servers_set(self): params = make_sample_params() del params['ntp_server'] template = self.patch_template() rendered = template.substitute(params) self.assertEqual(rendered, config.get_config(**params)) self.assertNotIn("ntp-servers", rendered)
def test__renders_router_ip_if_present(self): params = make_sample_params(self, ipv6=self.ipv6) router_ip = factory.make_ipv4_address() params['shared_networks'][0]['subnets'][0]['router_ip'] = router_ip rendered = config.get_config(self.template, **params) validate_dhcpd_configuration(self, rendered, self.ipv6) self.assertThat(rendered, Contains(router_ip))
def test__renders_with_hosts(self): params = make_sample_params(self, ipv6=self.ipv6) self.assertThat( params["hosts"], AfterPreprocessing( len, GreaterThanOrEqual(1))) config_output = config.get_config(self.template, **params) validate_dhcpd_configuration(self, config_output, self.ipv6) self.assertThat( config_output, ContainsAll([ host['host'] for host in params["hosts"] ])) self.assertThat( config_output, ContainsAll([ host['mac'] for host in params["hosts"] ])) self.assertThat( config_output, ContainsAll([ host['ip'] for host in params["hosts"] ]))
def test__renders_without_ntp_servers_set(self): params = make_sample_params(self, ipv6=self.ipv6) for network in params['shared_networks']: for subnet in network['subnets']: subnet['ntp_servers'] = "" rendered = config.get_config(self.template, **params) validate_dhcpd_configuration(self, rendered, self.ipv6) self.assertNotIn("ntp-servers", rendered)
def test__uses_branch_template_by_default(self): # Since the branch comes with dhcp templates in etc/maas, we can # instantiate those templates without any hackery. self.assertIsNotNone( config.get_config( self.template, **make_sample_params(self, ipv6=self.ipv6) ) )
def run(args): """Generate a DHCP server configuration, and write it to stdout.""" params = vars(args) output = config.get_config(**params).encode("ascii") if args.outfile is None: sys.stdout.write(output) else: with open(args.outfile, "wb") as out: out.write(output)
def test__renders_without_search_list_set(self): params = make_sample_params(self, ipv6=self.ipv6) for network in params['shared_networks']: for subnet in network['subnets']: subnet['search_list'] = "" rendered = config.get_config(self.template, **params) validate_dhcpd_configuration(self, rendered, self.ipv6) self.assertNotIn("dhcp6.domain-search", rendered) # IPv6 self.assertNotIn("domain-search", rendered) # IPv4
def test_renders_without_dns_servers_set(self): params = make_sample_params(self, ipv6=self.ipv6) for network in params["shared_networks"]: for subnet in network["subnets"]: subnet["dns_servers"] = "" rendered = config.get_config(self.template, **params) validate_dhcpd_configuration(self, rendered, self.ipv6) self.assertNotIn("dhcp6.name-servers", rendered) # IPv6 self.assertNotIn("domain-name-servers", rendered) # IPv4
def test__renders_global_dhcp_snippets(self): params = make_sample_params(self, ipv6=self.ipv6) config_output = config.get_config(self.template, **params) validate_dhcpd_configuration(self, config_output, self.ipv6) self.assertThat( config_output, ContainsAll([ dhcp_snippet['value'] for dhcp_snippet in params['global_dhcp_snippets'] ]))
def get_config(self, server): """Return the configuration for `server`.""" dhcpd_config = get_config( server.template_basename, omapi_key=self.omapi_key, failover_peers=self.failover_peers, ipv6=server.ipv6, shared_networks=self.shared_networks, hosts=sorted(self.hosts.values(), key=itemgetter("host")), global_dhcp_snippets=sorted( self.global_dhcp_snippets, key=itemgetter("name"))) return dhcpd_config, " ".join(self.interfaces)
def test__renders_with_empty_string_router_ip(self): params = make_sample_params(self, ipv6=self.ipv6) for network in params['shared_networks']: for subnet in network['subnets']: subnet['router_ip'] = '' rendered = config.get_config(self.template, **params) validate_dhcpd_configuration(self, rendered, self.ipv6) # Remove all lines that have been commented out. rendered = "".join(line for line in rendered.splitlines(keepends=True) if not line.lstrip().startswith("#")) self.assertNotIn("option routers", rendered)
def test__includes_compose_conditional_bootloader(self): params = make_sample_params(self, ipv6=self.ipv6) rack_ip = params['shared_networks'][0]['subnets'][0]['router_ip'] self.patch( net_utils, 'get_all_interface_addresses' ).return_value = [netaddr.IPAddress(rack_ip)] bootloader = config.compose_conditional_bootloader( ipv6=self.ipv6, rack_ip=rack_ip) rendered = config.get_config(self.template, **params) validate_dhcpd_configuration(self, rendered, self.ipv6) self.assertThat(rendered, Contains(bootloader))
def test_includes_next_server_in_config_from_interface_addresses(self): params = make_sample_params(self, ipv6=False, with_interface=True) subnet = params["shared_networks"][0]["subnets"][0] next_server_ip = factory.pick_ip_in_network( netaddr.IPNetwork(subnet["subnet_cidr"])) self.patch( net_utils, "get_all_addresses_for_interface").return_value = [next_server_ip] config_output = config.get_config("dhcpd.conf.template", **params) validate_dhcpd_configuration(self, config_output, False) self.assertThat(config_output, Contains("next-server %s;" % next_server_ip))
def test__renders_subnet_dhcp_snippets(self): params = make_sample_params(self, ipv6=self.ipv6) config_output = config.get_config(self.template, **params) validate_dhcpd_configuration(self, config_output, self.ipv6) for shared_network in params['shared_networks']: for subnet in shared_network['subnets']: self.assertThat( config_output, ContainsAll([ dhcp_snippet['value'] for dhcp_snippet in subnet['dhcp_snippets'] ]))
def test__renders_subnet_cidr(self): params = make_sample_params(self, ipv6=self.ipv6) config_output = config.get_config(self.template, **params) validate_dhcpd_configuration(self, config_output, self.ipv6) for shared_network in params['shared_networks']: for subnet in shared_network['subnets']: if self.ipv6 is True: expected = "subnet6 %s" % subnet['subnet_cidr'] else: expected = "subnet %s netmask %s" % (subnet['subnet'], subnet['subnet_mask']) self.assertThat(config_output, Contains(expected))
def test__includes_next_server_in_config_from_all_addresses(self): params = make_sample_params(self, ipv6=False) subnet = params['shared_networks'][0]['subnets'][0] next_server_ip = factory.pick_ip_in_network( netaddr.IPNetwork(subnet['subnet_cidr'])) self.patch(net_utils, 'get_all_interface_addresses').return_value = [ next_server_ip ] config_output = config.get_config('dhcpd.conf.template', **params) validate_dhcpd_configuration(self, config_output, False) self.assertThat( config_output, Contains('next-server %s;' % next_server_ip))
def test_renders_node_dhcp_snippets(self): params = make_sample_params(self, ipv6=self.ipv6) config_output = config.get_config(self.template, **params) validate_dhcpd_configuration(self, config_output, self.ipv6) for host in params["hosts"]: self.assertThat( config_output, ContainsAll([ dhcp_snippet["value"] for dhcp_snippet in host["dhcp_snippets"] ]), )
def test__renders_dns_servers_as_comma_separated_list(self): params = make_sample_params(self, ipv6=self.ipv6) rendered = config.get_config(self.template, **params) validate_dhcpd_configuration(self, rendered, self.ipv6) dns_servers_expected = [ ", ".join(map(str, subnet["dns_servers"])) for network in params['shared_networks'] for subnet in network['subnets'] ] dns_servers_pattern = r"\b%s\s+(.+);" % re.escape( "dhcp6.name-servers" if self.ipv6 else "domain-name-servers") dns_servers_observed = re.findall(dns_servers_pattern, rendered) self.assertEqual(dns_servers_expected, dns_servers_observed)
def write_dhcp_config(callback=None, **kwargs): """Write out the DHCP configuration file and restart the DHCP server. :param dhcp_interfaces: Space-separated list of interfaces that the DHCP server should listen on. :param **kwargs: Keyword args passed to dhcp.config.get_config() """ sudo_write_file( celery_config.DHCP_CONFIG_FILE, config.get_config(**kwargs)) sudo_write_file( celery_config.DHCP_INTERFACES_FILE, kwargs.get('dhcp_interfaces', '')) if callback is not None: callback.delay()
def test__silently_discards_unresolvable_ntp_servers(self): params = make_sample_params_only(ipv6=self.ipv6) rendered = config.get_config(self.template, **params) validate_dhcpd_configuration(self, rendered, self.ipv6) ntp_servers_expected = [ server for network in params['shared_networks'] for subnet in network['subnets'] for server in subnet["ntp_servers"] if is_ip_address(server) ] ntp_servers_observed = [ server for server_line in re.findall( r"\b(?:ntp-servers|dhcp6[.]sntp-servers)\s+(.+);", rendered) for server in server_line.split(", ") ] self.assertItemsEqual(ntp_servers_expected, ntp_servers_observed)
def test_includes_compose_conditional_bootloader(self): params = make_sample_params(self, ipv6=self.ipv6) rack_ip = params["shared_networks"][0]["subnets"][0]["router_ip"] self.patch(net_utils, "get_all_interface_addresses").return_value = [ netaddr.IPAddress(rack_ip) ] bootloader = config.compose_conditional_bootloader(ipv6=self.ipv6, rack_ip=rack_ip) rendered = config.get_config(self.template, **params) validate_dhcpd_configuration(self, rendered, self.ipv6) self.assertThat(rendered, Contains(bootloader)) # Verify that "/images/" is automatically added to bootloaders # loaded over HTTP. This ensures nginx handles the result without # bothering rackd. self.assertIn("/images/bootx64.efi", rendered)
def test__renders_ntp_servers_as_comma_separated_list(self): params = make_sample_params(self, ipv6=self.ipv6) rendered = config.get_config(self.template, **params) validate_dhcpd_configuration(self, rendered, self.ipv6) ntp_servers_expected = flatten([ server if is_ip_address(server) else _get_addresses(server) for network in params['shared_networks'] for subnet in network['subnets'] for server in subnet["ntp_servers"] ]) ntp_servers_observed = [ server for server_line in re.findall( r"\b(?:ntp-servers|dhcp6[.]sntp-servers)\s+(.+);", rendered) for server in server_line.split(", ") ] self.assertItemsEqual(ntp_servers_expected, ntp_servers_observed)
def test__renders_search_list_as_quoted_comma_separated_list(self): params = make_sample_params(self, ipv6=self.ipv6) for network in params['shared_networks']: for subnet in network['subnets']: subnet['search_list'].append("canonical.com") rendered = config.get_config(self.template, **params) validate_dhcpd_configuration(self, rendered, self.ipv6) dns_servers_expected = [ ", ".join(map(quote, subnet["search_list"])) for network in params['shared_networks'] for subnet in network['subnets'] ] dns_servers_pattern = r"\b%s\s+(.+);" % re.escape( "dhcp6.domain-search" if self.ipv6 else "domain-search") dns_servers_observed = re.findall(dns_servers_pattern, rendered) self.assertEqual(dns_servers_expected, dns_servers_observed)
def test_write_dhcp_config_invokes_script_correctly(self): mocked_proc = Mock() mocked_proc.returncode = 0 mocked_proc.communicate = Mock(return_value=('output', 'error output')) mocked_popen = self.patch( utils, "Popen", Mock(return_value=mocked_proc)) config_params = self.make_dhcp_config_params() write_dhcp_config(**config_params) # It should construct Popen with the right parameters. mocked_popen.assert_any_call( ["sudo", "-n", "maas-provision", "atomic-write", "--filename", celery_config.DHCP_CONFIG_FILE, "--mode", "0644"], stdin=PIPE) # It should then pass the content to communicate(). content = config.get_config(**config_params).encode("ascii") mocked_proc.communicate.assert_any_call(content) # Similarly, it also writes the DHCPD interfaces to # /var/lib/maas/dhcpd-interfaces. mocked_popen.assert_any_call( ["sudo", "-n", "maas-provision", "atomic-write", "--filename", celery_config.DHCP_INTERFACES_FILE, "--mode", "0644"], stdin=PIPE)
def test_param_substitution(self): template = self.patch_template() params = make_sample_params() self.assertEqual(template.substitute(params), config.get_config(**params))
def test_uses_branch_template_by_default(self): # Since the branch comes with a dhcp template in etc/maas, we can # instantiate that template without any hackery. self.assertIsNotNone(config.get_config(**make_sample_params()))
def test_config_refers_to_bootloader(self): params = make_sample_params() output = config.get_config(**params) self.assertThat(output, Contains(compose_bootloader_path()))
def test_param_substitution(self): template = self.patch_template() params = make_sample_params() self.assertEqual( template.substitute(params), config.get_config(**params))