Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
8aec470
minor change to pass where in teardown for support of power control
jimbr70 Jun 26, 2017
4a75aab
changes for adding pwer control
jimbr70 Jun 26, 2017
de5a8fe
changes for power control
jimbr70 Jun 26, 2017
246525a
changes for adding power control
jimbr70 Jun 26, 2017
a9c30b2
changes for power control and reset of live status to honor ignore mo…
jimbr70 Jun 26, 2017
a613c0f
changes to support functions for power control
jimbr70 Jun 26, 2017
9ba9e24
allows for default on ignore models when clearing status
jimbr70 Jun 28, 2017
009f9ca
tests updated to handle use of ignore models on clearing status
jimbr70 Jun 28, 2017
f111875
Changes to get Save Sandpshot and Restore Snapshot to work.
jimbr70 Jul 20, 2017
ec66357
Changes to get Save Sandpshot and Restore Snapshot to work.
jimbr70 Jul 20, 2017
9509708
Changes to get Save Sandpshot and Restore Snapshot to work.
jimbr70 Jul 20, 2017
0efc50a
Changes to get Save Sandpshot and Restore Snapshot to work.
jimbr70 Jul 20, 2017
af1d285
Changes to get Save Sandpshot and Restore Snapshot to work.
jimbr70 Jul 20, 2017
28ffea4
Changes to get Save Sandpshot and Restore Snapshot to work.
jimbr70 Jul 20, 2017
d4b0d36
Changes to get Save Sandpshot and Restore Snapshot to work.
jimbr70 Jul 20, 2017
0424770
Changes to get Save Sandpshot and Restore Snapshot to work.
jimbr70 Jul 20, 2017
ca79008
Changes to get Save Sandpshot and Restore Snapshot to work.
jimbr70 Jul 20, 2017
977d102
Changes to get Save Sandpshot and Restore Snapshot to work.
jimbr70 Jul 20, 2017
e658a45
Changes to get Save Sandpshot and Restore Snapshot to work.
jimbr70 Jul 20, 2017
4672343
Minor changes to resolve save snapshot/restore
jimbr70 Jul 24, 2017
71f53c0
Minor changes to resolve save snapshot/restore
jimbr70 Jul 24, 2017
94a99be
minor imprpovement to the power off at teardown
jimbr70 Jul 25, 2017
0299f9d
clarified error - which resource it applies to
jimbr70 Jul 25, 2017
6c8eb23
mssing newline at EOF
jimbr70 Jul 25, 2017
94a8e57
Added SSIB Model to ignore models.
jimbr70 Aug 14, 2017
c97d940
added newline at end
jimbr70 Aug 14, 2017
e84d954
Changes to handling invalid name appendage to save snapshot. Command …
jimbr70 Sep 5, 2017
33a9de6
Removed teardown premature message
jimbr70 Sep 5, 2017
855328d
Added msg that clear status indicatores is running - else the user po…
jimbr70 Sep 5, 2017
98683dd
changes
jimbr70 Sep 5, 2017
e1c83a1
updated to replace a space in the user-add-on-name with an underscore…
jimbr70 Sep 5, 2017
a8bc138
made changes to health check so that the orchestration controls the c…
jimbr70 Sep 5, 2017
1f60e3f
just two format cleanup items as pycharm was complaining
jimbr70 Sep 5, 2017
04369f9
Added a subst_log variable that is passed in case it is needed in fut…
jimbr70 Sep 27, 2017
fb36872
Changed ending message - removed Successfully as it is not always the…
jimbr70 Sep 27, 2017
8ce48b0
Fixes in a number of places where we did not account for a space in a…
jimbr70 Sep 27, 2017
d5f7dcb
Edits to fix tests to align with returning resource name, mostly
jimbr70 Sep 27, 2017
4f71f5a
Removed us of subst_log variable that was passed back from config fil…
jimbr70 Sep 27, 2017
d58c978
Removed subst_log usage
jimbr70 Sep 27, 2017
5c3d15a
per code revieiw removed repeated setting of ignore models
jimbr70 Oct 5, 2017
1580e38
commented out settings of subst_log. Left in - very valuable to isola…
jimbr70 Oct 5, 2017
28e3e6c
removed unnecessary passing in of resource name per code review
jimbr70 Oct 5, 2017
c9906e5
Added a TODO in case we get an api to change description. Per code re…
jimbr70 Oct 5, 2017
a8f5bbc
per code review
jimbr70 Oct 5, 2017
551c852
per code review
jimbr70 Oct 5, 2017
b05cec3
fixed tests
jimbr70 Oct 6, 2017
c35525a
fixed tests.
jimbr70 Oct 6, 2017
311ff25
fixed tests - all pass
jimbr70 Oct 6, 2017
7d33b7b
repaired
jimbr70 Oct 6, 2017
1f2dfa1
fixed tests.
jimbr70 Oct 6, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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