Skip to content

Commit

Permalink
Merge b05cec3 into e499f30
Browse files Browse the repository at this point in the history
  • Loading branch information
jimbr70 authored Oct 6, 2017
2 parents e499f30 + b05cec3 commit 6d07664
Show file tree
Hide file tree
Showing 15 changed files with 454 additions and 232 deletions.
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
30 changes: 18 additions & 12 deletions sandbox_scripts/QualiEnvironmentUtils/ConfigFileManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand All @@ -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)
Expand All @@ -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):
Expand All @@ -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)

101 changes: 65 additions & 36 deletions sandbox_scripts/QualiEnvironmentUtils/Resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 != "":
Expand All @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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]))

# -----------------------------------------
# -----------------------------------------
Expand Down Expand Up @@ -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:
Expand Down
53 changes: 48 additions & 5 deletions sandbox_scripts/QualiEnvironmentUtils/Sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

# ----------------------------------
# ----------------------------------
Expand Down Expand Up @@ -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()))

# ----------------------------------
# ----------------------------------
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 6d07664

Please sign in to comment.