diff --git a/configutilities/configutilities/configutilities/common/configobjects.py b/configutilities/configutilities/configutilities/common/configobjects.py index 7f9a0d99f7..91406c6656 100755 --- a/configutilities/configutilities/configutilities/common/configobjects.py +++ b/configutilities/configutilities/configutilities/common/configobjects.py @@ -133,7 +133,8 @@ class Network(object): def parse_config(self, system_config, config_type, network_type, min_addresses=0, multicast_addresses=0, optional=False, - naming_type=DEFAULT_NAMES): + naming_type=DEFAULT_NAMES, + logical_interface_required=True): network_prefix = NETWORK_PREFIX_NAMES[naming_type][network_type] network_name = network_prefix + '_NETWORK' @@ -375,8 +376,10 @@ class Network(object): gateway_address_str, network_name, e)) # Parse/validate the logical interface - logical_interface_name = system_config.get( - network_name, attr_prefix + 'LOGICAL_INTERFACE') - self.logical_interface = LogicalInterface() - self.logical_interface.parse_config(system_config, - logical_interface_name) + if logical_interface_required or system_config.has_option( + network_name, attr_prefix + 'LOGICAL_INTERFACE'): + logical_interface_name = system_config.get( + network_name, attr_prefix + 'LOGICAL_INTERFACE') + self.logical_interface = LogicalInterface() + self.logical_interface.parse_config(system_config, + logical_interface_name) diff --git a/configutilities/configutilities/configutilities/common/validator.py b/configutilities/configutilities/configutilities/common/validator.py index 072742e355..088a9e767d 100755 --- a/configutilities/configutilities/configutilities/common/validator.py +++ b/configutilities/configutilities/configutilities/common/validator.py @@ -69,6 +69,7 @@ class ConfigValidator(object): self.glance_region = None self.system_mode = None self.system_type = None + self.system_dc_role = None def is_simplex_cpe(self): return self.system_mode == SYSTEM_MODE_SIMPLEX @@ -79,6 +80,9 @@ class ConfigValidator(object): def set_system_mode(self, mode): self.system_mode = mode + def set_system_dc_role(self, dc_role): + self.system_dc_role = dc_role + def set_oam_config(self, use_lag, external_oam_interface_name): if self.cgcs_conf is not None: self.cgcs_conf.add_section('cEXT_OAM') @@ -165,6 +169,45 @@ class ConfigValidator(object): except Exception as e: raise ConfigFail("Error parsing configuration file: %s" % e) + def validate_aio_simplex_mgmt(self): + # AIO simplex management network configuration + mgmt_prefix = NETWORK_PREFIX_NAMES[self.naming_type][MGMT_TYPE] + self.mgmt_network = Network() + + min_addresses = 16 + + try: + self.mgmt_network.parse_config(self.conf, self.config_type, + MGMT_TYPE, + min_addresses=min_addresses, + multicast_addresses=0, + naming_type=self.naming_type, + logical_interface_required=False) + + except ConfigFail: + raise + except Exception as e: + raise ConfigFail("Error parsing configuration file: %s" % e) + + if self.mgmt_network.vlan or self.mgmt_network.multicast_cidr or \ + self.mgmt_network.start_end_in_config or \ + self.mgmt_network.floating_address or \ + self.mgmt_network.address_0 or self.mgmt_network.address_1 or \ + self.mgmt_network.dynamic_allocation or \ + self.mgmt_network.gateway_address or \ + self.mgmt_network.logical_interface: + raise ConfigFail("For AIO simplex, only the %s network CIDR can " + "be specified" % mgmt_prefix) + + if self.mgmt_network.cidr.version == 6: + raise ConfigFail("IPv6 management network not supported on " + "simplex configuration.") + + if self.cgcs_conf is not None: + self.cgcs_conf.add_section('cMGMT') + self.cgcs_conf.set('cMGMT', 'MANAGEMENT_SUBNET', + self.mgmt_network.cidr) + def validate_aio_network(self, subcloud=False): if not subcloud: # AIO-SX subcloud supports MGMT_NETWORK & PXEBOOT_NETWORK @@ -172,8 +215,7 @@ class ConfigValidator(object): raise ConfigFail("PXEBoot Network configuration is not " "supported.") if self.conf.has_section('MGMT_NETWORK'): - raise ConfigFail("Management Network configuration is not " - "supported.") + self.validate_aio_simplex_mgmt() if self.conf.has_section('INFRA_NETWORK'): raise ConfigFail("Infrastructure Network configuration is not " "supported.") @@ -192,6 +234,17 @@ class ConfigValidator(object): "No gateway specified - %s_GATEWAY must be specified" % oam_prefix) + # Check overlap with management network + if self.mgmt_network is not None: + try: + self.configured_networks.append(self.mgmt_network.cidr) + check_network_overlap(self.oam_network.cidr, + self.configured_networks) + except ValidateFail: + raise ConfigFail("%s CIDR %s overlaps with another configured " + "network" % + (oam_prefix, str(self.mgmt_network.cidr))) + self.set_oam_config(use_lag, external_oam_interface_name) def validate_version(self): diff --git a/configutilities/configutilities/configutilities/configfiletool.py b/configutilities/configutilities/configutilities/configfiletool.py index ef9589309a..5b875a528a 100755 --- a/configutilities/configutilities/configutilities/configfiletool.py +++ b/configutilities/configutilities/configutilities/configfiletool.py @@ -288,6 +288,10 @@ class ConfigPage(WizardPage): if mode: validator.set_system_mode(mode) + dc_role = get_opt('SYSTEM', 'DISTRIBUTED_CLOUD_ROLE') + if dc_role: + validator.set_system_dc_role(dc_role) + for method in self.validator_methods: getattr(validator, method)() @@ -808,14 +812,12 @@ class SYSTEMPage(ConfigPage): event.Skip() def skip_not_required_pages(self, skip): - # Skip PXEBOOT, MGMT, BMC and INFRA pages + # Skip PXEBOOT, BMC and INFRA pages self.parent.skip_page(PXEBootPage, skip) - self.parent.skip_page(MGMTPage, skip) self.parent.skip_page(INFRAPage, skip) # Remove the sections that are not required config.remove_section("PXEBOOT_NETWORK") - config.remove_section("MGMT_NETWORK") config.remove_section("BOARD_MANAGEMENT_NETWORK") config.remove_section("INFRA_NETWORK") @@ -894,117 +896,134 @@ class MGMTPage(ConfigPage): LINK_SPEED_10G, LINK_SPEED_25G] self.section = "MGMT_NETWORK" - self.validator_methods = ["validate_pxeboot", "validate_mgmt"] + if get_opt('SYSTEM', 'SYSTEM_MODE') != 'simplex': + self.validator_methods = ["validate_pxeboot", "validate_mgmt"] + self.help_text = ( + "The management network is used for internal communication " + "between platform components. IP addresses on this network " + "are reachable only within the data center.") + else: + self.validator_methods = ["validate_aio_simplex_mgmt"] + self.help_text = ( + "The management network is used for internal communication " + "between platform components. IP addresses on this network " + "are reachable only within the host.") self.title = "Management Network" - self.help_text = ( - "The management network is used for internal communication " - "between platform components. IP addresses on this network " - "are reachable only within the data center.") self.set_fields() self.do_setup() self.bind_events() def set_fields(self): - self.fields['mgmt_port1'] = Field( - text="Management interface", - type=TYPES.string, - initial="enp0s8", - transient=True - ) - self.fields['lag_help'] = Field( - text="A management bond interface provides redundant " - "connections for the management network. When selected, the " - "field above specifies the first member of the bond.", - type=TYPES.help, - ) - self.fields['LAG_INTERFACE'] = Field( - text="Use management interface link aggregation", - type=TYPES.checkbox, - shows=["LAG_MODE", "mgmt_port2"], - transient=True - ) - self.fields['LAG_MODE'] = Field( - text="Management interface bonding policy", - type=TYPES.choice, - choices=self.lag_choices.keys(), - transient=True - ) - self.fields['mgmt_port2'] = Field( - text="Second management interface member", - type=TYPES.string, - initial="", - transient=True - ) - self.fields['INTERFACE_MTU'] = Field( - text="Management interface MTU", - type=TYPES.int, - initial="1500", - transient=True - ) - self.fields['INTERFACE_LINK_CAPACITY'] = Field( - text="Management interface link capacity Mbps", - type=TYPES.choice, - choices=self.mgmt_speed_choices, - initial=self.mgmt_speed_choices[0], - transient=True - ) - if config.has_option('PXEBOOT_NETWORK', 'PXEBOOT_CIDR') or \ - config.has_option('REGION2_PXEBOOT_NETWORK', 'PXEBOOT_CIDR'): - self.fields['vlan_help'] = Field( - text=("A management VLAN is required because a separate " - "PXEBoot network was configured on the management " - "interface."), - type=TYPES.help + if get_opt('SYSTEM', 'SYSTEM_MODE') != 'simplex': + self.fields['mgmt_port1'] = Field( + text="Management interface", + type=TYPES.string, + initial="enp0s8", + transient=True ) - self.fields['VLAN'] = Field( - text="Management VLAN Identifier", - type=TYPES.int, + self.fields['lag_help'] = Field( + text="A management bond interface provides redundant " + "connections for the management network. When selected, " + "the field above specifies the first member of the bond.", + type=TYPES.help, + ) + self.fields['LAG_INTERFACE'] = Field( + text="Use management interface link aggregation", + type=TYPES.checkbox, + shows=["LAG_MODE", "mgmt_port2"], + transient=True + ) + self.fields['LAG_MODE'] = Field( + text="Management interface bonding policy", + type=TYPES.choice, + choices=self.lag_choices.keys(), + transient=True + ) + self.fields['mgmt_port2'] = Field( + text="Second management interface member", + type=TYPES.string, initial="", + transient=True ) - self.fields['CIDR'] = Field( - text="Management subnet", - type=TYPES.string, - initial="192.168.204.0/24", - ) - self.fields['MULTICAST_CIDR'] = Field( - text="Management multicast subnet", - type=TYPES.string, - initial='239.1.1.0/28' - ) + self.fields['INTERFACE_MTU'] = Field( + text="Management interface MTU", + type=TYPES.int, + initial="1500", + transient=True + ) + self.fields['INTERFACE_LINK_CAPACITY'] = Field( + text="Management interface link capacity Mbps", + type=TYPES.choice, + choices=self.mgmt_speed_choices, + initial=self.mgmt_speed_choices[0], + transient=True + ) + if config.has_option('PXEBOOT_NETWORK', 'PXEBOOT_CIDR') or \ + config.has_option('REGION2_PXEBOOT_NETWORK', + 'PXEBOOT_CIDR'): + self.fields['vlan_help'] = Field( + text=("A management VLAN is required because a separate " + "PXEBoot network was configured on the management " + "interface."), + type=TYPES.help + ) + self.fields['VLAN'] = Field( + text="Management VLAN Identifier", + type=TYPES.int, + initial="", + ) - # Start/end ranges - self.fields['use_entire_subnet'] = Field( - text="Restrict management subnet address range", - type=TYPES.checkbox, - shows=["IP_START_ADDRESS", "IP_END_ADDRESS"], - transient=True - ) - self.fields['IP_START_ADDRESS'] = Field( - text="Management network start address", - type=TYPES.string, - initial="192.168.204.2", - ) - self.fields['IP_END_ADDRESS'] = Field( - text="Management network end address", - type=TYPES.string, - initial="192.168.204.254", - ) + self.fields['CIDR'] = Field( + text="Management subnet", + type=TYPES.string, + initial="192.168.204.0/24", + ) - # Dynamic addressing - self.fields['dynamic_help'] = Field( - text=( - "IP addresses can be assigned to hosts dynamically or " - "a static IP address can be specified for each host. " - "Note: This choice applies to both the management network " - "and infrastructure network."), - type=TYPES.help, - ) - self.fields['DYNAMIC_ALLOCATION'] = Field( - text="Use dynamic IP address allocation", - type=TYPES.checkbox, - initial='Y' - ) + self.fields['MULTICAST_CIDR'] = Field( + text="Management multicast subnet", + type=TYPES.string, + initial='239.1.1.0/28' + ) + + # Start/end ranges + self.fields['use_entire_subnet'] = Field( + text="Restrict management subnet address range", + type=TYPES.checkbox, + shows=["IP_START_ADDRESS", "IP_END_ADDRESS"], + transient=True + ) + self.fields['IP_START_ADDRESS'] = Field( + text="Management network start address", + type=TYPES.string, + initial="192.168.204.2", + ) + self.fields['IP_END_ADDRESS'] = Field( + text="Management network end address", + type=TYPES.string, + initial="192.168.204.254", + ) + + # Dynamic addressing + self.fields['dynamic_help'] = Field( + text=( + "IP addresses can be assigned to hosts dynamically or " + "a static IP address can be specified for each host. " + "Note: This choice applies to both the management network " + "and infrastructure network."), + type=TYPES.help, + ) + self.fields['DYNAMIC_ALLOCATION'] = Field( + text="Use dynamic IP address allocation", + type=TYPES.checkbox, + initial='Y' + ) + else: + self.fields['CIDR'] = Field( + text="Management subnet", + type=TYPES.string, + initial="192.168.204.0/28", + ) def validate_page(self): super(MGMTPage, self).validate_page() @@ -1013,19 +1032,21 @@ class MGMTPage(ConfigPage): def get_config(self): super(MGMTPage, self).get_config() - # Add logical interface - ports = self.fields['mgmt_port1'].get_value() - if self.fields['mgmt_port2'].get_value(): - ports += "," + self.fields['mgmt_port2'].get_value() - li = create_li( - lag=self.fields['LAG_INTERFACE'].get_value(), - mode=self.lag_choices.get(self.fields['LAG_MODE'].get_value()), - mtu=self.fields['INTERFACE_MTU'].get_value(), - link_capacity=self.fields['INTERFACE_LINK_CAPACITY'].get_value(), - ports=ports - ) - config.set(self.section, 'LOGICAL_INTERFACE', li) - clean_lis() + if get_opt('SYSTEM', 'SYSTEM_MODE') != 'simplex': + # Add logical interface + ports = self.fields['mgmt_port1'].get_value() + if self.fields['mgmt_port2'].get_value(): + ports += "," + self.fields['mgmt_port2'].get_value() + li = create_li( + lag=self.fields['LAG_INTERFACE'].get_value(), + mode=self.lag_choices.get(self.fields['LAG_MODE'].get_value()), + mtu=self.fields['INTERFACE_MTU'].get_value(), + link_capacity=self.fields[ + 'INTERFACE_LINK_CAPACITY'].get_value(), + ports=ports + ) + config.set(self.section, 'LOGICAL_INTERFACE', li) + clean_lis() class INFRAPage(ConfigPage): diff --git a/controllerconfig/controllerconfig/controllerconfig/common/constants.py b/controllerconfig/controllerconfig/controllerconfig/common/constants.py index 5a218a580e..54dba506d8 100644 --- a/controllerconfig/controllerconfig/controllerconfig/common/constants.py +++ b/controllerconfig/controllerconfig/controllerconfig/common/constants.py @@ -87,7 +87,7 @@ LOOPBACK_IFNAME = 'lo' DEFAULT_MULTICAST_SUBNET_IPV4 = '239.1.1.0/28' DEFAULT_MULTICAST_SUBNET_IPV6 = 'ff08::1:1:0/124' -DEFAULT_MGMT_ON_LOOPBACK_SUBNET_IPV4 = '127.168.204.0/24' +DEFAULT_MGMT_ON_LOOPBACK_SUBNET_IPV4 = '192.168.204.0/28' DEFAULT_REGION_NAME = "RegionOne" DEFAULT_SERVICE_PROJECT_NAME = "services" diff --git a/controllerconfig/controllerconfig/controllerconfig/configassistant.py b/controllerconfig/controllerconfig/controllerconfig/configassistant.py index 55486527b1..b29eaa1564 100644 --- a/controllerconfig/controllerconfig/controllerconfig/configassistant.py +++ b/controllerconfig/controllerconfig/controllerconfig/configassistant.py @@ -1322,13 +1322,47 @@ class ConfigAssistant(): """ Management interface configuration complete""" self.management_interface_configured = True - def populate_aio_management_config(self): - """Populate management on aio interface config.""" + def input_aio_simplex_management_config(self, management_subnet=None): + """Allow user to input AIO simplex management config and perform + validation.""" + + if management_subnet is not None: + self.management_subnet = management_subnet + else: + print "\nManagement Network:" + print "-------------------\n" + + print textwrap.fill( + "The management network is used for internal communication " + "between platform components. IP addresses on this network " + "are reachable only within the host.", 80) + print + + self.management_subnet = IPNetwork( + constants.DEFAULT_MGMT_ON_LOOPBACK_SUBNET_IPV4) + min_addresses = 16 + while True: + user_input = input("Management subnet [" + + str(self.management_subnet) + "]: ") + if user_input.lower() == 'q': + raise UserQuit + elif user_input == "": + user_input = self.management_subnet + + try: + tmp_management_subnet = validate_network_str(user_input, + min_addresses) + if tmp_management_subnet.version == 6: + print ("IPv6 management network not supported on " + + "simplex configuration") + continue + self.management_subnet = tmp_management_subnet + break + except ValidateFail as e: + print "{}".format(e) self.management_interface = constants.LOOPBACK_IFNAME self.management_interface_name = constants.LOOPBACK_IFNAME - self.management_subnet = IPNetwork( - constants.DEFAULT_MGMT_ON_LOOPBACK_SUBNET_IPV4) self.management_start_address = self.management_subnet[2] self.management_end_address = self.management_subnet[-2] self.controller_floating_address = self.management_start_address @@ -2307,7 +2341,7 @@ class ConfigAssistant(): self.check_storage_config() if self.system_mode == sysinv_constants.SYSTEM_MODE_SIMPLEX: self.default_pxeboot_config() - self.populate_aio_management_config() + self.input_aio_simplex_management_config() else: # An AIO system cannot function as a Distributed Cloud System # Controller @@ -2425,9 +2459,16 @@ class ConfigAssistant(): # Management network configuration if self.system_mode == sysinv_constants.SYSTEM_MODE_SIMPLEX and \ not self.subcloud_config(): - # For AIO-SX subcloud, mgmt n/w will be on a separate - # physical interface instead of the loopback interface. - self.populate_aio_management_config() + # For AIO-SX, only the management subnet is configurable + # (unless this is a subcloud). + if config.has_option('cMGMT', 'MANAGEMENT_SUBNET'): + management_subnet = IPNetwork(config.get( + 'cMGMT', 'MANAGEMENT_SUBNET')) + else: + management_subnet = IPNetwork( + constants.DEFAULT_MGMT_ON_LOOPBACK_SUBNET_IPV4) + self.input_aio_simplex_management_config( + management_subnet=management_subnet) else: self.management_interface_name = config.get( 'cMGMT', 'MANAGEMENT_INTERFACE_NAME') diff --git a/controllerconfig/controllerconfig/controllerconfig/tests/files/system_config.simplex_mgmt b/controllerconfig/controllerconfig/controllerconfig/tests/files/system_config.simplex_mgmt new file mode 100644 index 0000000000..c8c204d8d7 --- /dev/null +++ b/controllerconfig/controllerconfig/controllerconfig/tests/files/system_config.simplex_mgmt @@ -0,0 +1,25 @@ +[LOGICAL_INTERFACE_1] +LAG_INTERFACE=N +;LAG_MODE= +INTERFACE_MTU=1500 +;INTERFACE_LINK_CAPACITY= +INTERFACE_PORTS=eth0 + +[MGMT_NETWORK] +CIDR=192.168.42.0/28 + +[OAM_NETWORK] +IP_ADDRESS=10.10.10.20 +CIDR=10.10.10.0/24 +GATEWAY=10.10.10.1 +LOGICAL_INTERFACE=LOGICAL_INTERFACE_1 + +[AUTHENTICATION] +ADMIN_PASSWORD=Li69nux* + +[VERSION] +RELEASE = TEST.SW.VERSION + +[SYSTEM] +SYSTEM_TYPE=All-in-one +SYSTEM_MODE=simplex diff --git a/controllerconfig/controllerconfig/controllerconfig/tests/test_system_config.py b/controllerconfig/controllerconfig/controllerconfig/tests/test_system_config.py index c179e7f1f9..c0a9b8fbee 100644 --- a/controllerconfig/controllerconfig/controllerconfig/tests/test_system_config.py +++ b/controllerconfig/controllerconfig/controllerconfig/tests/test_system_config.py @@ -107,6 +107,62 @@ def test_system_config_simplex(): _test_system_config(systemfile) +def test_system_config_simplex_mgmt(): + """ Test import of system_config file for AIO-simplex with management + configuration""" + + # Create the path to the system_config file + systemfile = os.path.join( + os.getcwd(), "controllerconfig/tests/files/", + "system_config.simplex_mgmt") + + _test_system_config(systemfile) + + # Test MGMT_NETWORK parameters that are not allowed + system_config = cr.parse_system_config(systemfile) + system_config.set('MGMT_NETWORK', 'GATEWAY', '192.168.42.1') + with pytest.raises(exceptions.ConfigFail): + cr.create_cgcs_config_file(None, system_config, None, None, None, 0, + validate_only=True) + with pytest.raises(exceptions.ConfigFail): + validate(system_config, DEFAULT_CONFIG, None, False) + system_config = cr.parse_system_config(systemfile) + system_config.set('MGMT_NETWORK', 'LOGICAL_INTERFACE', + 'LOGICAL_INTERFACE_1') + with pytest.raises(exceptions.ConfigFail): + cr.create_cgcs_config_file(None, system_config, None, None, None, 0, + validate_only=True) + with pytest.raises(exceptions.ConfigFail): + validate(system_config, DEFAULT_CONFIG, None, False) + + # Test overlap with OAM network + system_config = cr.parse_system_config(systemfile) + system_config.set('MGMT_NETWORK', 'CIDR', '10.10.10.0/24') + with pytest.raises(exceptions.ConfigFail): + cr.create_cgcs_config_file(None, system_config, None, None, None, 0, + validate_only=True) + with pytest.raises(exceptions.ConfigFail): + validate(system_config, DEFAULT_CONFIG, None, False) + + # Test IPv6 management CIDR (not supported) + system_config = cr.parse_system_config(systemfile) + system_config.set('MGMT_NETWORK', 'CIDR', 'FD01::0000/64') + with pytest.raises(exceptions.ConfigFail): + cr.create_cgcs_config_file(None, system_config, None, None, None, 0, + validate_only=True) + with pytest.raises(exceptions.ConfigFail): + validate(system_config, DEFAULT_CONFIG, None, False) + + # Test management CIDR that is too small + system_config = cr.parse_system_config(systemfile) + system_config.set('MGMT_NETWORK', 'CIDR', '192.168.42.0/29') + with pytest.raises(exceptions.ConfigFail): + cr.create_cgcs_config_file(None, system_config, None, None, None, 0, + validate_only=True) + with pytest.raises(exceptions.ConfigFail): + validate(system_config, DEFAULT_CONFIG, None, False) + + def test_system_config_validation(): """ Test detection of various errors in system_config file """ diff --git a/puppet-manifests/src/modules/platform/manifests/network.pp b/puppet-manifests/src/modules/platform/manifests/network.pp index e1f9c56840..9c164ed94e 100644 --- a/puppet-manifests/src/modules/platform/manifests/network.pp +++ b/puppet-manifests/src/modules/platform/manifests/network.pp @@ -86,10 +86,20 @@ define network_address ( $address, $ifname, ) { + # In AIO simplex configurations, the management addresses are assigned to the + # loopback interface. These addresses must be assigned using the host scope + # or assignment is prevented (can't have multiple global scope addresses on + # the loopback interface). + if $ifname == 'lo' { + $options = 'scope host' + } else { + $options = '' + } + # addresses should only be configured if running in simplex, otherwise SM # will configure them on the active controller. exec { "Configuring ${name} IP address": - command => "ip addr replace ${address} dev ${ifname}", + command => "ip addr replace ${address} dev ${ifname} ${options}", onlyif => "test -f /etc/platform/simplex", } } diff --git a/sysinv/sysinv/sysinv/sysinv/puppet/nfv.py b/sysinv/sysinv/sysinv/sysinv/puppet/nfv.py index e4740e1811..49592314d6 100644 --- a/sysinv/sysinv/sysinv/sysinv/puppet/nfv.py +++ b/sysinv/sysinv/sysinv/sysinv/puppet/nfv.py @@ -181,6 +181,13 @@ class NfvPuppet(openstack.OpenstackBasePuppet): nova_oslo_messaging_data['password'], } config.update(rabbit_config) + + # Listen to nova api proxy on management address + nova_api_proxy_config = { + 'nfv::nfvi::compute_rest_api_host': + self._get_management_address(), + } + config.update(nova_api_proxy_config) else: # The openstack auth info is still required as the VIM will # audit some keystone entities (e.g. tenants). Point it to