diff --git a/README.md b/README.md index 09fdd07..3e6167c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,8 @@ The orchestration package should bring an out-of-the-box solution for our custom ### Setup During the setup process the script will iterate over the resources and prepare them for the reservation. - For each networking device: - - Perform health check (if exists) + - Power up + - Perform health check (if exists) - Load firmware (optional) - Load configuration (optional) - Re-run health check (if exists) @@ -34,7 +35,12 @@ During the teardown process the script will wipe the configuration from the netw - Disconnect routes and connectors ### Snapshot -The user can save a snapshot of the sandbox. In the background, the script will save the sandbox as a new blueprint, and the current configuration of the devices and VMs will be saved for future use (reserving this saved blueprint will restore the configuration on the resources and the VM snapshots). -- Save the sandbox as a blueprint +The user can save a snapshot of the sandbox. In the background, the script will save the sandbox as a new blueprint, and +the current configuration of the devices and VMs will be saved for future use (reserving this saved blueprint will +restore the configuration on the resources and the VM snapshots). +- Save the sandbox as a blueprint, add to blueprint category "Snapshots" - Save all the configuration files of the devices on the storage server (e.g. FTP server) - +- REQUIRES + a) root folder "Snapshots" created in Resource Manager + b) Create a blueprint category "Snapshots" + c) On ftp storage, create ftproot/CloudShell/Snapshots diff --git a/sandbox_scripts/QualiEnvironmentUtils/ConfigFileManager.py b/sandbox_scripts/QualiEnvironmentUtils/ConfigFileManager.py index 4c5ba43..cc0b872 100644 --- a/sandbox_scripts/QualiEnvironmentUtils/ConfigFileManager.py +++ b/sandbox_scripts/QualiEnvironmentUtils/ConfigFileManager.py @@ -22,14 +22,18 @@ def create_concrete_config_from_template(self, template_config_data, config_set_ :param SandboxBase sandbox: The sandbox to get other resources values from :param ResourceBase resource: The resource we want to create the config file for """ + # if resource is None: + # raise QualiError('') try: concrete_config_data = template_config_data + #subst_log = resource.name + '\n' # This is for debug use - uncoment any statements using it if desired # Replace {ConfigPool.PARAM} with PARAM's value from the pool it = re.finditer(r'\{ConfigPool\:[^}]*\}', concrete_config_data, flags=re.IGNORECASE) for match in it: param = match.group() if param.lower() in config_set_pool_data: concrete_config_data = concrete_config_data.replace(param, config_set_pool_data[param.lower()]) + #subst_log += param + ' = ' + config_set_pool_data[param.lower()] + '\n' else: raise Exception('Could not find attribute ' + param.lower() + ' in the config pool') @@ -39,19 +43,19 @@ def create_concrete_config_from_template(self, template_config_data, config_set_ param = match.group() quali_note = 'Built from template: ' + strftime('%Y-%b-%d %H:%M:%S', gmtime()) concrete_config_data = concrete_config_data.replace(param, quali_note) - + #subst_log += param + ' = ' + quali_note + '\n' # Replace {Device.Self.Name} with the resource's name it = re.finditer(r'\{Device:Self:Name\}', concrete_config_data, flags=re.IGNORECASE) for match in it: param = match.group() concrete_config_data = concrete_config_data.replace(param, resource.name) - + #subst_log += param + ' = ' + resource.name + '\n' # Replace {Device.Self.Address} with the resource's management ip it = re.finditer(r'\{Device:Self:Address\}', concrete_config_data, flags=re.IGNORECASE) for match in it: param = match.group() concrete_config_data = concrete_config_data.replace(param, resource.address) - + #subst_log += param + ' = ' + resource.address + '\n' # Replace {Device.Self.ATTRIBUTE_NAME} with the resource's attribute value # TODO: Need to decode password attributes: Password, Enable Password, and SNMP Read Community it = re.finditer(r'\{Device:Self\:[^}]*\}', concrete_config_data, flags=re.IGNORECASE) @@ -62,18 +66,18 @@ def create_concrete_config_from_template(self, template_config_data, config_set_ param_val = resource.get_attribute(att_name) # param_val = resource.get_attribute(param) concrete_config_data = concrete_config_data.replace(param, param_val) - - # Replacement of params from types: {Device:ALIAS:Attribute_name} + #subst_log += param + ' = ' + param_val + '\n' + # Replacement of params from types: {Device:ALIAS:Attribute_name} WHERE ALIAS is at any structure level it = re.finditer(r'\{Device:[^}]*\}', concrete_config_data, flags=re.IGNORECASE) - root_resources = None + the_resources = None for match in it: param = match.group() junk, sb_alias, alias_attribname = param.split(":") alias_attribname = alias_attribname.replace("}", "") concrete_name = '' - if root_resources is None: # fetch once the resources - root_resources = sandbox.get_root_networking_resources() - for resource in root_resources: + if the_resources is None: # fetch once the resources + the_resources = sandbox.get_all_resources() + for resource in the_resources: if resource.alias == sb_alias: concrete_name = resource.name if resource.attribute_exist(alias_attribname): @@ -82,11 +86,13 @@ def create_concrete_config_from_template(self, template_config_data, config_set_ raise Exception("Could not find attribute '{0}' in resource '{1}'".format(alias_attribname, resource.name)) concrete_config_data = concrete_config_data.replace(param, param_val) + #subst_log += param + ' = ' + param_val + '\n' break if concrete_name <= ' ': raise Exception('Could not find a resource with alias ' + sb_alias + '; likely missing from blueprint.') - return concrete_config_data + return concrete_config_data #, subst_log except Exception as ex: - raise QualiError('ConfigFileManager', "Failed to create a concrete config file from the template\'s data. " - "Unexpected error: " + ex.message) + raise QualiError('ConfigFileManager', "Failed to create concrete config for " + resource.name + + " from template. Unexpected error: " + ex.message) + diff --git a/sandbox_scripts/QualiEnvironmentUtils/Resource.py b/sandbox_scripts/QualiEnvironmentUtils/Resource.py index b3a9f2a..9a3980b 100644 --- a/sandbox_scripts/QualiEnvironmentUtils/Resource.py +++ b/sandbox_scripts/QualiEnvironmentUtils/Resource.py @@ -9,6 +9,10 @@ import json from time import sleep + +POWER_ON_WL = ["power_on", "Power On", "Power ON", "PowerOn"] # whitelist for power on commands +POWER_OFF_WL = ["power_off", "Power Off", "Power OFF", "PowerOff"] # whitelist for power off commands + class ResourceBase(object): def __init__(self, resource_name, resource_alias=''): if resource_name != "": @@ -26,9 +30,12 @@ def __init__(self, resource_name, resource_alias=''): self.model = self.get_attribute('Model') else: self.model = self.details.ResourceModelName - + #self.model = self.model.replace(' ','_') + #Hacked in to cover for filenames too long for product, such as those below: + self.model = self.model.replace('Cisco 5500 Series Wireless LAN Controller','WLC') self.alias = resource_alias + # ----------------------------------------- # ----------------------------------------- def has_command(self, command_name): @@ -40,6 +47,38 @@ def has_command(self, command_name): return True return False + # ----------------------------------------- + # ----------------------------------------- + def has_power_on(self): + for command in self.commands: + if command.Name in POWER_ON_WL: + return command.Name + return "" + + # ----------------------------------------- + # ----------------------------------------- + def has_power_off(self): + for command in self.commands: + if command.Name in POWER_OFF_WL: + return command.Name + return "" + + # ----------------------------------------- + # ----------------------------------------- + def has_connected_power_on(self): + for command in self.connected_commands: + if command.Name in POWER_ON_WL: + return command.Name + return "" + + # ----------------------------------------- + # ----------------------------------------- + def has_connected_power_off(self): + for command in self.connected_commands: + if command.Name in POWER_OFF_WL: + return command.Name + return "" + # ----------------------------------------- # ----------------------------------------- def attribute_exist(self, attribute_name): @@ -77,6 +116,19 @@ def set_attribute_value(self, attribute_name, attribute_value): except CloudShellAPIError as error: raise QualiError(self.name, "Failed to set attribute named or ending-with '" + attribute_name + "'. " + error.message) + # ----------------------------------------- + # ----------------------------------------- + def get_upcoming(self, period_start, period_end): + try: + upcoming = self.api_session.GetResourceAvailabilityInTimeRange(resourcesNames=([self.name]), + startTime=period_start, + endTime=period_end, + showAllDomains=False) + except CloudShellAPIError as error: + raise QualiError(self.name, "Failed to get upcoming rsvn list. " + error.message) + + return upcoming + # ----------------------------------------- # implement the command to get the neighbors and their ports # will return a dictionary of device's name and its port @@ -96,7 +148,7 @@ def get_neighbors(self, reservation_id): # ---------------------------------- # ---------------------------------- - def health_check(self,reservation_id, health_check_attempts=1): + def health_check(self,reservation_id, health_check_attempts=1, printOutput=True): """ Run the healthCheck command on the device :param str reservation_id: Reservation id. @@ -105,7 +157,7 @@ def health_check(self,reservation_id, health_check_attempts=1): for attempts in range(0, int(health_check_attempts)): try: # Return a detailed description in case of a failure - out = self.execute_command(reservation_id, 'health_check', printOutput=True) #.Output() + out = self.execute_command(reservation_id, 'health_check', printOutput=printOutput) #.Output() if out.Output.find(' passed') == -1 and attempts == (int(health_check_attempts) -1): err = "Health check did not pass for device " + self.name + ". " + out.Output return err @@ -187,44 +239,26 @@ def save_network_config(self, reservation_id, config_path, config_type): :param config_type: StartUp or Running """ #TODO modify the function to identify the command name and its params (similar behavior as in load_network_config) - # Run executeCommand with the restore command and its params (ConfigPath,RestoreMethod) + # Run executeCommand for the Save command and its params try: - command_inputs = [InputNameValue('source_filename', str(config_type)), - InputNameValue('destination_host', str(config_path))] + command_inputs = [InputNameValue('folder_path', str(config_path)), + InputNameValue('configuration_type', str(config_type))] if self.attribute_exist('VRF Management Name'): vrf_name = self.get_attribute('VRF Management Name') if vrf_name !='': - command_inputs.append(InputNameValue('vrf', str(vrf_name))) + command_inputs.append(InputNameValue('vrf_management_name', str(vrf_name))) - config_name = self.execute_command(reservation_id, 'Save', + config_name = self.execute_command(reservation_id, 'save', commandInputs=command_inputs, - printOutput=True).Output + printOutput=False).Output #TODO check the output is the created file name return config_name + except QualiError as qerror: + raise QualiError(self.name, "Failed to save configuration: " + qerror.message) except: - try: - command_inputs = [InputNameValue('source_filename', str(config_type)), - InputNameValue('destination_host', str(config_path))] - - if self.attribute_exist('VRF Management Name'): - vrf_name = self.get_attribute('VRF Management Name') - if vrf_name !='': - command_inputs.append(InputNameValue('vrf', str(vrf_name))) - - config_name = self.execute_command(reservation_id, 'save', - commandInputs=command_inputs, - printOutput=True).Output - - #TODO check the output is the created file name - config_name = config_name.rstrip(',') - return config_name - - except QualiError as qerror: - raise QualiError(self.name, "Failed to save configuration: " + qerror.message) - except: - raise QualiError(self.name, "Failed to save configuration. Unexpected error:" + str(sys.exc_info()[0])) + raise QualiError(self.name, "Failed to save configuration. Unexpected error:" + str(sys.exc_info()[0])) # ----------------------------------------- # ----------------------------------------- @@ -286,24 +320,19 @@ def orchestration_save(self, reservation_id, config_path, config_type): config_name = None if self.is_app(): - for command in self.connected_commands: if 'orchestration_save' == command.Name: tag = command.Tag - config_name = self.execute_connected_command(reservation_id, 'orchestration_save', tag, - commandInputs=['shallow',''], printOutput=False) - + commandInputs=[InputNameValue('shallow','')], printOutput=False) return config_name.Output else: if self.has_command('orchestration_save'): - config_name = self.execute_command(reservation_id, 'orchestration_save', commandInputs=[InputNameValue('custom_params', json_str), InputNameValue('mode','shallow')], printOutput=False) - return config_name.Output if config_name: diff --git a/sandbox_scripts/QualiEnvironmentUtils/Sandbox.py b/sandbox_scripts/QualiEnvironmentUtils/Sandbox.py index 28415bb..d3a2073 100644 --- a/sandbox_scripts/QualiEnvironmentUtils/Sandbox.py +++ b/sandbox_scripts/QualiEnvironmentUtils/Sandbox.py @@ -187,6 +187,32 @@ def get_root_resources(self): return root_resources + # ---------------------------------- + #created 28 June so we have all alias names accessible for substitution + def get_all_resources(self): + """ + Get the resources + :rtype: list[ResourceBase] + """ + all_resources = [] + all_resources_names_dict = {} + details = self.get_details() + resources = details.ReservationDescription.Resources + topo_resources = details.ReservationDescription.TopologiesReservedResources + # Loop over all devices in the sandbox and add to a dictionary all root devices: + for resource in resources: + all_resources_names_dict[resource.Name] = 1 + + # instantiate a resource object for each root device + for a_resource_name in all_resources_names_dict.keys(): + a_resource_alias = '' + for topo_resource in topo_resources: + if topo_resource.Name == a_resource_name: + a_resource_alias = topo_resource.Alias + break + all_resources.append(ResourceBase(a_resource_name, a_resource_alias)) + + return all_resources # ---------------------------------- # ---------------------------------- @@ -240,15 +266,15 @@ def get_root_networking_resources(self): # ---------------------------------- # ---------------------------------- - def clear_all_resources_live_status(self): + def clear_all_resources_live_status(self, ignore_models=[]): """ - Clear the live status from all the devices + Clear the live status from all the devices if not a model in ignore models """ - #TODO change to honor ignor_models root_resources = self.get_root_resources() for resource in root_resources: - self.api_session.SetResourceLiveStatus(resource.name, liveStatusName="Info", - additionalInfo='status cleared ' + strftime("%H:%M:%S", gmtime())) + if resource.model not in ignore_models: + self.api_session.SetResourceLiveStatus(resource.name, liveStatusName="Info", + additionalInfo='status cleared ' + strftime("%H:%M:%S", gmtime())) # ---------------------------------- # ---------------------------------- @@ -386,6 +412,23 @@ def enqueue_command(self, commandName, commandInputs=[], printOutput=False): # ----------------------------------------- # ----------------------------------------- + def setcategorysnapshots(self, snapshot_name): + try: + snapshot_name = str("Snapshots/" + snapshot_name) + self.api_session.SetTopologyCategory(snapshot_name, "Snapshots") + except CloudShellAPIError as error: + err = "Failed to set category to Snapshots. " + error.message + self.report_error(error_message=err, raise_error=True, write_to_output_window=True) + + # ----------------------------------------- + # ----------------------------------------- + # TODO When we can change desc by api.... + def update_description(self, description): + # No api exists yet for this feature + return + + # ----------------------------------------- + # ------------- --------------------------- def save_sandbox_as_blueprint(self, blueprint_name, write_to_output=True): try: # TODO - fullpath should be passed as a param to the function and not hard coded diff --git a/sandbox_scripts/QualiEnvironmentUtils/StorageManager.py b/sandbox_scripts/QualiEnvironmentUtils/StorageManager.py index b2e4374..2954cbf 100644 --- a/sandbox_scripts/QualiEnvironmentUtils/StorageManager.py +++ b/sandbox_scripts/QualiEnvironmentUtils/StorageManager.py @@ -26,7 +26,7 @@ def __init__(self, sandbox ): # ---------------------------------- # ---------------------------------- - #def _get_storage_client(self, storage_resource): + # def _get_storage_client(self, storage_resource): # if storage_resource.model.lower() == 'generic tftp server': # return TFTPClient(self.sandbox,storage_resource) # elif storage_resource.model.lower() == 'generic ftp server': @@ -38,7 +38,7 @@ def _get_storage_client(self, storage_resource): elif storage_resource.model.lower() == 'generic ftp server': return FTPClient(self.sandbox,storage_resource) - #---------------------------------- + # ----------------------------------- # ---------------------------------- def _get_repository_client(self, repository_resource): if repository_resource.model.lower() == 'gitlab': @@ -90,7 +90,7 @@ def upload(self, destination, source): # ---------------------------------- # ---------------------------------- def dir_exist(self, dir_name): - self.storage_client.dir_exist(dir_name) + return self.storage_client.dir_exist(dir_name) # ---------------------------------- # ---------------------------------- diff --git a/sandbox_scripts/QualiEnvironmentUtils/tests/test_ConfigFileManager.py b/sandbox_scripts/QualiEnvironmentUtils/tests/test_ConfigFileManager.py index 3b5c528..d802d8e 100644 --- a/sandbox_scripts/QualiEnvironmentUtils/tests/test_ConfigFileManager.py +++ b/sandbox_scripts/QualiEnvironmentUtils/tests/test_ConfigFileManager.py @@ -21,7 +21,13 @@ def test_values_only_from_pool_when_pool_doesnt_have_the_attribute(self, mock_ap config_file_mgr = ConfigFileManager() sandbox = None - resource = None + rd = Mock() + rd.Name = 'myresource' + rd.Address = '1.2.3.4' + rd.ChildResources = [] + rd.ResourceAttributes = [] + mock_api_session.return_value.GetResourceDetails = Mock(return_value=rd) + resource = ResourceBase(resource_name='myresource') tmp_template_config_file_data = """ {ConfigPool:Pool1} @@ -34,8 +40,8 @@ def test_values_only_from_pool_when_pool_doesnt_have_the_attribute(self, mock_ap the_exception = e.exception self.assertEqual(str(the_exception), - "CloudShell error at ConfigFileManager. " - "Error is: Failed to create a concrete config file from the template's data. " + "CloudShell error at ConfigFileManager. Error is: Failed to create concrete config " + "for myresource from template. " "Unexpected error: Could not find attribute {configpool:pool1} in the config pool") @patch('cloudshell.helpers.scripts.cloudshell_scripts_helpers.get_api_session') @@ -160,7 +166,7 @@ def test_attribute_from_other_resource_in_sandbox(self, mock_api_session): resource = ResourceBase(resource_name='myresource', resource_alias='OtherDevice') sandbox = Mock() - sandbox.get_root_networking_resources.return_value = [resource] + sandbox.get_all_resources.return_value = [resource] tmp_template_config_file_data = """{Device:OtherDevice:Pool1}""" @@ -187,7 +193,7 @@ def test_attribute_from_other_resource_in_sandbox_not_found(self, mock_api_sessi resource = ResourceBase(resource_name='myresource', resource_alias='OtherDevice') sandbox = Mock() - sandbox.get_root_networking_resources.return_value = [resource] + sandbox.get_all_resources.return_value = [resource] tmp_template_config_file_data = """{Device:OtherDevice:Pool123}""" @@ -199,7 +205,7 @@ def test_attribute_from_other_resource_in_sandbox_not_found(self, mock_api_sessi the_exception = e.exception self.assertEqual(str(the_exception), "CloudShell error at ConfigFileManager. " - "Error is: Failed to create a concrete config file from the template's data. " + "Error is: Failed to create concrete config for myresource from template. " "Unexpected error: Could not find attribute 'Pool123' in resource 'myresource'") @patch('cloudshell.helpers.scripts.cloudshell_scripts_helpers.get_api_session') @@ -218,7 +224,7 @@ def test_attribute_from_other_resource_in_sandbox_resource_not_found(self, mock_ resource = ResourceBase(resource_name='myresource', resource_alias='OtherDevice') sandbox = Mock() - sandbox.get_root_networking_resources.return_value = [resource] + sandbox.get_all_resources.return_value = [resource] tmp_template_config_file_data = """{Device:OtherDevice2:Pool1}""" @@ -229,8 +235,8 @@ def test_attribute_from_other_resource_in_sandbox_resource_not_found(self, mock_ the_exception = e.exception self.assertEqual(str(the_exception), - "CloudShell error at ConfigFileManager. Error is: " - "Failed to create a concrete config file from the template's data. " + "CloudShell error at ConfigFileManager. Error is: Failed to create concrete config " + "for myresource from template. " "Unexpected error: Could not find a resource with alias OtherDevice2; " "likely missing from blueprint.") diff --git a/sandbox_scripts/QualiEnvironmentUtils/tests/test_Sandbox.py b/sandbox_scripts/QualiEnvironmentUtils/tests/test_Sandbox.py index e31febf..086b8dd 100644 --- a/sandbox_scripts/QualiEnvironmentUtils/tests/test_Sandbox.py +++ b/sandbox_scripts/QualiEnvironmentUtils/tests/test_Sandbox.py @@ -210,6 +210,23 @@ def test_clear_all_resources_live_status_two_devices(self,mock_resourcebase): call('r2', additionalInfo='status cleared 12:00:01', liveStatusName='Info')] self.mock_api_session.return_value.SetResourceLiveStatus.assert_has_calls(calls) + @patch('sandbox_scripts.QualiEnvironmentUtils.Sandbox.ResourceBase') + def test_clear_all_resources_live_status_two_devices(self,mock_resourcebase): + resource1 = Mock() + resource1.name = "r1" + resource1.model = "m1" + resource2 = Mock() + resource2.name = "r2" + resource2.model = "m2" + rr = Mock() + rr = [resource1, resource2] + self.sandbox.get_root_resources = Mock(return_value=rr) + + with freeze_time("2017-01-17 12:00:01"): + self.sandbox.clear_all_resources_live_status(ignore_models=['m1']) + calls = [call('r2', additionalInfo='status cleared 12:00:01', liveStatusName='Info')] + self.mock_api_session.return_value.SetResourceLiveStatus.assert_has_calls(calls) + #================================================================ #test activate_connectors def test_activate_connectors_no_connectors_no_output(self): diff --git a/sandbox_scripts/environment/savesnapshot/SaveSnapshot.py b/sandbox_scripts/environment/savesnapshot/SaveSnapshot.py index 80f073f..5f93c54 100644 --- a/sandbox_scripts/environment/savesnapshot/SaveSnapshot.py +++ b/sandbox_scripts/environment/savesnapshot/SaveSnapshot.py @@ -4,7 +4,8 @@ from sandbox_scripts.helpers.Networking.save_restore_mgr import SaveRestoreManager from cloudshell.core.logger.qs_logger import get_qs_logger from sandbox_scripts.QualiEnvironmentUtils.QualiUtils import QualiError - +import os, sys +import datetime class EnvironmentSaveSnapshot: def __init__(self): @@ -13,32 +14,42 @@ def __init__(self): log_group=self.reservation_id, log_category='EnvironmentCommands') - def execute(self): sandbox = SandboxBase(self.reservation_id, self.logger) saveNRestoreTool = SaveRestoreManager(sandbox) + ignore_models=['Generic TFTP server', 'Config Set Pool', 'Generic FTP server', 'netscout switch 3912', + 'Subnet-28', 'Subnet-30', 'GitLab', 'SSID_Pool' ] - sandbox.clear_all_resources_live_status() + # why do this: sandbox.clear_all_resources_live_status(ignore_models) try: - username = helpers.get_reservation_context_details().owner_user if sandbox.get_storage_server_resource(): - snapshot_name = sandbox.Blueprint_name+"_"+username+"_"+ os.environ['name'] + #take first part of blueprint name up to first hyphen, underscore, or space + snapshot_name = sandbox.Blueprint_name.replace('-', '_') + snapshot_name = snapshot_name.replace(' ', '_').split('_')[0] + "_" + snapshot_name += username + "_" + os.environ['name'].replace(' ','_') + snapshot_name = snapshot_name.replace('-', '_') sandbox.save_sandbox_as_blueprint(snapshot_name) - - # replace spaces with _ in the snapshot's name - snapshot_name = snapshot_name.replace(' ', '_') - - saveNRestoreTool.save_config(snapshot_name=snapshot_name, config_type='running', - ignore_models=['Generic TFTP server', 'Config Set Pool','Generic FTP server', - 'netscout switch 3912']) + sandbox.setcategorysnapshots(snapshot_name) + sandbox.report_info("Blueprint " + snapshot_name + " created", write_to_output_window=True) + #future feature when we have the api to update descriptions + #description = Snapshot of created + #description = "Snapshot of " + sandbox.Blueprint_name + #description += " created " + datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y") + #sandbox.update_description(description) + + saveNRestoreTool.save_config(snapshot_name=snapshot_name, config_type='Running', + ignore_models=ignore_models) else: sandbox.report_error("There is no storage resource (e.g. FTP) available in the reservation",True,True) except QualiError as qe: - self.logger.error("Save snapshot failed. " + str(qe)) - - except: - self.logger.error("Save snapshot. Unexpected error:" + str(sys.exc_info())) + self.logger.error("Save snapshot failed. " + qe.message) + if "Name must contain only al" in qe.message: + sys.tracebacklimit = 0 + raise Exception("Save snapshot failed. " + qe.message) + except Exception as ex: + blob = ex.message + self.logger.error("Save snapshot. Unexpected error: " + str(sys.exc_info())) diff --git a/sandbox_scripts/environment/setup/setup_VM.py b/sandbox_scripts/environment/setup/setup_VM.py index e81b5a9..d739202 100644 --- a/sandbox_scripts/environment/setup/setup_VM.py +++ b/sandbox_scripts/environment/setup/setup_VM.py @@ -16,8 +16,8 @@ def __init__(self): log_group=self.reservation_id, log_category='Setup') - # --------------------------- - # --------------------------- + # ---------------------------- + # ---------------------------- def execute(self): self.sandbox = SandboxBase(self.reservation_id, self.logger) #TODO: don't use networking save and restore to figure if it's a snapshot setup diff --git a/sandbox_scripts/environment/setup/setup_resources.py b/sandbox_scripts/environment/setup/setup_resources.py index b01a7c8..77ee302 100644 --- a/sandbox_scripts/environment/setup/setup_resources.py +++ b/sandbox_scripts/environment/setup/setup_resources.py @@ -18,8 +18,12 @@ def execute(self): sandbox = SandboxBase(self.reservation_id, self.logger) saveNRestoreTool = SaveRestoreManager(sandbox) sandbox.report_info('Beginning load configuration for resources') + sandbox.report_info('Clearing status indicators ', write_to_output_window=True) + #Consider an ignore family capability? This list gets to be a maint issue...? + ignore_models=['Generic TFTP server', 'Config Set Pool', 'Generic FTP server', + 'netscout switch 3912', 'Subnet-28', 'Subnet-30', 'GitLab', 'SSID_Pool' ] try: - sandbox.clear_all_resources_live_status() + sandbox.clear_all_resources_live_status(ignore_models) if sandbox.get_storage_server_resource(): # Get the config set name from the orchestration's params config_set_name = '' @@ -27,9 +31,6 @@ def execute(self): config_set_name = os.environ['Set Name'] except: pass - #Consider an ignore family capability? This list gets to be a maint issue...? - ignore_models=['Generic TFTP server', 'Config Set Pool', 'Generic FTP server', - 'netscout switch 3912', 'Subnet-28', 'Subnet-30', 'GitLab'] if saveNRestoreTool.get_storage_manager(): if saveNRestoreTool.is_snapshot(): @@ -50,7 +51,7 @@ def execute(self): # call activate_all_routes_and_connectors sandbox.activate_all_routes_and_connectors() - sandbox.report_info('Sandbox setup finished successfully') + sandbox.report_info('Sandbox setup finished.', write_to_output_window=True) # Call routes_validation # sandbox.routes_validation() diff --git a/sandbox_scripts/environment/setup/setup_script.py b/sandbox_scripts/environment/setup/setup_script.py index 0ed694d..1595f09 100644 --- a/sandbox_scripts/environment/setup/setup_script.py +++ b/sandbox_scripts/environment/setup/setup_script.py @@ -57,7 +57,7 @@ def execute(self): self.logger.info("Setup for reservation {0} completed".format(self.reservation_id)) #api.WriteMessageToReservationOutput(reservationId=self.reservation_id, - # message='Sandbox setup finished successfully') + # message='Sandbox setup finished successfully') def _prepare_connectivity(self, api, reservation_id): """ diff --git a/sandbox_scripts/environment/setup/tests/test_setup_resources.py b/sandbox_scripts/environment/setup/tests/test_setup_resources.py index 000b009..99ec4c4 100644 --- a/sandbox_scripts/environment/setup/tests/test_setup_resources.py +++ b/sandbox_scripts/environment/setup/tests/test_setup_resources.py @@ -39,12 +39,13 @@ def test_flow_ok_with_snapshots(self, mock_save, mock_sandboxbase, mock_api_sess mock_save.return_value.is_snapshot.return_value = True self.setup_script.execute() - mock_sandboxbase.return_value.clear_all_resources_live_status.assert_called_with() - mock_save.return_value.load_config.assert_called_with(config_stage='Snapshots', config_type='Running', ignore_models=['Generic TFTP server', 'Config Set Pool', 'Generic FTP server', 'netscout switch 3912', 'Subnet-28', 'Subnet-30', 'GitLab']) + mock_sandboxbase.return_value.clear_all_resources_live_status.assert_called_with(['Generic TFTP server', 'Config Set Pool', 'Generic FTP server', 'netscout switch 3912', 'Subnet-28', 'Subnet-30', 'GitLab', 'SSID_Pool']) + mock_save.return_value.load_config.assert_called_with(config_stage='Snapshots', config_type='Running', ignore_models=['Generic TFTP server', 'Config Set Pool', 'Generic FTP server', 'netscout switch 3912', 'Subnet-28', 'Subnet-30', 'GitLab', 'SSID_Pool']) mock_sandboxbase.return_value.power_on_vms.assert_called_with() mock_sandboxbase.return_value.activate_all_routes_and_connectors.assert_called_with() report_info_calls = [call('Beginning load configuration for resources'), - call('Sandbox setup finished successfully')] + call('Clearing status indicators ', write_to_output_window=True), + call('Sandbox setup finished.', write_to_output_window=True)] mock_sandboxbase.return_value.report_info.assert_has_calls(report_info_calls) @@ -55,12 +56,15 @@ def test_flow_ok_with_gold(self, mock_save, mock_sandboxbase, mock_api_session): mock_save.return_value.is_snapshot.return_value = False mock_sandboxbase.return_value.get_storage_server_resource.return_value = True self.setup_script.execute() - mock_sandboxbase.return_value.clear_all_resources_live_status.assert_called_with() - mock_save.return_value.load_config.assert_called_with(config_set_name='', config_stage='Gold', config_type='Running', ignore_models=['Generic TFTP server', 'Config Set Pool', 'Generic FTP server', 'netscout switch 3912', 'Subnet-28', 'Subnet-30', 'GitLab']) + mock_sandboxbase.return_value.clear_all_resources_live_status.assert_called_with( + ['Generic TFTP server', 'Config Set Pool', 'Generic FTP server', 'netscout switch 3912', 'Subnet-28', + 'Subnet-30', 'GitLab', 'SSID_Pool']) + mock_save.return_value.load_config.assert_called_with(config_set_name='', config_stage='Gold', config_type='Running', ignore_models=['Generic TFTP server', 'Config Set Pool', 'Generic FTP server', 'netscout switch 3912', 'Subnet-28', 'Subnet-30', 'GitLab', 'SSID_Pool']) mock_sandboxbase.return_value.power_on_vms.assert_called_with() mock_sandboxbase.return_value.activate_all_routes_and_connectors.assert_called_with() report_info_calls = [call('Beginning load configuration for resources'), - call('Sandbox setup finished successfully')] + call('Clearing status indicators ', write_to_output_window=True), + call('Sandbox setup finished.', write_to_output_window=True)] mock_sandboxbase.return_value.report_info.assert_has_calls(report_info_calls) @patch('cloudshell.helpers.scripts.cloudshell_scripts_helpers.get_api_session') @@ -69,10 +73,13 @@ def test_flow_ok_with_gold(self, mock_save, mock_sandboxbase, mock_api_session): def test_flow_ok_with_no_storage_device(self, mock_save, mock_sandboxbase, mock_api_session): mock_sandboxbase.return_value.get_storage_server_resource.return_value = False self.setup_script.execute() - mock_sandboxbase.return_value.clear_all_resources_live_status.assert_called_with() + mock_sandboxbase.return_value.clear_all_resources_live_status.assert_called_with( + ['Generic TFTP server', 'Config Set Pool', 'Generic FTP server', 'netscout switch 3912', 'Subnet-28', + 'Subnet-30', 'GitLab', 'SSID_Pool']) report_info_calls = [call('Beginning load configuration for resources'), + call('Clearing status indicators ', write_to_output_window=True), call('Skipping load configuration. No storage resource associated with the blueprint ', write_to_output_window=True), - call('Sandbox setup finished successfully')] + call('Sandbox setup finished.', write_to_output_window=True)] mock_sandboxbase.return_value.report_info.assert_has_calls(report_info_calls) @patch('cloudshell.helpers.scripts.cloudshell_scripts_helpers.get_api_session') @@ -84,6 +91,7 @@ def test_qualierror_exception(self, mock_save, mock_sandboxbase, mock_api_sessio self.setup_script.execute() report_info_calls = [call('Beginning load configuration for resources'), + call('Clearing status indicators ', write_to_output_window=True), call('Skipping load configuration. No storage resource associated with the blueprint ', write_to_output_window=True)] mock_sandboxbase.return_value.report_info.assert_has_calls(report_info_calls) diff --git a/sandbox_scripts/environment/teardown/teardown_resources.py b/sandbox_scripts/environment/teardown/teardown_resources.py index 42e69f2..90bc257 100644 --- a/sandbox_scripts/environment/teardown/teardown_resources.py +++ b/sandbox_scripts/environment/teardown/teardown_resources.py @@ -4,7 +4,7 @@ from sandbox_scripts.QualiEnvironmentUtils.Sandbox import SandboxBase from sandbox_scripts.helpers.Networking.save_restore_mgr import SaveRestoreManager from sandbox_scripts.QualiEnvironmentUtils.QualiUtils import QualiError - +import sys class EnvironmentTeardownResources: def __init__(self): @@ -18,12 +18,12 @@ def execute(self): sandbox = SandboxBase(self.reservation_id, self.logger) saveNRestoreTool = SaveRestoreManager(sandbox) - sandbox.report_info("Beginning load baseline configuration for resources", write_to_output_window=True) - sandbox.clear_all_resources_live_status() + sandbox.report_info("Beginning load baseline configuration for resources (teardown)", write_to_output_window=True) + ignore_models=['Generic TFTP server', 'Config Set Pool', 'Generic FTP server', + 'netscout switch 3912', 'Subnet-28', 'Subnet-30', 'GitLab', 'SSID_Pool'] + sandbox.clear_all_resources_live_status(ignore_models) try: if saveNRestoreTool.get_storage_manager(): - ignore_models = ['Generic TFTP server', 'Config Set Pool', 'Generic FTP server', 'netscout switch 3912', - 'OnPATH Switch 3903', 'Ixia Traffic generator', "SubNet-28", "SubNet-30", "GitLab"] saveNRestoreTool.load_config(config_stage='Base', config_type='Running', ignore_models=ignore_models, diff --git a/sandbox_scripts/environment/teardown/teardown_script.py b/sandbox_scripts/environment/teardown/teardown_script.py index c3bc12c..39b67a1 100644 --- a/sandbox_scripts/environment/teardown/teardown_script.py +++ b/sandbox_scripts/environment/teardown/teardown_script.py @@ -33,8 +33,8 @@ def execute(self): self._cleanup_connectivity(api, self.reservation_id) self.logger.info("Teardown for reservation {0} completed".format(self.reservation_id)) - api.WriteMessageToReservationOutput(reservationId=self.reservation_id, - message='Sandbox teardown finished successfully') + #api.WriteMessageToReservationOutput(reservationId=self.reservation_id, + # message='Sandbox teardown finished successfully') def _disconnect_all_routes_in_reservation(self, api, reservation_details): connectors = reservation_details.ReservationDescription.Connectors diff --git a/sandbox_scripts/helpers/Networking/NetworkingSaveNRestore.py b/sandbox_scripts/helpers/Networking/NetworkingSaveNRestore.py index 7bd0299..da1f699 100644 --- a/sandbox_scripts/helpers/Networking/NetworkingSaveNRestore.py +++ b/sandbox_scripts/helpers/Networking/NetworkingSaveNRestore.py @@ -1,6 +1,6 @@ # coding=utf-8 import csv -import os +import os, datetime, time import tempfile from multiprocessing.pool import ThreadPool from threading import Lock @@ -9,6 +9,7 @@ from sandbox_scripts.helpers.Networking.base_save_restore import * from sandbox_scripts.QualiEnvironmentUtils.QualiUtils import QualiError from sandbox_scripts.QualiEnvironmentUtils.QualiUtils import rsc_run_result_struct +from cloudshell.api.cloudshell_api import InputNameValue class NetworkingSaveRestore(object): def __init__(self, sandbox): @@ -44,7 +45,8 @@ def __init__(self, sandbox): # e.g. tftp://configs/Base/svl290-gg07-sw1_c3850.cfg # ---------------------------------- def load_config(self, config_stage, config_type, restore_method="Override", config_set_name='', ignore_models=None, - write_to_output=True, remove_temp_files=False, use_Config_file_path_attr=False): + write_to_output=True, remove_temp_files=False, use_Config_file_path_attr=False, + in_teardown_mode=False): """ Load the configuration from config files on the Blueprint's devices :param str config_stage: The stage of the config e.g Gold, Base @@ -81,9 +83,8 @@ def load_config(self, config_stage, config_type, restore_method="Override", conf async_results = [pool.apply_async(self._run_asynch_load, (resource, images_path_dict, root_path, ignore_models, config_stage, health_check_attempts, lock, - use_Config_file_path_attr)) + use_Config_file_path_attr,in_teardown_mode)) for resource in root_resources] - pool.close() pool.join() for async_result in async_results: @@ -98,8 +99,29 @@ def load_config(self, config_stage, config_type, restore_method="Override", conf self.sandbox.report_info(res.resource_name + "\n" + res.message, write_to_output_window=True) if remove_temp_files: self._remove_temp_config_files() + + #Run Validate L2 Routes command on each IOS or NXOS device; pause for ports to come 'detected' + if not in_teardown_mode: + been_here = False + for resource in root_resources: + if resource.has_command('Validate_L2_Routes'): + if been_here is False: + been_here = True + self.sandbox.report_info('Pausing 90s before "Validate L2 Routes"', write_to_output_window=True) + time.sleep(90) + try: + command_inputs = [InputNameValue('Show_Not_Found_Only', 'y')] + # TODO See why we can't change this to use resource.execute command + self.sandbox.api_session.ExecuteCommand(self.sandbox.id, resource.name, 'Resource', + "Validate_L2_Routes", + commandInputs=command_inputs, + printOutput=True) + except Exception as ex: + self.sandbox.report_error("Validate L2 Routes failed to run for " + resource.name + + ' ex: ' + ex.message, + write_to_output_window=True) else: - self.sandbox.report_info("No networking resources found to process.") + self.sandbox.report_info("No networking resources found to process.", write_to_output_window=True) # ---------------------------------- # ---------------------------------- @@ -120,18 +142,39 @@ def _remove_temp_config_files(self): # ---------------------------------- # ---------------------------------- def _run_asynch_load(self, resource, images_path_dict, root_path, ignore_models, config_stage, - health_check_attempts, lock, - use_Config_file_path_attr): + health_check_attempts, lock, use_Config_file_path_attr, in_teardown_mode): + # TODO resolve different approach - message var vs real-time sandbox op window messages. message = "" # run_status = True saved_artifact_info = None additionalinfo = '' load_result = rsc_run_result_struct(resource.name) # Check if needs to load the config to the device + is_power_ctrl_ok = self._is_power_ctrl_ok(resource, ignore_models=ignore_models) + if is_power_ctrl_ok: + # Attempt PowerOn + try: + pwr_cmd = resource.has_connected_power_on() + if pwr_cmd > "": + resource.execute_connected_command(self.sandbox.id, pwr_cmd, tag='power') + self.sandbox.report_info(resource.name + ' - powered ON',write_to_output_window=True) + else: + pwr_cmd = resource.has_power_on() + if pwr_cmd > "": + resource.execute_command(self.sandbox.id, pwr_cmd) + self.sandbox.report_info(resource.name + ' - powered on',write_to_output_window=True) + else: + self.sandbox.report_info(resource.name + ' - no power on command found', + write_to_output_window=True) + except Exception as ex: + print resource.name + " power on fault. " + ex.message + load_config_to_device = self._is_load_config_to_device(resource, ignore_models=ignore_models) + # ------------------------------------------------------ + #if load_config_to_device and '3850-6' in resource.name: + # TODO change form save/restore to orchestration save and restore - high priority. if load_config_to_device: - - self.sandbox.report_info(resource.name + " starting health check", write_to_output_window=True) + self.sandbox.report_info(resource.name + " - starting health check", write_to_output_window=True) health_check_result = resource.health_check(self.sandbox.id, health_check_attempts) if health_check_result == "": self.sandbox.report_info(resource.name + " -- Initial Health Check Passed.") @@ -140,17 +183,12 @@ def _run_asynch_load(self, resource, images_path_dict, root_path, ignore_models, with lock: config_path = self._get_concrete_config_file_path(root_path, resource, config_stage, write_to_output=False) + self.sandbox.report_info("Config path for " + resource.alias + ": " + str(config_path)) if use_Config_file_path_attr: resource.set_attribute_value('Config file path', config_path) # TODO - Snapshots currently only restore configuration. We need to restore firmware as well if config_stage.lower() == 'snapshots': - if resource.has_command('orchestration_restore'): - dest_name = resource.name + '_' + resource.model + '_artifact.txt' - dest_name = dest_name.replace(' ', '-') - saved_artifact_info = self.storage_mgr.download_artifact_info(config_path, dest_name) - resource.orchestration_restore(self.sandbox.id, config_path, saved_artifact_info) - else: - resource.load_network_config(self.sandbox.id, config_path, 'Running', 'Override') + resource.load_network_config(self.sandbox.id, config_path, 'Running', 'Override') else: if len(images_path_dict) > 0: # check what the device FW version is currently. @@ -172,13 +210,17 @@ def _run_asynch_load(self, resource, images_path_dict, root_path, ignore_models, image_key = resource.model.replace(' ', '_') if image_key: + #self.sandbox.report_info("Using image key " + str(image_key) + " for " + resource.name) dict_img_version = images_path_dict[image_key].version + if dict_img_version == '': + self.sandbox.report_error('Failed to find target version for ' + resource.name) else: # Getting here means no firmware specified in FirmwareData.csv - message += "\n" + resource.name + ": NO firmware version specified in Base FirmwareData.csv" + message += "\n" + resource.name + ": NO firmware version specified in FirmwareData.csv" # same image version - Only load config (running override) - message += resource.name + ": loading configuration from " + config_path + #message += resource.name + ": loading configuration from " + config_path + self.sandbox.report_info(resource.name + ": loading configuration from " + config_path) if dict_img_version.lower() == version.lower(): resource.load_network_config(self.sandbox.id, config_path=config_path, config_type='Running', @@ -203,17 +245,17 @@ def _run_asynch_load(self, resource, images_path_dict, root_path, ignore_models, message += "\n" + resource.name + ": loading config from:" + config_path resource.load_network_config(self.sandbox.id, config_path, 'Running', 'Override') - health_check_result = resource.health_check(self.sandbox.id, health_check_attempts=1) + health_check_result = resource.health_check(self.sandbox.id, health_check_attempts=1, printOutput=False) if health_check_result != '': raise QualiError(self.sandbox.id, resource.name + " did not pass health check after loading configuration") else: - self.sandbox.report_info(resource.name + " -- final Health Check Passed.") + self.sandbox.report_info(resource.name + " - Final Health Check Passed.", write_to_output_window=True) except QualiError as qe: load_result.run_result = False - err = "\nFailed to load configuration " + additionalinfo + " for device " + resource.name + ". " + str( - qe) + err = "\nFailed to load configuration " + additionalinfo + " for device " + resource.name + \ + ". " + str(qe) message += err except Exception as ex: load_result.run_result = False @@ -230,6 +272,50 @@ def _run_asynch_load(self, resource, images_path_dict, root_path, ignore_models, message += err load_result.message = message + #self.sandbox.report_info('power off control disabled in this vers', write_to_output_window=True) + # TODO more to resource class + if is_power_ctrl_ok and in_teardown_mode and resource.model not in ignore_models: + # Attempt PowerOFF. + # If threshold is a Negative value, no power off. + # If threshold is 0-4, power off now + # If threshold is >5, check for usesage in that period with a 2minute shift ahead + try: + PowerOff = True + threshold = resource.get_attribute('PowerOff Threshold') + # avoid attempts to manage too small a window + if int(threshold) <= 4 and int(threshold) > 0: + threshold = 0 + self.sandbox.report_info("Using power threshold of " + str(threshold) + " for " + resource.name) + if int(threshold) > 4: + period_start = (datetime.datetime.now() + datetime.timedelta(minutes=2)).isoformat() + period_end = (datetime.datetime.now() + datetime.timedelta(minutes=int(threshold))).isoformat() + upcoming = resource.get_upcoming(period_start=period_start, period_end=period_end) + for element in upcoming.Resources: + if element.Name == resource.name: + if element.Reservations.__len__() > 0: + for resv in element.Reservations: + if resv.StartTime >= period_start and resv.StartTime < period_end: + self.sandbox.report_info(resource.name + ": not powered off due to upcoming use.", + write_to_output_window=True) + PowerOff = False + if int(threshold) == 0 or PowerOff==True: + pwr_cmd = resource.has_connected_power_off() + if pwr_cmd > "": + resource.execute_connected_command(self.sandbox.id, pwr_cmd, tag='power') + self.sandbox.report_info(resource.name + ': powered OFF', + write_to_output_window=True) + else: + pwr_cmd = resource.has_power_off() + if pwr_cmd > "": + resource.execute_command(self.sandbox.id, pwr_cmd) + self.sandbox.report_info(resource.name + ': powered off', + write_to_output_window=True) + else: + self.sandbox.report_info(resource.name + ': no power_off command found', + write_to_output_window=True) + except Exception as ex: + print ex.message + return load_result # ---------------------------------- @@ -271,70 +357,72 @@ def _get_concrete_config_file_path(self, root_path, resource, config_stage, writ :rtype: str """ - config_file_mgr = ConfigFileManager() - # TODO - set the pool dictionary only once during the init of the class - config_set_pool_data = dict() - # If there is a pool resource, get the pool data - config_set_pool_resource = self.sandbox.get_config_set_pool_resource() - if config_set_pool_resource is not None: - config_set_pool_manager = ConfigPoolManager(sandbox=self.sandbox, pool_resource=config_set_pool_resource) - config_set_pool_data = config_set_pool_manager.pool_data - if config_stage == 'snapshots': - config_path = root_path + resource.name + '_' + resource.model + '.cfg' + if config_stage == 'Snapshots': + config_path = root_path + resource.name + '_' + resource.model.replace(' ', '_') + '.cfg' else: - config_path = root_path + resource.alias + '_' + resource.model + '.cfg' - config_path = config_path.replace(' ', '_') - # Look for a template config file - tmp_template_config_file = tempfile.NamedTemporaryFile(delete=False) - tftp_template_config_path = root_path + resource.alias + '_' + resource.model + '.tm' - tftp_template_config_path = tftp_template_config_path.replace(' ', '_') - # try: - # look for a concrete config file - try: - self.storage_mgr.download(config_path, tmp_template_config_file.name) - tmp_template_config_file.close() - os.unlink(tmp_template_config_file.name) - # if no concrete file, delete the look for a template file - except: + config_file_mgr = ConfigFileManager() + # TODO - set the pool dictionary only once during the init of the class + config_set_pool_data = dict() + # If there is a pool resource, get the pool data + config_set_pool_resource = self.sandbox.get_config_set_pool_resource() + if config_set_pool_resource is not None: + config_set_pool_manager = ConfigPoolManager(sandbox=self.sandbox, pool_resource=config_set_pool_resource) + config_set_pool_data = config_set_pool_manager.pool_data + config_path = root_path + resource.alias + '_' + resource.model.replace(' ','_') + '.cfg' + config_path = config_path.replace(' ', '_') + # Look for a template config file + tmp_template_config_file = tempfile.NamedTemporaryFile(delete=False) + tftp_template_config_path = root_path + resource.alias + '_' + resource.model + '.tm' + tftp_template_config_path = tftp_template_config_path.replace(' ', '_') + # try: + # look for a concrete config file try: - self.storage_mgr.download(tftp_template_config_path, tmp_template_config_file.name) + self.storage_mgr.download(config_path, tmp_template_config_file.name) + tmp_template_config_file.close() + os.unlink(tmp_template_config_file.name) + # if no concrete file, delete the look for a template file except: - # look for a generic config file for the model - tftp_template_config_path = root_path + resource.model + '.tm' - tftp_template_config_path = tftp_template_config_path.replace(' ', '_') - self.storage_mgr.download(tftp_template_config_path, tmp_template_config_file.name) - with open(tmp_template_config_file.name, 'r') as content_file: - tmp_template_config_file_data = content_file.read() - - concrete_config_data = '' - try: - concrete_config_data = config_file_mgr.create_concrete_config_from_template( - tmp_template_config_file_data, - config_set_pool_data, - self.sandbox, resource) - except QualiError as qe: - self.sandbox.report_error(error_message='Could not create a concrete config file ' - 'for resource {0}'.format(resource.name), - log_message=qe.message, - write_to_output_window=True) - - tmp_concrete_config_file = tempfile.NamedTemporaryFile(delete=False) - tf = file(tmp_concrete_config_file.name, 'wb+') - tf.write(concrete_config_data) - tf.flush() - tf.close() - tmp_template_config_file.close() - os.unlink(tmp_template_config_file.name) - short_Reservation_id = self.sandbox.id[len(self.sandbox.id) - 4:len(self.sandbox.id)] - concrete_file_path = root_path + 'temp/' + short_Reservation_id + '_' + resource.alias + \ - '_' + resource.model + '.cfg' - concrete_file_path = concrete_file_path.replace(' ', '_') - # TODO - clean the temp dir on the tftp server - self.storage_mgr.upload(concrete_file_path, tmp_concrete_config_file.name) - tmp_concrete_config_file.close() - os.unlink(tmp_concrete_config_file.name) - # Set the path to the new concrete file - config_path = concrete_file_path + try: + self.storage_mgr.download(tftp_template_config_path, tmp_template_config_file.name) + except Exception as ex: + self.sandbox.report_info(ex.message) + # look for a generic config file for the model + tftp_template_config_path = root_path + resource.model + '.tm' + tftp_template_config_path = tftp_template_config_path.replace(' ', '_') + self.storage_mgr.download(tftp_template_config_path, tmp_template_config_file.name) + with open(tmp_template_config_file.name, 'r') as content_file: + tmp_template_config_file_data = content_file.read() + + concrete_config_data = '' + + try: + concrete_config_data = config_file_mgr.create_concrete_config_from_template( + tmp_template_config_file_data, + config_set_pool_data, + self.sandbox, resource) + except QualiError as qe: + self.sandbox.report_error(error_message='Could not create a concrete config file ' + 'for resource {0}'.format(resource.name), + log_message=qe.message, + write_to_output_window=True) + + tmp_concrete_config_file = tempfile.NamedTemporaryFile(delete=False) + tf = file(tmp_concrete_config_file.name, 'wb+') + tf.write(concrete_config_data) + tf.flush() + tf.close() + tmp_template_config_file.close() + os.unlink(tmp_template_config_file.name) + short_Reservation_id = self.sandbox.id[len(self.sandbox.id) - 4:len(self.sandbox.id)] + concrete_file_path = root_path + 'temp/' + short_Reservation_id + '_' + resource.alias + \ + '_' + resource.model + '.cfg' + concrete_file_path = concrete_file_path.replace(' ', '_') + # TODO - clean the temp dir on the tftp server + self.storage_mgr.upload(concrete_file_path, tmp_concrete_config_file.name) + tmp_concrete_config_file.close() + os.unlink(tmp_concrete_config_file.name) + # Set the path to the new concrete file + config_path = concrete_file_path return config_path @@ -352,10 +440,14 @@ def save_config(self, snapshot_name, config_type, ignore_models=None, write_to_o env_dir = "" try: env_dir = self.config_files_root + '/Snapshots/' + snapshot_name.strip() - if not self.storage_mgr.dir_exist(env_dir): + if self.storage_mgr.dir_exist(env_dir): + self.sandbox.report_info("Directory for snapshot already exists:\n" + env_dir, + write_to_output_window=True) + else: self.storage_mgr.create_dir(env_dir, write_to_output=True) + # self.sandbox.report_info("Configs saving to\n" + env_dir, write_to_output_window=True) except QualiError as e: - self.sandbox.report_error("Save snapshot failed. " + str(e), + self.sandbox.report_error("Save snapshot failed attempting to make dir. " + str(e), write_to_output_window=write_to_output, raise_error=True) root_resources = self.sandbox.get_root_networking_resources() @@ -377,7 +469,7 @@ def save_config(self, snapshot_name, config_type, ignore_models=None, write_to_o self.sandbox.report_error(res.message, raise_error=False, send_email=True) elif res.message != '': - self.sandbox.report_info(res.resource_name + "\n" + res.message) + self.sandbox.report_info(res.message, write_to_output_window=True) # ---------------------------------- # ---------------------------------- @@ -389,23 +481,13 @@ def _run_asynch_save(self, resource, snapshot_dir, config_type, lock, ignore_mod save_config_for_device = self._is_load_config_to_device(resource, ignore_models=ignore_models) if save_config_for_device: try: - message += '\nSaving configuration for device: ' + resource.name - if resource.has_command('orchestration_save'): - config_path = snapshot_dir.replace('\\', '/') - saved_artifact_info = resource.orchestration_save(self.sandbox.id, config_path, config_type) - if saved_artifact_info != "": - dest_name = resource.name + '_' + resource.model + '_artifact.txt' - dest_name = dest_name.replace(' ', '-') - with lock: - self.storage_mgr.save_artifact_info(saved_artifact_info, config_path, dest_name, - write_to_output=True) - else: - file_name = resource.save_network_config(self.sandbox.id, snapshot_dir, config_type) - # rename file on the storage server - file_path = snapshot_dir + '/' + file_name - to_name = resource.name + '_' + resource.model + '.cfg' - with lock: - self.storage_mgr.rename_file(file_path, to_name) + file_name = resource.save_network_config(self.sandbox.id, snapshot_dir, config_type) + # rename file on the storage server + file_path = snapshot_dir + '/' + file_name + to_name = resource.name + '_' + resource.model + '.cfg' + with lock: + self.storage_mgr.rename_file(file_path, to_name) + message += 'Saved config for device: ' + resource.name except QualiError as qe: save_result.run_result = False @@ -447,6 +529,14 @@ def _is_load_config_to_device(self, resource, ignore_models=None): if resource.model.lower() == ignore_model.lower(): return False + return True + + def _is_power_ctrl_ok(self, resource, ignore_models=None): + if ignore_models: + for ignore_model in ignore_models: + if resource.model.lower() == ignore_model.lower(): + return False + apps = self.sandbox.get_Apps_resources() for app in apps: if app.Name == resource.name: diff --git a/sandbox_scripts/helpers/Networking/save_restore_mgr.py b/sandbox_scripts/helpers/Networking/save_restore_mgr.py index dc219b5..1552464 100644 --- a/sandbox_scripts/helpers/Networking/save_restore_mgr.py +++ b/sandbox_scripts/helpers/Networking/save_restore_mgr.py @@ -20,7 +20,8 @@ def load_config(self, config_stage, config_type, restore_method="Override", conf ignore_models=ignore_models, write_to_output=write_to_output, remove_temp_files=remove_temp_files, - use_Config_file_path_attr=use_Config_file_path_attr) + use_Config_file_path_attr=use_Config_file_path_attr, + in_teardown_mode=in_teardown_mode) self.vm_save_restore.load_config(config_stage=config_stage, config_set_name=config_set_name, ignore_models=ignore_models,write_to_output=write_to_output, @@ -33,6 +34,8 @@ def save_config(self, snapshot_name, config_type, ignore_models=None, write_to_o self.networking_save_restore.save_config(snapshot_name=snapshot_name,config_type=config_type, ignore_models=ignore_models,write_to_output=write_to_output) + + self.vm_save_restore.save_config(snapshot_name=snapshot_name , config_type=config_type, ignore_models=ignore_models, write_to_output=write_to_output) diff --git a/sandbox_scripts/helpers/Networking/vm_save_restore.py b/sandbox_scripts/helpers/Networking/vm_save_restore.py index 3f7bac2..292f3a1 100644 --- a/sandbox_scripts/helpers/Networking/vm_save_restore.py +++ b/sandbox_scripts/helpers/Networking/vm_save_restore.py @@ -36,12 +36,11 @@ def load_config(self, config_stage, config_set_name='', ignore_models=None, root_path = root_path + config_set_name.strip() + '/' root_path = root_path.replace(' ', '_') - - self.sandbox.report_info( - "Loading image on the VMs. This action may take some time.",write_to_output_window=True) root_resources = self.sandbox.get_root_vm_resources() """:type : list[ResourceBase]""" if len(root_resources) > 0: + self.sandbox.report_info("Loading image on the VMs. This action may take some time.", + write_to_output_window=True) pool = ThreadPool(len(root_resources)) async_results = [pool.apply_async(self._run_asynch_load, (resource, root_path, ignore_models, in_teardown_mode)) @@ -60,7 +59,7 @@ def load_config(self, config_stage, config_set_name='', ignore_models=None, elif res.message != '': self.sandbox.report_info(res.resource_name + "\n" + res.message) else: - self.sandbox.report_info("No VM resources found to process") + self.sandbox.report_info("No VM resources found to process", write_to_output_window=True) # ---------------------------------- # ---------------------------------- @@ -124,8 +123,10 @@ def save_config(self, snapshot_name, config_type, ignore_models=None, write_to_o env_dir = "" try: - env_dir = self.config_files_root + '/Snapshots/' + snapshot_name.strip() - if not self.storage_mgr.dir_exist(env_dir): + env_dir = self.config_files_root + '/Snapshots/' + snapshot_name + if self.storage_mgr.dir_exist(env_dir): + pass + else: self.storage_mgr.create_dir(env_dir, write_to_output=True) except QualiError as e: self.sandbox.report_error("Save snapshot failed. " + str(e), @@ -133,24 +134,25 @@ def save_config(self, snapshot_name, config_type, ignore_models=None, write_to_o root_resources = self.sandbox.get_root_vm_resources() """:type : list[ResourceBase]""" - pool = ThreadPool(len(root_resources)) - lock = Lock() - async_results = [pool.apply_async(self._run_asynch_save, - (resource, env_dir, config_type, lock, ignore_models)) + if len(root_resources) == 0: + self.sandbox.report_info("No VMs found to snapshot.", write_to_output_window=True) + else: + pool = ThreadPool(len(root_resources)) + lock = Lock() + async_results = [pool.apply_async(self._run_asynch_save, + (resource, env_dir, config_type, lock, ignore_models)) for resource in root_resources] - - pool.close() - pool.join() - for async_result in async_results: - res = async_result.get() - """:type : rsc_run_result_struct""" - if not res.run_result: - err = "Failed to save configuration on device " + res.resource_name - self.sandbox.report_error(err, write_to_output_window=write_to_output, raise_error=False) - self.sandbox.report_error(res.message, raise_error=False) - - elif res.message != '': - self.sandbox.report_info(res.resource_name + "\n" + res.message) + pool.close() + pool.join() + for async_result in async_results: + res = async_result.get() + """:type : rsc_run_result_struct""" + if not res.run_result: + err = "Failed to save configuration on device " + res.resource_name + self.sandbox.report_error(err, write_to_output_window=write_to_output, raise_error=False) + self.sandbox.report_error(res.message, raise_error=False) + elif res.message != '': + self.sandbox.report_info(res.message, write_to_output_window=True) # ---------------------------------- # ----------------------------------