From d96ce5291c78c371e132813a514a1fb6e0994850 Mon Sep 17 00:00:00 2001 From: Bart Wensley Date: Thu, 24 Jan 2019 07:22:05 -0600 Subject: [PATCH] Allow DNS server configuration for kubernetes In kubernetes deployments, a DNS server is required to locate the registry servers used to download the kubernetes images. Currently, when config_controller is run, the 8.8.8.8 nameserver is used, with no way to change it. Some users need to specify their own name server to be used during the execution of config_controller. This change allows the user to specify up to three DNS servers when running config_controller interactively or with a config file. If using a config file, add the following section to the config file (only one nameserver is required, but up to three are allowed): [DNS] NAMESERVER_1=8.8.8.8 NAMESERVER_2=8.8.4.4 NAMESERVER_3=9.9.9.9 Change-Id: I59556138a11c6f627f45886a2da6b8a1ad9d89e1 Closes-bug: 1812449 Signed-off-by: Bart Wensley --- .../configutilities/__init__.py | 1 + .../configutilities/common/validator.py | 22 +++- .../controllerconfig/configassistant.py | 116 ++++++++++++++++++ .../tests/files/cgcs_config.kubernetes | 86 +++++++++++++ ...onfig.cluster => system_config.kubernetes} | 5 + .../controllerconfig/tests/test_answerfile.py | 12 +- .../tests/test_system_config.py | 24 ++-- .../modules/platform/manifests/kubernetes.pp | 9 +- 8 files changed, 251 insertions(+), 24 deletions(-) create mode 100755 controllerconfig/controllerconfig/controllerconfig/tests/files/cgcs_config.kubernetes rename controllerconfig/controllerconfig/controllerconfig/tests/files/{system_config.cluster => system_config.kubernetes} (94%) diff --git a/configutilities/configutilities/configutilities/__init__.py b/configutilities/configutilities/configutilities/__init__.py index bd7610e820..0717792c47 100755 --- a/configutilities/configutilities/configutilities/__init__.py +++ b/configutilities/configutilities/configutilities/__init__.py @@ -29,4 +29,5 @@ from configutilities.common.utils import validate_address # noqa: F401 from configutilities.common.utils import ip_version_to_string # noqa: F401 from configutilities.common.utils import lag_mode_to_str # noqa: F401 from configutilities.common.utils import validate_openstack_password # noqa: F401 +from configutilities.common.utils import validate_nameserver_address_str # noqa: F401 from configutilities.common.utils import extract_openstack_password_rules_from_file # noqa: F401 diff --git a/configutilities/configutilities/configutilities/common/validator.py b/configutilities/configutilities/configutilities/common/validator.py index 2bba43351e..3045459bf0 100644 --- a/configutilities/configutilities/configutilities/common/validator.py +++ b/configutilities/configutilities/configutilities/common/validator.py @@ -23,6 +23,7 @@ from configutilities.common.utils import is_mtu_valid from configutilities.common.utils import get_service from configutilities.common.utils import get_optional from configutilities.common.utils import validate_address_str +from configutilities.common.utils import validate_nameserver_address_str from configutilities.common.exceptions import ConfigFail from configutilities.common.exceptions import ValidateFail @@ -944,8 +945,25 @@ class ConfigValidator(object): raise ConfigFail("SDN Configuration is no longer supported") def validate_dns(self): - if self.conf.has_section('DNS'): - raise ConfigFail("DNS Configuration is no longer supported") + # DNS config is optional + if not self.conf.has_section('DNS'): + return + if self.cgcs_conf is not None: + self.cgcs_conf.add_section('cDNS') + for x in range(0, 3): + if self.conf.has_option('DNS', 'NAMESERVER_' + str(x + 1)): + dns_address_str = self.conf.get('DNS', 'NAMESERVER_' + str( + x + 1)) + try: + dns_address = validate_nameserver_address_str( + dns_address_str) + if self.cgcs_conf is not None: + self.cgcs_conf.set('cDNS', 'NAMESERVER_' + str(x + 1), + str(dns_address)) + except ValidateFail as e: + raise ConfigFail( + "Invalid DNS NAMESERVER value of %s.\nReason: %s" % + (dns_address_str, e)) def validate_ntp(self): if self.conf.has_section('NTP'): diff --git a/controllerconfig/controllerconfig/controllerconfig/configassistant.py b/controllerconfig/controllerconfig/controllerconfig/configassistant.py index 1a224ba90c..b1c1b5db71 100644 --- a/controllerconfig/controllerconfig/controllerconfig/configassistant.py +++ b/controllerconfig/controllerconfig/controllerconfig/configassistant.py @@ -28,6 +28,7 @@ from configutilities import validate_network_str from configutilities import validate_address_str from configutilities import validate_address from configutilities import ip_version_to_string +from configutilities import validate_nameserver_address_str from configutilities import validate_openstack_password from configutilities import DEFAULT_DOMAIN_NAME from netaddr import IPNetwork @@ -463,6 +464,10 @@ class ConfigAssistant(): # SDN config self.enable_sdn = False + + # DNS config + self.nameserver_addresses = ["8.8.8.8", "8.8.4.4", ""] + # HTTPS self.enable_https = False # Network config @@ -2666,6 +2671,64 @@ class ConfigAssistant(): """ Cluster host interface configuration complete""" self.cluster_host_interface_configured = True + def get_dns_servers(self): + """Produce a comma separated list of DNS servers.""" + servers = [str(s) for s in self.nameserver_addresses if s] + return ",".join(servers) + + def input_dns_config(self): + """Allow user to input DNS config and perform validation.""" + + print("\nDomain Name System (DNS):") + print("-------------------------\n") + print(textwrap.fill( + "Configuring DNS servers accessible through the external " + "OAM network allows domain names to be mapped to IP " + "addresses.", 80)) + print(textwrap.fill( + "The configuration of at least one DNS server is mandatory. To " + "skip the configuration of one or more nameservers (1 to 3 are " + "allowed), enter C to continue to the next configuration item.", + 80)) + print('') + + if self.external_oam_subnet.version == 6: + self.nameserver_addresses = ["2001:4860:4860::8888", + "2001:4860:4860::8844", ""] + + for server in range(0, len(self.nameserver_addresses)): + while True: + user_input = raw_input( + "Nameserver " + str(server + 1) + " [" + + str(self.nameserver_addresses[server]) + "]: ") + if user_input.lower() == 'q': + raise UserQuit + elif user_input.lower() == 'c': + if server == 0: + print("At least one DNS server is required.") + continue + for x in range(server, len(self.nameserver_addresses)): + self.nameserver_addresses[x] = "" + return + elif user_input == "": + user_input = self.nameserver_addresses[server] + # Pressing enter with a blank default will continue + if user_input == "": + return + + try: + try: + ip_input = validate_nameserver_address_str( + user_input, self.external_oam_subnet.version) + except ValidateFail as e: + print('{}'.format(e)) + continue + self.nameserver_addresses[server] = ip_input + break + except (AddrFormatError, ValueError): + print("Invalid address - please enter a valid IPv4 " + "address") + def input_authentication_config(self): """Allow user to input authentication config and perform validation. """ @@ -2711,6 +2774,8 @@ class ConfigAssistant(): self.management_interface_configured = True self.external_oam_interface_configured = True self.default_pxeboot_config() + if not self.kubernetes: + self.nameserver_addresses = ["", "", ""] if utils.is_cpe(): self.system_mode = sysinv_constants.SYSTEM_MODE_DUPLEX @@ -2747,6 +2812,8 @@ class ConfigAssistant(): if self.kubernetes: self.input_cluster_host_config() self.input_external_oam_config() + if self.kubernetes: + self.input_dns_config() self.input_authentication_config() def is_valid_management_multicast_subnet(self, ip_subnet): @@ -3079,6 +3146,19 @@ class ConfigAssistant(): self.external_oam_interface_configured = True + # DNS configuration + if self.kubernetes: + if config.has_section('cDNS'): + self.nameserver_addresses = ["", "", ""] + for x in range(0, len(self.nameserver_addresses)): + if config.has_option('cDNS', + 'NAMESERVER_' + str(x + 1)): + cvalue = config.get('cDNS', + 'NAMESERVER_' + str(x + 1)) + if cvalue != "NC" and cvalue != "": + self.nameserver_addresses[x] = \ + IPAddress(cvalue) + # SDN Network configuration if config.has_option('cSDN', 'ENABLE_SDN'): raise ConfigFail("The option ENABLE_SDN is no longer " @@ -3511,6 +3591,18 @@ class ConfigAssistant(): else: print("External OAM address: " + str(self.external_oam_address_0)) + if self.kubernetes: + print("\nDNS Configuration") + print("-----------------") + dns_config = False + for x in range(0, len(self.nameserver_addresses)): + if self.nameserver_addresses[x]: + print("Nameserver " + str(x + 1) + ": " + + str(self.nameserver_addresses[x])) + dns_config = True + if not dns_config: + print("External DNS servers not configured") + if self.region_config: print("\nRegion Configuration") print("--------------------") @@ -3796,6 +3888,17 @@ class ConfigAssistant(): f.write("EXTERNAL_OAM_1_ADDRESS=" + str(self.external_oam_address_1) + "\n") + if self.kubernetes: + # DNS configuration + f.write("\n[cDNS]") + f.write("\n# DNS Configuration\n") + for x in range(0, len(self.nameserver_addresses)): + if self.nameserver_addresses[x]: + f.write("NAMESERVER_" + str(x + 1) + "=" + + str(self.nameserver_addresses[x]) + "\n") + else: + f.write("NAMESERVER_" + str(x + 1) + "=NC" + "\n") + # Network configuration f.write("\n[cNETWORK]") f.write("\n# Data Network Configuration\n") @@ -5188,6 +5291,17 @@ class ConfigAssistant(): "required_patches": "N/A"} client.sysinv.load.create(**patch) + def _populate_dns_config(self, client): + # Retrieve the list of dns servers to get the uuid + dns_list = client.sysinv.idns.list() + dns_record = dns_list[0] + values = { + 'nameservers': self.get_dns_servers(), + 'action': 'apply' + } + patch = sysinv.dict_to_patch(values) + client.sysinv.idns.update(dns_record.uuid, patch) + def populate_initial_config(self): """Populate initial system inventory configuration""" try: @@ -5195,6 +5309,8 @@ class ConfigAssistant(): self._populate_system_config(client) self._populate_load_config(client) self._populate_network_config(client) + if self.kubernetes: + self._populate_dns_config(client) controller = self._populate_controller_config(client) # ceph_mon config requires controller host to be created self._inventory_config_complete_wait(client, controller) diff --git a/controllerconfig/controllerconfig/controllerconfig/tests/files/cgcs_config.kubernetes b/controllerconfig/controllerconfig/controllerconfig/tests/files/cgcs_config.kubernetes new file mode 100755 index 0000000000..24cb70edd6 --- /dev/null +++ b/controllerconfig/controllerconfig/controllerconfig/tests/files/cgcs_config.kubernetes @@ -0,0 +1,86 @@ +[cSYSTEM] +# System Configuration +SYSTEM_MODE=duplex +TIMEZONE=UTC + +[cPXEBOOT] +# PXEBoot Network Support Configuration +PXECONTROLLER_FLOATING_HOSTNAME=pxecontroller + +[cMGMT] +# Management Network Configuration +MANAGEMENT_INTERFACE_NAME=eth1 +MANAGEMENT_INTERFACE=eth1 +MANAGEMENT_MTU=1500 +MANAGEMENT_SUBNET=192.168.204.0/24 +LAG_MANAGEMENT_INTERFACE=no +CONTROLLER_FLOATING_ADDRESS=192.168.204.2 +CONTROLLER_0_ADDRESS=192.168.204.3 +CONTROLLER_1_ADDRESS=192.168.204.4 +NFS_MANAGEMENT_ADDRESS_1=192.168.204.5 +NFS_MANAGEMENT_ADDRESS_2=192.168.204.6 +CONTROLLER_FLOATING_HOSTNAME=controller +CONTROLLER_HOSTNAME_PREFIX=controller- +OAMCONTROLLER_FLOATING_HOSTNAME=oamcontroller +DYNAMIC_ADDRESS_ALLOCATION=yes +MANAGEMENT_MULTICAST_SUBNET=239.1.1.0/28 + +[cINFRA] +# Infrastructure Network Configuration +INFRASTRUCTURE_INTERFACE_NAME=NC +INFRASTRUCTURE_INTERFACE=NC +INFRASTRUCTURE_VLAN=NC +INFRASTRUCTURE_MTU=NC +INFRASTRUCTURE_SUBNET=NC +LAG_INFRASTRUCTURE_INTERFACE=no +INFRASTRUCTURE_BOND_MEMBER_0=NC +INFRASTRUCTURE_BOND_MEMBER_1=NC +INFRASTRUCTURE_BOND_POLICY=NC +CONTROLLER_0_INFRASTRUCTURE_ADDRESS=NC +CONTROLLER_1_INFRASTRUCTURE_ADDRESS=NC +NFS_INFRASTRUCTURE_ADDRESS_1=NC +STORAGE_0_INFRASTRUCTURE_ADDRESS=NC +STORAGE_1_INFRASTRUCTURE_ADDRESS=NC +CONTROLLER_INFRASTRUCTURE_HOSTNAME_SUFFIX=NC +INFRASTRUCTURE_START_ADDRESS=NC +INFRASTRUCTURE_END_ADDRESS=NC + +[cCLUSTER] +# Cluster Host Network Configuration +CLUSTER_INTERFACE_NAME=eth1 +CLUSTER_INTERFACE=eth1 +CLUSTER_VLAN=NC +CLUSTER_MTU=1500 +CLUSTER_SUBNET=192.168.206.0/24 +LAG_CLUSTER_INTERFACE=no + +[cEXT_OAM] +# External OAM Network Configuration +EXTERNAL_OAM_INTERFACE_NAME=eth0 +EXTERNAL_OAM_INTERFACE=eth0 +EXTERNAL_OAM_VLAN=NC +EXTERNAL_OAM_MTU=1500 +LAG_EXTERNAL_OAM_INTERFACE=no +EXTERNAL_OAM_SUBNET=10.10.10.0/24 +EXTERNAL_OAM_GATEWAY_ADDRESS=10.10.10.1 +EXTERNAL_OAM_FLOATING_ADDRESS=10.10.10.2 +EXTERNAL_OAM_0_ADDRESS=10.10.10.3 +EXTERNAL_OAM_1_ADDRESS=10.10.10.4 + +[cDNS] +# DNS Configuration +NAMESERVER_1=1.2.3.4 +NAMESERVER_2=5.6.7.8 +NAMESERVER_3=NC + +[cNETWORK] +# Data Network Configuration +VSWITCH_TYPE=ovs-dpdk + +[cSECURITY] +[cREGION] +# Region Configuration +REGION_CONFIG=False + +[cAUTHENTICATION] +ADMIN_PASSWORD=Li69nux* diff --git a/controllerconfig/controllerconfig/controllerconfig/tests/files/system_config.cluster b/controllerconfig/controllerconfig/controllerconfig/tests/files/system_config.kubernetes similarity index 94% rename from controllerconfig/controllerconfig/controllerconfig/tests/files/system_config.cluster rename to controllerconfig/controllerconfig/controllerconfig/tests/files/system_config.kubernetes index 6117b7d4b4..d14364b7ca 100755 --- a/controllerconfig/controllerconfig/controllerconfig/tests/files/system_config.cluster +++ b/controllerconfig/controllerconfig/controllerconfig/tests/files/system_config.kubernetes @@ -44,6 +44,11 @@ CIDR=10.10.10.0/24 GATEWAY=10.10.10.1 LOGICAL_INTERFACE=LOGICAL_INTERFACE_2 +[DNS] +# DNS Configuration +NAMESERVER_1=1.2.3.4 +NAMESERVER_2=5.6.7.8 + ;[PXEBOOT_NETWORK] ;PXEBOOT_CIDR=192.168.203.0/24 diff --git a/controllerconfig/controllerconfig/controllerconfig/tests/test_answerfile.py b/controllerconfig/controllerconfig/controllerconfig/tests/test_answerfile.py index e0562d50e7..d87c735caa 100644 --- a/controllerconfig/controllerconfig/controllerconfig/tests/test_answerfile.py +++ b/controllerconfig/controllerconfig/controllerconfig/tests/test_answerfile.py @@ -19,13 +19,14 @@ import controllerconfig.common.constants as constants def _test_answerfile(tmpdir, filename, mock_get_net_device_list, mock_get_rootfs_node, - compare_results=True): + compare_results=True, + ca_options={}): """ Test import and generation of answerfile """ mock_get_net_device_list.return_value = \ ['eth0', 'eth1', 'eth2'] mock_get_rootfs_node.return_value = '/dev/sda' - assistant = ca.ConfigAssistant() + assistant = ca.ConfigAssistant(**ca_options) # Create the path to the answerfile answerfile = os.path.join( @@ -93,3 +94,10 @@ def test_answerfile_region_nuage_vrs(tmpdir): """ Test import of answerfile with region values for nuage_vrs""" _test_answerfile(tmpdir, "cgcs_config.region_nuage_vrs") + + +def test_answerfile_kubernetes(tmpdir): + """ Test import of answerfile with kubernetes values """ + + _test_answerfile(tmpdir, "cgcs_config.kubernetes", + ca_options={"kubernetes": True}) diff --git a/controllerconfig/controllerconfig/controllerconfig/tests/test_system_config.py b/controllerconfig/controllerconfig/controllerconfig/tests/test_system_config.py index eda2692230..22f2224ddf 100644 --- a/controllerconfig/controllerconfig/controllerconfig/tests/test_system_config.py +++ b/controllerconfig/controllerconfig/controllerconfig/tests/test_system_config.py @@ -42,7 +42,7 @@ def _test_system_config(filename): cr.create_cgcs_config_file(None, system_config, None, None, None, 0, validate_only=True) - # Validate the region config file. + # Validate the system config file. # Using onboard validation since the validator's reference version number # is only set at build-time when validating offboard validate(system_config, DEFAULT_CONFIG, None, False) @@ -508,14 +508,6 @@ def test_system_config_validation(): with pytest.raises(exceptions.ConfigFail): validate(system_config, DEFAULT_CONFIG, None, False) - # Test detection of unsupported DNS NAMESERVER - system_config = cr.parse_system_config(simple_systemfile) - system_config.add_section('DNS') - system_config.set('DNS', 'NAMESERVER_1', '8.8.8.8') - with pytest.raises(exceptions.ConfigFail): - cr.create_cgcs_config_file(None, system_config, None, None, None, 0, - validate_only=True) - # Test detection of unsupported NTP NTP_SERVER system_config = cr.parse_system_config(simple_systemfile) system_config.add_section('NTP') @@ -606,12 +598,13 @@ def test_pxeboot_range(): validate(system_config, DEFAULT_CONFIG, None, False) -def test_cluster_network(): - """ Test import of system_config file for cluster network address """ +def test_kubernetes(): + """ Test import of system_config file for kubernetes """ # Create the path to the system_config file systemfile = os.path.join( - os.getcwd(), "controllerconfig/tests/files/", "system_config.cluster") + os.getcwd(), "controllerconfig/tests/files/", + "system_config.kubernetes") # Test import and generation of answer file _test_system_config(systemfile) @@ -647,3 +640,10 @@ def test_cluster_network(): validate_only=True) with pytest.raises(exceptions.ConfigFail): validate(system_config, DEFAULT_CONFIG, None, False) + + # Test absence of optional DNS configuration + system_config = cr.parse_system_config(systemfile) + system_config.remove_section('DNS') + cr.create_cgcs_config_file(None, system_config, None, None, None, 0, + validate_only=True) + validate(system_config, DEFAULT_CONFIG, None, False) diff --git a/puppet-manifests/src/modules/platform/manifests/kubernetes.pp b/puppet-manifests/src/modules/platform/manifests/kubernetes.pp index b6b6a74b15..f30dbd5da6 100644 --- a/puppet-manifests/src/modules/platform/manifests/kubernetes.pp +++ b/puppet-manifests/src/modules/platform/manifests/kubernetes.pp @@ -58,15 +58,8 @@ class platform::kubernetes::master::init # For initial controller install, configure kubernetes from scratch. $resolv_conf = '/etc/resolv.conf' - # Add a DNS server to allow access to kubernetes repo. This will no longer - # be required once we are using our own internal repo. - file_line { "${resolv_conf} nameserver 8.8.8.8": - path => $resolv_conf, - line => 'nameserver 8.8.8.8', - } - # Configure the master node. - -> file { '/etc/kubernetes/kubeadm.yaml': + file { '/etc/kubernetes/kubeadm.yaml': ensure => file, content => template('platform/kubeadm.yaml.erb'), }