From 5f79056bf253c3d087b26c0298ba5e3a4eeacd88 Mon Sep 17 00:00:00 2001 From: Bin Qian Date: Thu, 13 Dec 2018 14:39:35 -0500 Subject: [PATCH] Enable PXEBoot address range options in config_controller 1. Add steps to config_controller wizard for PXEBoot address range options 2. Add optional PXEBoot address range config to config_controller config file Story: 2004584 Task: 28390 Change-Id: I2f16b08b6dbc6e19ab265d92b372e8053d1996e8 Signed-off-by: Bin Qian --- .../configutilities/common/validator.py | 70 +++++++++- .../controllerconfig/configassistant.py | 124 ++++++++++++++++-- .../tests/files/system_config.pxeboot | 57 ++++++++ .../tests/test_system_config.py | 35 +++++ 4 files changed, 269 insertions(+), 17 deletions(-) create mode 100755 controllerconfig/controllerconfig/controllerconfig/tests/files/system_config.pxeboot diff --git a/configutilities/configutilities/configutilities/common/validator.py b/configutilities/configutilities/configutilities/common/validator.py index dffc20f28f..481f06d917 100755 --- a/configutilities/configutilities/configutilities/common/validator.py +++ b/configutilities/configutilities/configutilities/common/validator.py @@ -10,7 +10,7 @@ from configobjects import DEFAULT_NAMES, NETWORK_PREFIX_NAMES, OAM_TYPE, \ from netaddr import IPRange from utils import lag_mode_to_str, validate_network_str, \ check_network_overlap, is_mtu_valid, is_speed_valid, get_service, \ - get_optional + get_optional, validate_address_str from exceptions import ConfigFail, ValidateFail @@ -359,6 +359,8 @@ class ConfigValidator(object): def validate_pxeboot(self): # PXEBoot network configuration + start_end_in_config = False + if self.config_type in [REGION_CONFIG, SUBCLOUD_CONFIG]: self.pxeboot_section_name = 'REGION2_PXEBOOT_NETWORK' else: @@ -373,6 +375,51 @@ class ConfigValidator(object): raise ValidateFail("Invalid PXEBOOT_NETWORK IP version - " "only IPv4 supported") self.configured_networks.append(pxeboot_subnet) + pxeboot_start_address = None + pxeboot_end_address = None + if self.conf.has_option(self.pxeboot_section_name, + "IP_START_ADDRESS"): + start_addr_str = self.conf.get(self.pxeboot_section_name, + "IP_START_ADDRESS") + pxeboot_start_address = validate_address_str( + start_addr_str, pxeboot_subnet + ) + + if self.conf.has_option(self.pxeboot_section_name, + "IP_END_ADDRESS"): + end_addr_str = self.conf.get(self.pxeboot_section_name, + "IP_END_ADDRESS") + pxeboot_end_address = validate_address_str( + end_addr_str, pxeboot_subnet + ) + + if pxeboot_start_address or pxeboot_end_address: + if not pxeboot_end_address: + raise ConfigFail("Missing attribute %s for %s" % + ('IP_END_ADDRESS', + self.pxeboot_section_name)) + + if not pxeboot_start_address: + raise ConfigFail("Missing attribute %s for %s" % + ('IP_START_ADDRESS', + self.pxeboot_section_name)) + + if not pxeboot_start_address < pxeboot_end_address: + raise ConfigFail("Start address %s not " + "less than end address %s for %s." + % (start_addr_str, + end_addr_str, + self.pxeboot_section_name)) + + min_addresses = 8 + if not IPRange(start_addr_str, end_addr_str).size >= \ + min_addresses: + raise ConfigFail("Address range for %s must contain " + "at least %d addresses." % + (self.pxeboot_section_name, + min_addresses)) + start_end_in_config = True + self.pxeboot_network_configured = True except ValidateFail as e: raise ConfigFail("Invalid PXEBOOT_CIDR value of %s for %s." @@ -385,13 +432,28 @@ class ConfigValidator(object): if self.pxeboot_network_configured: self.cgcs_conf.set('cPXEBOOT', 'PXEBOOT_SUBNET', str(pxeboot_subnet)) + if start_end_in_config: + self.cgcs_conf.set("cPXEBOOT", + "PXEBOOT_START_ADDRESS", + start_addr_str) + self.cgcs_conf.set("cPXEBOOT", + "PXEBOOT_END_ADDRESS", + end_addr_str) + + pxeboot_floating_addr = pxeboot_start_address + pxeboot_controller_addr_0 = pxeboot_start_address + 1 + pxeboot_controller_addr_1 = pxeboot_controller_addr_0 + 1 + else: + pxeboot_floating_addr = pxeboot_subnet[2] + pxeboot_controller_addr_0 = pxeboot_subnet[3] + pxeboot_controller_addr_1 = pxeboot_subnet[4] self.cgcs_conf.set('cPXEBOOT', 'CONTROLLER_PXEBOOT_FLOATING_ADDRESS', - str(pxeboot_subnet[2])) + str(pxeboot_floating_addr)) self.cgcs_conf.set('cPXEBOOT', 'CONTROLLER_PXEBOOT_ADDRESS_0', - str(pxeboot_subnet[3])) + str(pxeboot_controller_addr_0)) self.cgcs_conf.set('cPXEBOOT', 'CONTROLLER_PXEBOOT_ADDRESS_1', - str(pxeboot_subnet[4])) + str(pxeboot_controller_addr_1)) self.cgcs_conf.set('cPXEBOOT', 'PXECONTROLLER_FLOATING_HOSTNAME', 'pxecontroller') diff --git a/controllerconfig/controllerconfig/controllerconfig/configassistant.py b/controllerconfig/controllerconfig/controllerconfig/configassistant.py index e309e893a0..423b9ee5f3 100644 --- a/controllerconfig/controllerconfig/controllerconfig/configassistant.py +++ b/controllerconfig/controllerconfig/controllerconfig/configassistant.py @@ -58,8 +58,16 @@ def timestamped(dname, fmt='{dname}_%Y-%m-%d-%H-%M-%S'): def prompt_for(prompt_text, default_input, validator): - valid = False - while not valid: + """ + :param prompt_text: text for the prompt + :param default_input: default input if user hit enter directly + :param validator: validator function to validate user input, + validator should return error message in case + of invalid input, or None if input is valid. + :return: return a valid user input + """ + error_msg = None + while True: user_input = input(prompt_text) if user_input.lower() == 'q': raise UserQuit @@ -67,12 +75,12 @@ def prompt_for(prompt_text, default_input, validator): user_input = default_input if validator: - valid = validator(user_input) - else: - valid = True + error_msg = validator(user_input) - if not valid: - print("Invalid choice") + if error_msg is not None: + print(error_msg) + else: + break return user_input @@ -345,6 +353,9 @@ class ConfigAssistant(): self.controller_pxeboot_hostname_suffix = "-pxeboot" self.private_pxeboot_subnet = IPNetwork("169.254.202.0/24") self.pxecontroller_floating_hostname = "pxecontroller" + self.pxeboot_start_address = None + self.pxeboot_end_address = None + self.use_entire_pxeboot_subnet = True # Management network config self.management_interface_configured = False @@ -668,7 +679,8 @@ class ConfigAssistant(): } user_input = prompt_for( "System mode [duplex-direct]: ", '1', - lambda text: text in value_mapping + lambda text: "Invalid choice" if text not in value_mapping + else None ) self.system_mode = value_mapping[user_input.lower()] @@ -683,7 +695,8 @@ class ConfigAssistant(): } user_input = prompt_for( "Configure Distributed Cloud System Controller [y/N]: ", 'n', - lambda text: text in value_mapping + lambda text: "Invalid choice" if text not in value_mapping + else None ) self.system_dc_role = value_mapping[user_input.lower()] @@ -781,6 +794,9 @@ class ConfigAssistant(): self.controller_pxeboot_address_1 = \ IPAddress(self.pxeboot_subnet[4]) + self.pxeboot_start_address = self.pxeboot_subnet[2] + self.pxeboot_end_address = self.pxeboot_subnet[-2] + def input_pxeboot_config(self): """Allow user to input pxeboot config and perform validation.""" @@ -846,12 +862,77 @@ class ConfigAssistant(): break except AddrFormatError: print("Invalid subnet - please enter a valid IPv4 subnet") + + value_mapping = { + "y": True, + "n": False, + } + + user_input = prompt_for( + "Use entire PXEBoot subnet [Y/n]: ", 'Y', + lambda text: "Invalid choice" + if text.lower() not in value_mapping + else None + ) + self.use_entire_pxeboot_subnet = value_mapping[user_input.lower()] + + if not self.use_entire_pxeboot_subnet: + def validate_input_address(text, error_header): + try: + validate_address_str(text, self.pxeboot_subnet) + return None + except ValidateFail as e: + return "%s\n Reason: %s" % (error_header, e) + + while True: + self.pxeboot_start_address = self.pxeboot_subnet[2] + self.pxeboot_end_address = self.pxeboot_subnet[-2] + input_str = prompt_for( + "PXEBoot network start address [" + + str(self.pxeboot_start_address) + + "]: ", str(self.pxeboot_start_address), + lambda text: validate_input_address( + text, "Invalid start address.") + ) + self.pxeboot_start_address = IPAddress(input_str) + + input_str = prompt_for( + "PXEBoot network end address [" + + str(self.pxeboot_end_address) + + "]: ", str(self.pxeboot_end_address), + lambda text: validate_input_address( + text, "Invalid end address.") + ) + self.pxeboot_end_address = IPAddress(input_str) + + if not self.pxeboot_start_address < \ + self.pxeboot_end_address: + print("Start address not less than end address. ") + continue + + address_range = IPRange( + str(self.pxeboot_start_address), + str(self.pxeboot_end_address)) + + min_addresses = 8 + if not address_range.size >= min_addresses: + print( + "Address range must contain at least " + "%d addresses." % min_addresses) + continue + + print('') + break + else: + self.pxeboot_start_address = self.pxeboot_subnet[2] + self.pxeboot_end_address = self.pxeboot_subnet[-2] else: # Use private subnet for pxe booting self.pxeboot_subnet = self.private_pxeboot_subnet + self.pxeboot_start_address = self.pxeboot_subnet[2] + self.pxeboot_end_address = self.pxeboot_subnet[-2] - default_controller_pxeboot_float_ip = self.pxeboot_subnet[2] - ip_input = IPAddress(default_controller_pxeboot_float_ip) + ip_input = self.pxeboot_start_address if not self.is_valid_pxeboot_address(ip_input): raise ConfigFail("Unable to create controller PXEBoot " "floating address") @@ -2443,6 +2524,19 @@ class ConfigAssistant(): self.separate_pxeboot_network = True self.pxeboot_subnet = IPNetwork(config.get( 'cPXEBOOT', 'PXEBOOT_SUBNET')) + if config.has_option('cPXEBOOT', 'PXEBOOT_START_ADDRESS'): + self.pxeboot_start_address = IPAddress(config.get( + 'cPXEBOOT', 'PXEBOOT_START_ADDRESS')) + if config.has_option('cPXEBOOT', 'PXEBOOT_END_ADDRESS'): + self.pxeboot_end_address = IPAddress(config.get( + 'cPXEBOOT', 'PXEBOOT_END_ADDRESS')) + if not self.pxeboot_start_address and \ + not self.pxeboot_end_address: + self.pxeboot_start_address = self.pxeboot_subnet[2] + self.pxeboot_end_address = self.pxeboot_subnet[-2] + self.use_entire_pxeboot_subnet = True + else: + self.use_entire_pxeboot_subnet = False self.controller_pxeboot_address_0 = IPAddress(config.get( 'cPXEBOOT', 'CONTROLLER_PXEBOOT_ADDRESS_0')) self.controller_pxeboot_address_1 = IPAddress(config.get( @@ -2955,6 +3049,10 @@ class ConfigAssistant(): str(self.controller_pxeboot_address_0)) print("Controller 1 PXEBoot address: " + str(self.controller_pxeboot_address_1)) + if not self.use_entire_pxeboot_subnet: + print("PXEBoot start address: " + + str(self.pxeboot_start_address)) + print("PXEBoot end address: " + str(self.pxeboot_end_address)) print("PXEBoot Controller floating hostname: " + str(self.pxecontroller_floating_hostname)) @@ -4103,8 +4201,8 @@ class ConfigAssistant(): 'name': 'pxeboot', 'network': str(self.pxeboot_subnet.network), 'prefix': self.pxeboot_subnet.prefixlen, - 'ranges': [(str(self.pxeboot_subnet[2]), - str(self.pxeboot_subnet[-2]))], + 'ranges': [(str(self.pxeboot_start_address), + str(self.pxeboot_end_address))], } pool = client.sysinv.address_pool.create(**values) diff --git a/controllerconfig/controllerconfig/controllerconfig/tests/files/system_config.pxeboot b/controllerconfig/controllerconfig/controllerconfig/tests/files/system_config.pxeboot new file mode 100755 index 0000000000..a636bd76a9 --- /dev/null +++ b/controllerconfig/controllerconfig/controllerconfig/tests/files/system_config.pxeboot @@ -0,0 +1,57 @@ +[LOGICAL_INTERFACE_1] +LAG_INTERFACE=N +INTERFACE_MTU=1500 +INTERFACE_LINK_CAPACITY=1000 +INTERFACE_PORTS=eth1 + +[LOGICAL_INTERFACE_2] +LAG_INTERFACE=N +;LAG_MODE= +INTERFACE_MTU=1500 +;INTERFACE_LINK_CAPACITY= +INTERFACE_PORTS=eth0 + +[PXEBOOT_NETWORK] +PXEBOOT_CIDR=192.168.102.0/24 +IP_START_ADDRESS=192.168.102.32 +IP_END_ADDRESS=192.168.102.54 + +[MGMT_NETWORK] +VLAN=123 +CIDR=192.168.204.0/24 +MULTICAST_CIDR=239.1.1.0/28 +DYNAMIC_ALLOCATION=Y +LOGICAL_INTERFACE=LOGICAL_INTERFACE_1 + +;[INFRA_NETWORK] +;VLAN=124 +;IP_START_ADDRESS=192.168.205.102 +;IP_END_ADDRESS=192.168.205.199 +;DYNAMIC_ALLOCATION=Y +;CIDR=192.168.205.0/24 +;LOGICAL_INTERFACE=LOGICAL_INTERFACE_1 + +[OAM_NETWORK] +;VLAN= +;IP_START_ADDRESS=10.10.10.2 +;IP_END_ADDRESS=10.10.10.4 +IP_FLOATING_ADDRESS=10.10.10.20 +IP_UNIT_0_ADDRESS=10.10.10.30 +IP_UNIT_1_ADDRESS=10.10.10.40 +CIDR=10.10.10.0/24 +GATEWAY=10.10.10.1 +LOGICAL_INTERFACE=LOGICAL_INTERFACE_2 + +;[PXEBOOT_NETWORK] +;PXEBOOT_CIDR=192.168.203.0/24 + +;[BOARD_MANAGEMENT_NETWORK] +;VLAN=1 +;MTU=1496 +;SUBNET=192.168.203.0/24 + +[AUTHENTICATION] +ADMIN_PASSWORD=Li69nux* + +[VERSION] +RELEASE = TEST.SW.VERSION diff --git a/controllerconfig/controllerconfig/controllerconfig/tests/test_system_config.py b/controllerconfig/controllerconfig/controllerconfig/tests/test_system_config.py index e94f3f2c35..8c17f319ab 100644 --- a/controllerconfig/controllerconfig/controllerconfig/tests/test_system_config.py +++ b/controllerconfig/controllerconfig/controllerconfig/tests/test_system_config.py @@ -528,3 +528,38 @@ def test_system_config_validation(): validate_only=True) with pytest.raises(exceptions.ConfigFail): validate(system_config, DEFAULT_CONFIG, None, False) + + +def test_pxeboot_range(): + """ Test import of system_config file for PXEBoot network address """ + + # Create the path to the system_config file + systemfile = os.path.join( + os.getcwd(), "controllerconfig/tests/files/", "system_config.pxeboot") + + # Test import and generation of answer file + _test_system_config(systemfile) + + # Test detection of invalid PXEBoot network start address + system_config = cr.parse_system_config(systemfile) + system_config.set('PXEBOOT_NETWORK', 'IP_START_ADDRESS', '8.123.122.345') + with pytest.raises(exceptions.ConfigFail): + validate(system_config, DEFAULT_CONFIG, None, False) + + # Test detection of invalid PXEBoot network end address + system_config = cr.parse_system_config(systemfile) + system_config.set('PXEBOOT_NETWORK', 'IP_END_ADDRESS', '128.123.122.345') + with pytest.raises(exceptions.ConfigFail): + validate(system_config, DEFAULT_CONFIG, None, False) + + # Test detection of smaller PXEBoot network end address + system_config = cr.parse_system_config(systemfile) + system_config.set('PXEBOOT_NETWORK', 'IP_END_ADDRESS', '192.168.102.30') + with pytest.raises(exceptions.ConfigFail): + validate(system_config, DEFAULT_CONFIG, None, False) + + # Test detection of PXEBoot network range less than min required (8) + system_config = cr.parse_system_config(systemfile) + system_config.set('PXEBOOT_NETWORK', 'IP_END_ADDRESS', '128.123.122.34') + with pytest.raises(exceptions.ConfigFail): + validate(system_config, DEFAULT_CONFIG, None, False)