Skip to content

Commit

Permalink
Merge branch 'develop' into more-tests
Browse files Browse the repository at this point in the history
# Conflicts:
#	sandbox_scripts/QualiEnvironmentUtils/Resource.py
#	sandbox_scripts/QualiEnvironmentUtils/Sandbox.py
#
sandbox_scripts/QualiEnvironmentUtils/tests/DebugInteractive_setup_resou
rces.py
#	sandbox_scripts/environment/setup/setup_VM.py
#	sandbox_scripts/environment/teardown/teardown_VM.py
#	sandbox_scripts/environment/teardown/teardown_resources.py
#	sandbox_scripts/helpers/Networking/NetworkingSaveNRestore.py
#	test_requirements.txt
  • Loading branch information
kalsky committed May 5, 2017
2 parents fc53857 + 381c113 commit 31e0f75
Show file tree
Hide file tree
Showing 15 changed files with 282 additions and 141 deletions.
3 changes: 0 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
after_success: coveralls
install:
- pip install -r ./test_requirements.txt
- pip install "cloudshell-core>=2.0.0,<2.1.0"
- pip install "cloudshell-automation-api>=7.1,<7.3"
- pip install "cloudshell-shell-core>=2.3.0,<2.4.0"
- pip install pytest-cov
language: python
notifications:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import pip

try:
import gitlab
imported_gitlab = True
import gitlab
except:
try:
pip.main(["install","pyapi-gitlab"])
Expand Down Expand Up @@ -58,7 +58,6 @@ def download(self, source, destination):
try:
with open(destination,'w') as dest:
dest.write(filetext)
print "Downloaded: " + source
return 0, "Successfully retrieved file from repository and saved to destination"
except Exception as ex:
raise QualiError("GitLabClient","ERROR: Retrieved file from repository - failed to save to destination : " + str(ex.message))
27 changes: 18 additions & 9 deletions sandbox_scripts/QualiEnvironmentUtils/Resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def __init__(self, resource_name, resource_alias=''):
self.connected_commands = self.api_session.GetResourceConnectedCommands(resource_name).Commands

self.attributes = self.details.ResourceAttributes
# If there is an attribute named 'model' take its value (exist in shells), otherwise take the family's model
# If there is an attribute 'named' 'model' use its value, otherwise take the family's model
if self.attribute_exist('Model'):
self.model = self.get_attribute('Model')
else:
Expand All @@ -45,7 +45,7 @@ def has_command(self, command_name):
def attribute_exist(self, attribute_name):
attribute_name = attribute_name.lower()
for attribute in self.attributes:
if attribute.Name.lower() == attribute_name:
if attribute.Name.lower() == attribute_name or attribute.Name.lower().endswith('.' + attribute_name):
return True
return False

Expand All @@ -54,7 +54,7 @@ def attribute_exist(self, attribute_name):
def get_attribute(self, attribute_name):
attribute_name_lower = attribute_name.lower()
for attribute in self.attributes:
if attribute.Name.lower() == attribute_name_lower:
if attribute.Name.lower() == attribute_name_lower or attribute.Name.lower().endswith('.' + attribute_name_lower):
if attribute.Type == 'Password':
decrypted = self.api_session.DecryptPassword(attribute.Value)
return decrypted.Value
Expand All @@ -65,11 +65,17 @@ def get_attribute(self, attribute_name):
# -----------------------------------------
# -----------------------------------------
def set_attribute_value(self, attribute_name, attribute_value):
# if caller passes ending string of name, need to handle not knowing prefix
try:
self.api_session.SetAttributeValue(resourceFullPath=self.name, attributeName=attribute_name,
attributeValue=attribute_value)
attribute_name = attribute_name.lower()
for attribute in self.attributes:
if attribute.Name.lower() == attribute_name or attribute.Name.lower().endswith('.' + attribute_name):
self.api_session.SetAttributeValue(resourceFullPath=self.name,
attributeName=attribute.Name,
attributeValue=attribute_value)
return
except CloudShellAPIError as error:
raise QualiError(self.name, "Failed to set attribute: '" + attribute_name + "'. " + error.message)
raise QualiError(self.name, "Failed to set attribute named or ending-with '" + attribute_name + "'. " + error.message)

# -----------------------------------------
# implement the command to get the neighbors and their ports
Expand Down Expand Up @@ -130,7 +136,7 @@ def load_network_config(self, reservation_id, config_path, config_type, restore_
for parm in command.Parameters:
if parm.Name in ["path", "src_Path"]:
the_path = parm.Name
elif parm.Name in ["configuration_type","config_type"]:
elif parm.Name in ["configuration_type", "config_type"]:
the_cfgtype = parm.Name
elif parm.Name in ["restore_method"]:
the_restoremeth = parm.Name
Expand Down Expand Up @@ -161,7 +167,10 @@ def load_network_config(self, reservation_id, config_path, config_type, restore_
raise QualiError(self.name, "Failed to load configuration: " + qerror.message)

except Exception as e:
raise QualiError(self.name, "Failed to load configuration. Unexpected error:" + e.message)
if type(e) == QualiError:
raise e
else:
raise QualiError(self.name, "Failed to load configuration. Unexpected error:" + e.message)

# -----------------------------------------
# -----------------------------------------
Expand Down Expand Up @@ -351,7 +360,7 @@ def execute_connected_command(self, reservation_id, commandName,tag,commandInput
return self.api_session.ExecuteResourceConnectedCommand(reservation_id,self.name,
commandName=commandName,
commandTag=tag,
parameterValues = commandInputs)
parameterValues=commandInputs)

except CloudShellAPIError as error:
raise QualiError(self.name, error.message)
Expand Down
113 changes: 93 additions & 20 deletions sandbox_scripts/QualiEnvironmentUtils/Sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from cloudshell.helpers.scripts import cloudshell_scripts_helpers as helpers
from cloudshell.api.common_cloudshell_api import CloudShellAPIError
from os.path import *

from time import gmtime, strftime
import smtplib
from email.mime.text import MIMEText

SEVERITY_INFO = 20
SEVERITY_ERROR = 40
Expand All @@ -21,9 +23,11 @@ def __init__(self, reservation_id, logger):
""":type : logging.Logger"""
self.api_session = helpers.get_api_session()
self.id = reservation_id

self.Blueprint_name = helpers.get_reservation_context_details().environment_name

context = helpers.get_reservation_context_details()
self.owner = context.owner_user
self.Blueprint_name = context.environment_name
if self.Blueprint_name == '':
raise QualiError("Blueprint name empty (from env name)")

full_path = None
tp = self.api_session.GetActiveTopologyNames()
Expand All @@ -37,9 +41,9 @@ def __init__(self, reservation_id, logger):
if full_path:
self.blueprint_details = self.api_session.GetTopologyDetails(full_path)

except:
except Exception as e:
err = "Failed to initialize the Sandbox. Unexpected error:" + \
str(sys.exc_info()[0])
e.message
self.report_error(error_message=err)

# ----------------------------------
Expand All @@ -54,19 +58,41 @@ def _write_message_to_output(self, message, severity_level=SEVERITY_INFO):
self.api_session.WriteMessageToReservationOutput(self.id, '<font color="red">' + message + '</font>')

# ----------------------------------
def report_error(self, error_message, log_message=None, raise_error=True, write_to_output_window=False):
def report_error(self, error_message, log_message=None, raise_error=True, write_to_output_window=False,
send_email=False):
"""
Report on an error to the log file, output window is optional.There is also an option to raise the error up
:param str error_message: The error message you would like to present
:param str log_message: The error message you would like to write to the log. Keep None to use the message param
:param bool raise_error: Do you want to throw an exception
:param bool write_to_output_window: Would you like to write the message to the output window
"""

emailresult = ''
if raise_error and send_email:
emailOwner = False
try:
emailSubject = str(self.Blueprint_name) + ' / ' + str(self.owner)
emailBody = "Sandbox: " + str(self.Blueprint_name) + "\n" + \
"Owner: " + str(self.owner) + "\n\n"
sb_owner = str(self.owner)
except:
emailSubject = "Catastrophic ERROR in sandbox."
emailBody = str(os.environ) + "\n\n"
sb_owner = "Owner Unknown"

if log_message:
emailBody += "LogMsg: " + log_message + "\n\n"
if error_message:
emailBody += "ErrMsg: " + error_message + "\n\n"
emailresult = self._emailalert(emailSubject, emailBody, owner=sb_owner, ishtml=False,
emailOwner=emailOwner) + "\n"

if self._logger:
if log_message:
self._logger.error(log_message)
self._logger.error(emailresult + log_message)
else:
self._logger.error(error_message)
self._logger.error(emailresult + error_message)
if write_to_output_window:
self._write_message_to_output(error_message, SEVERITY_ERROR)
if raise_error:
Expand All @@ -88,6 +114,52 @@ def report_info(self, message, log_message=None, write_to_output_window=False):
if write_to_output_window:
self._write_message_to_output(message, SEVERITY_INFO)

# ----------------------------------
def _emailalert(self, subject, body, owner, ishtml=False, emailOwner=False):
try:
globalsresource = self.get_config_set_pool_resource()
host = str(globalsresource.get_attribute("ConfigPool_SMTP_Server"))
port = str(globalsresource.get_attribute("ConfigPool_SMTP_port"))
emailfrom = str(globalsresource.get_attribute("ConfigPool_SMTP_from"))
emailto = emailfrom
emailcc = ''
emailbcc = ''

if emailOwner:
try:
emailto = str(self.api_session.GetUserDetails(owner).Email)
emailbcc = emailfrom
body += "----\n A copy of this email was also sent to our support staff."
except:
emailbcc = ''
emailto = emailfrom

try:
if ishtml:
emsg = MIMEText(body + '\n\n', 'html')
else:
emsg = MIMEText(body + '\n\n', 'plain')

emsg['Subject'] = subject
emsg['From'] = emailfrom
emsg['To'] = ",".join([emailto])
emsg['CC'] = ""
emsg.preamble = subject
tolist = emailto.split(",") + emailcc.split(",") + emailbcc.split(",")
mailer = smtplib.SMTP(host=host, port=port)
mailer.sendmail(emailfrom, tolist, emsg.as_string())
return "Emailed OK"

except smtplib.SMTPException as e:
# cannot post again as error or we could be in a loop!
return ("ERROR Failed to send email, %s" % str(e))
except:
# cannot post again as error or we could be in a loop!
return "ERROR Failed to send email(1)"
except:
return "ERROR Failed to send email(2)"


# ----------------------------------
def get_root_resources(self):
"""
Expand Down Expand Up @@ -129,8 +201,8 @@ def get_root_vm_resources(self):
resources = details.ReservationDescription.Resources
# Loop over all devices in the sandbox and add to a dictionary all root devices of VM type:
for resource in resources:
#resource_details = self.api_session.GetResourceDetails(resource.Name)
if resource.VmDetails and hasattr(resource.VmDetails,'UID') and resource.VmDetails.UID:
# resource_details = self.api_session.GetResourceDetails(resource.Name)
if resource.VmDetails and hasattr(resource.VmDetails, 'UID') and resource.VmDetails.UID:
split_name = resource.Name.split('/')
root_resources_names_dict[split_name[0]] = 1
root_resources.append(ResourceBase(resource.Name, ''))
Expand All @@ -151,7 +223,7 @@ def get_root_networking_resources(self):
topo_resources = details.ReservationDescription.TopologiesReservedResources
# Loop over all devices in the sandbox and add to a dictionary all root devices of type networking devices:
for resource in resources:
if not(resource.VmDetails and hasattr(resource.VmDetails,'UID') and resource.VmDetails.UID):
if not (resource.VmDetails and hasattr(resource.VmDetails, 'UID') and resource.VmDetails.UID):
split_name = resource.Name.split('/')
root_resources_names_dict[split_name[0]] = 1

Expand All @@ -174,7 +246,8 @@ def clear_all_resources_live_status(self):
"""
root_resources = self.get_root_resources()
for resource in root_resources:
self.api_session.SetResourceLiveStatus(resource.name, '')
self.api_session.SetResourceLiveStatus(resource.name, liveStatusName="Info",
additionalInfo='status cleared ' + strftime("%H:%M:%S", gmtime()))

# ----------------------------------
# ----------------------------------
Expand Down Expand Up @@ -314,10 +387,11 @@ def enqueue_command(self, commandName, commandInputs=[], printOutput=False):
# -----------------------------------------
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
# TODO - fullpath should be passed as a param to the function and not hard coded
# save the current Sandbox as a new Blueprint with the given snapshot name
fullpath = 'Snapshots'
self.api_session.SaveReservationAsTopology(self.id, topologyName=blueprint_name,folderFullPath=fullpath, includeInactiveRoutes=True)
self.api_session.SaveReservationAsTopology(self.id, topologyName=blueprint_name, folderFullPath=fullpath,
includeInactiveRoutes=True)

except CloudShellAPIError as error:
err = "Failed to save sandbox as blueprint. " + error.message
Expand All @@ -327,18 +401,18 @@ def save_sandbox_as_blueprint(self, blueprint_name, write_to_output=True):
# self.report_error(error_message=err, write_to_output_window=write_to_output)
# raise Exception('Blueprint name already exist. Please select a different name.')

#update the new snapshot with the user as owner
# update the new snapshot with the user as owner
username = helpers.get_reservation_context_details().owner_user
fullTopologyName = 'Snapshots/'+blueprint_name
self.api_session.UpdateTopologyOwner(topologyName=fullTopologyName,ownerName=username)
fullTopologyName = 'Snapshots/' + blueprint_name
self.api_session.UpdateTopologyOwner(topologyName=fullTopologyName, ownerName=username)

# -----------------------------------------
# check if this resource originated from an abstract resource
# -----------------------------------------
def is_abstract(self, resource_alias):
for abstract_resource in self.blueprint_details.AbstractResources:
if resource_alias == abstract_resource.Alias:
return True
return True
return False

# -----------------------------------------
Expand Down Expand Up @@ -404,7 +478,6 @@ def is_apps_in_reservation(self):
# Power on VMs
# ----------------------------------
def power_on_vms(self, write_to_output=True):

root_resources = self.get_root_resources()

for resource in root_resources:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
import tempfile
import pip


try:
import tftpy
imported_tftpy = True
import tftpy
except:
try:
pip.main(["install","tftpy"])
Expand Down
6 changes: 6 additions & 0 deletions sandbox_scripts/QualiEnvironmentUtils/StorageManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ def __init__(self, sandbox ):

# ----------------------------------
# ----------------------------------
#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':
# return FTPClient(self.sandbox,storage_resource)

def _get_storage_client(self, storage_resource):
if storage_resource.model.lower() == 'generic tftp server':
return TFTPClient(self.sandbox,storage_resource)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
dev_helpers.attach_to_cloudshell_as(user="admin", password="admin", domain="Global",
reservation_id="c04d3da4-8025-4efe-9f4d-820ba19d20af",
server_address="localhost")
#os.environ["environment_name"] = "Abstract-ALL"
os.environ["environment_name"] = "Abstract-ALL"

x = EnvironmentSetupResources()
x.execute()
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import cloudshell.helpers.scripts.cloudshell_dev_helpers as dev_helpers
from sandbox_scripts.environment.teardown.teardown_resources import *

dev_helpers.attach_to_cloudshell_as(user="admin", password="xx", domain="Global",
reservation_id="bc0517e5-7240-4184-b04d-19e755f9c9a7",
server_address="svl-dev-quali")

x = EnvironmentTeardownResources()
x.execute()
9 changes: 6 additions & 3 deletions sandbox_scripts/QualiEnvironmentUtils/tests/test_Sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
import json
import os
from cloudshell.api.common_cloudshell_api import CloudShellAPIError
from freezegun import freeze_time

resContext = '''{"id":"5487c6ce-d0b3-43e9-8ee7-e27af8406905",
"ownerUser":"bob",
"ownerPass":"nIqm+BG6ZGJjby5hUittVFFJASc=",
"domain":"Global",
"environmentName":"My environment",
"environmentPath":"My environment",
"description":"New demo environment",
"parameters":
{ "globalInputs": [],
Expand Down Expand Up @@ -202,9 +204,10 @@ def test_clear_all_resources_live_status_two_devices(self,mock_resourcebase):
rr = [resource1, resource2]
self.sandbox.get_root_resources = Mock(return_value=rr)

self.sandbox.clear_all_resources_live_status()
calls = [call('r1', ''),
call('r2', '')]
with freeze_time("2017-01-17 12:00:01"):
self.sandbox.clear_all_resources_live_status()
calls = [call('r1', additionalInfo='status cleared 12:00:01', liveStatusName='Info'),
call('r2', additionalInfo='status cleared 12:00:01', liveStatusName='Info')]
self.mock_api_session.return_value.SetResourceLiveStatus.assert_has_calls(calls)

#================================================================
Expand Down
Loading

0 comments on commit 31e0f75

Please sign in to comment.