From 710c07bd540e24d91f027bc4a5f28c7246636495 Mon Sep 17 00:00:00 2001 From: sahanaspc Date: Tue, 1 May 2018 19:04:56 +0000 Subject: [PATCH] [DM] Sanity test for image upgrade Sanity test for the entire image upgrade workflow Change-Id: I0035b19511440b4f18279cd5b3c6be49efe512d8 Closes-Bug: #1768159 --- .../ansible-playbooks/test-requirements.txt | 2 + .../test/sanity/config/image_config.yml | 18 ++ .../test/sanity/images/.gitignore | 4 + .../test/sanity/sanity_base.py | 102 +++++++++++ .../test/sanity/sanity_test01.py | 25 ++- .../test/sanity/sanity_test02.py | 164 ++++++++++++++++++ 6 files changed, 313 insertions(+), 2 deletions(-) create mode 100644 src/config/fabric-ansible/ansible-playbooks/test/sanity/config/image_config.yml create mode 100644 src/config/fabric-ansible/ansible-playbooks/test/sanity/images/.gitignore create mode 100644 src/config/fabric-ansible/ansible-playbooks/test/sanity/sanity_test02.py diff --git a/src/config/fabric-ansible/ansible-playbooks/test-requirements.txt b/src/config/fabric-ansible/ansible-playbooks/test-requirements.txt index e6731742f67..620757d4f2e 100644 --- a/src/config/fabric-ansible/ansible-playbooks/test-requirements.txt +++ b/src/config/fabric-ansible/ansible-playbooks/test-requirements.txt @@ -4,9 +4,11 @@ flexmock coverage pyaml cfgm_common +fabric_ansible vnc_api sandesh sandesh_common junitxml timeout-decorator requests +python-swiftclient diff --git a/src/config/fabric-ansible/ansible-playbooks/test/sanity/config/image_config.yml b/src/config/fabric-ansible/ansible-playbooks/test/sanity/config/image_config.yml new file mode 100644 index 00000000000..8ec29d57419 --- /dev/null +++ b/src/config/fabric-ansible/ansible-playbooks/test/sanity/config/image_config.yml @@ -0,0 +1,18 @@ +swift: + keystone_ip: 10.155.75.141 + keystone_port: 35357 + auth_version: 3 + temp_url_key: mykey + temp_url_key_2: mykey2 + chosen_temp_url_key: temp_url_key + container_name: contrail_container + connection_retry_count: 5 +image: + image_name: jinstall-host-qfx-10-f-flex-15.1X53-D63.9-domestic-signed.tgz + image_version: 15.1X53-D63.9 + image_family_name: juniper-qfx + image_vendor_name: juniper + device_family_name: juniper-qfx + device_vendor_name: juniper + + diff --git a/src/config/fabric-ansible/ansible-playbooks/test/sanity/images/.gitignore b/src/config/fabric-ansible/ansible-playbooks/test/sanity/images/.gitignore new file mode 100644 index 00000000000..5e7d2734cfc --- /dev/null +++ b/src/config/fabric-ansible/ansible-playbooks/test/sanity/images/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/src/config/fabric-ansible/ansible-playbooks/test/sanity/sanity_base.py b/src/config/fabric-ansible/ansible-playbooks/test/sanity/sanity_base.py index d65766d3655..bbb4a94288f 100644 --- a/src/config/fabric-ansible/ansible-playbooks/test/sanity/sanity_base.py +++ b/src/config/fabric-ansible/ansible-playbooks/test/sanity/sanity_base.py @@ -21,6 +21,8 @@ from vnc_api.vnc_api import VncApi from vnc_api.gen.resource_client import Fabric from vnc_api.gen.resource_client import FabricNamespace +from vnc_api.gen.resource_client import DeviceImage +from vnc_api.gen.resource_client import PhysicalRouter # pylint: disable=E1101 @@ -179,6 +181,64 @@ def add_asn_namespace(self, fab, asn): return namespace # end _add_asn_namespace + def create_image_and_device(self, img_name, img_uri, img_version, + img_family, img_vendor, device_family, + device_vendor, prouter_ip, prouter_password, device_name): + + """create image and physical device""" + img_fqname = None + device_fqname = None + try: + self._logger.info('Creating image: %s', img_name) + img_fqname = ['default-global-system-config', img_name] + image = DeviceImage( + name=img_name, + fq_name=img_fqname, + parent_type='global-system-config', + device_image_file_uri=img_uri, + device_image_os_version=img_version, + device_image_device_family=img_family, + device_image_vendor_name=img_vendor + ) + img_uuid = self._api.device_image_create(image) + image = self._api.device_image_read(id=img_uuid) + + except RefsExistError: + self._logger.warn("Image '%s' already exists", img_name) + image = self._api.device_image_read(fq_name=img_fqname) + + self._logger.debug( + "Image created:\n%s", + pprint.pformat(self._api.obj_to_dict(image), indent=4)) + + try: + self._logger.info('Creating device: %s', device_name) + device_fqname = ['default-global-system-config', device_name] + device = PhysicalRouter( + name=device_name, + fq_name=device_fqname, + physical_router_device_family=device_family, + physical_router_management_ip=prouter_ip, + physical_router_vendor_name=device_vendor, + physical_router_user_credentials={ + 'username': 'root', 'password': prouter_password + } + ) + device_uuid = self._api.physical_router_create(device) + device = self._api.physical_router_read(id=device_uuid) + + except RefsExistError: + self._logger.warn("Device '%s' already exists", device_name) + device = self._api.physical_router_read(fq_name=device_fqname) + + self._logger.debug( + "Device created:\n%s", + pprint.pformat(self._api.obj_to_dict(device), indent=4)) + + return image, device + + # end create_image_and_device + def cleanup_fabric(self, fab_name): """delete fabric including all prouters in the fabric""" try: @@ -206,6 +266,31 @@ def cleanup_fabric(self, fab_name): self._logger.warn('Fabric "%s" not found', fab_name) # end cleanup_fabric + def cleanup_image_prouter(self, img_name, device_name): + # image cleanup + self._logger.info("Clean up image and prouter from db") + try: + img_fqname = ['default-global-system-config', img_name] + img = self._api.device_image_read(fq_name=img_fqname) + self._logger.debug( + "Delete Image: %s", img_fqname) + self._api.device_image_delete(img_fqname) + + except NoIdError: + self._logger.warn('Image "%s" not found', img_name) + + # device cleanup + try: + device_fqname = ['default-global-system-config', device_name] + device = self._api.physical_router_read(fq_name=device_fqname) + self._logger.debug( + "Delete Physical Router: %s", device_fqname) + self._api.physical_router_delete(device_fqname) + except NoIdError: + self._logger.warn('Device "%s" not found', device_name) + + # end cleanup_image_prouter + def _delete_prouter(self, uuid): prouter = self._api.physical_router_read(id=uuid) @@ -369,6 +454,22 @@ def underlay_config(self, prouters): self._wait_for_job_to_finish('Underlay config', job_execution_id) # end underlay_config + def image_upgrade(self, image, device): + """upgrade the physical routers with specified images""" + self._logger.info("Upgrade image on the physical router ...") + job_execution_info = self._api.execute_job( + job_template_fq_name=[ + 'default-global-system-config', 'image_upgrade_template'], + job_input={'image_uuid': image.uuid}, + device_list=[device.uuid] + ) + job_execution_id = job_execution_info.get('job_execution_id') + self._logger.info( + "Image upgrade job started with execution id: %s", job_execution_id) + self._wait_for_job_to_finish('Image upgrade', job_execution_id) + + # end image_upgrade + def _exit_with_error(self, errmsg): self._logger.error(errmsg) sys.exit(1) @@ -376,3 +477,4 @@ def _exit_with_error(self, errmsg): # end SanityBase class + diff --git a/src/config/fabric-ansible/ansible-playbooks/test/sanity/sanity_test01.py b/src/config/fabric-ansible/ansible-playbooks/test/sanity/sanity_test01.py index c884ffab866..dc915508e56 100644 --- a/src/config/fabric-ansible/ansible-playbooks/test/sanity/sanity_test01.py +++ b/src/config/fabric-ansible/ansible-playbooks/test/sanity/sanity_test01.py @@ -38,20 +38,40 @@ def _validate_discovered_prouters(self, prouters): assert prouters[i].physical_router_vendor_name == 'juniper' assert prouters[i].physical_router_product_name assert prouters[i].physical_router_device_family + assert (prouters[i].physical_router_management_ip + == self._prouter['ips'][i]), ("The IP address of the discovered" + " device {}, does not match the given IP address range,{}." + .format(prouters[i].physical_router_management_ip, + self._prouter['ips'][i])) + assert (prouters[i].physical_router_user_credentials.username + == 'root'), ("The username of the discovered" + " device {}, does not match the configured username, {}." + .format(prouters[i].physical_router_user_credentials.username,'root')) + assert (prouters[i].physical_router_vendor_name == 'Juniper'),("The vendor name of the discovered" + " device {}, does not match the configured vendor name, Juniper." + .format(prouters[i].physical_router_vendor_name)) + assert (prouters[i].physical_router_product_name),("The discovered" + " device does not contain a product name.") + assert (prouters[i].physical_router_device_family),("The discovered" + " device does not have a device family.") + # end _validate_discovered_prouters def _validate_imported_prouters(self, prouters): for prouter in prouters: ifd_refs = self._api.physical_interfaces_list( parent_id=prouter.uuid) - assert ifd_refs.get('physical-interfaces') > 0 + assert (ifd_refs.get('physical-interfaces') > 0),("No Physical interfaces" + "were imported for the given device.") + # end _validate_imported_prouters def _validate_underlay_config(self, prouters): for prouter in prouters: prouter = self._api.physical_router_read(prouter.fq_name) bgp_router_refs = prouter.get_bgp_router_refs() or [] - assert len(bgp_router_refs) == 1 + assert (len(bgp_router_refs) == 1), ("No bgp router references " + "were created for the discovered device {}.".format(prouter)) # end _validate_underlay_config def test(self): @@ -81,3 +101,4 @@ def test(self): if __name__ == "__main__": SanityTest01(config.load('config/test_config.yml')).test() # end __main__ + diff --git a/src/config/fabric-ansible/ansible-playbooks/test/sanity/sanity_test02.py b/src/config/fabric-ansible/ansible-playbooks/test/sanity/sanity_test02.py new file mode 100644 index 00000000000..e72151df932 --- /dev/null +++ b/src/config/fabric-ansible/ansible-playbooks/test/sanity/sanity_test02.py @@ -0,0 +1,164 @@ +""" +This file contains sanity test for image upgrade workflow +""" +import sys +import hashlib +import config +import re + +class FabricAnsibleModule: + pass + +sys.path.append('../..') +sys.modules['ansible.module_utils.fabric_utils'] = __import__('sanity_test02') +from library.swift_fileutil import FileSvcUtil +from sanity_base import SanityBase + +class SwiftFileUtil(FileSvcUtil): + + def createObjectFile(self, filename, tempfilepath, md5, sha1): + try: + meta = self.getObjectFileMeta(filename) + if meta is not None and meta['etag'] == md5: + file_uri = self.getobjectFileUri(filename) + return file_uri + except Exception: + pass + + with open(tempfilepath, 'rb') as f: + self.swift_conn.put_object(container=self.container_name, + obj=filename, contents=f, + chunk_size=65536, + headers={'X-Object-Meta-md5': md5, + 'X-Object-Meta-sha1': sha1}) + # get object meta data to verify + meta = self.getObjectFileMeta(filename) + file_uri = None + if meta is not None and meta['etag'] == md5: + file_uri = self.getobjectFileUri(filename) + return file_uri + + # Delete the file from the container + def deleteObjectFile(self, filename): + self.swift_conn.delete_object(container=self.container_name, + obj=filename) + + def getObjectFileMeta(self, filename): + return self.swift_conn.head_object(container=self.container_name, + obj=filename) + + +# pylint: disable=E1101 +class SanityTest02(SanityBase): + """ + Sanity test for image upgrade workflow: + """ + + def __init__(self, cfg): + SanityBase.__init__(self, cfg, "sanity_test_02") + self._prouter_ip = cfg['prouter']['ips'][0] + self._prouter_password = cfg['prouter']['passwords'][0] + self._device_name = 'mxdev' + self._swift_params = cfg['swift'] + self._image_details = cfg['image'] + self._keystone_ip = self._swift_params['keystone_ip'] + self._port = self._swift_params['keystone_port'] + self._image_name = self._image_details['image_name'] + self._image_version = self._image_details['image_version'] + self._image_family_name = self._image_details['image_family_name'] + self._image_vendor_name = self._image_details['image_vendor_name'] + self._device_family_name = self._image_details['device_family_name'] + self._device_vendor_name = self._image_details['device_vendor_name'] + self._auth_url = 'http://' + self._keystone_ip + ':' + str(self._port) \ + + '/v3' + try: + self._logger.debug("Connecting to swift") + self._fileutilobj = SwiftFileUtil("", self._auth_url, + self._api_server['username'], + self._api_server['password'], + self._api_server['tenant'], + self._swift_params[ + 'auth_version'], + self._swift_params[ + 'container_name'], + self._swift_params[ + 'temp_url_key'], + self._swift_params[ + 'temp_url_key_2'], + self._swift_params[ + 'connection_retry_count'], + self._swift_params[ + 'chosen_temp_url_key'] + ) + except Exception as e: + self._exit_with_error( + "Test failed due to exception while connecting to swift: %s" % + str(e)) + + # end __init__ + + def test_image_upgrade(self): + # upload image file to swift + img_uri = None + try: + temp_file = 'images/' + self._image_name + md5 = self._getmd5(temp_file) + sha1 = self._getsha1(temp_file) + self._logger.info("Uploading image to swift") + img_uri = self._fileutilobj.createObjectFile(self._image_name, + temp_file, md5, sha1) + if not img_uri: + self._exit_with_error( + "Test failed due to swift uri being None after upload") + except Exception as ex: + self._exit_with_error( + "Could not upload image file to swift: %s" % str(ex)) + + # Test image upgrade + try: + # Create image and device db objects + image, device = self.create_image_and_device(self._image_name, + img_uri, + self._image_version, + self._image_family_name, + self._image_vendor_name, + self._device_family_name, + self._device_vendor_name, + self._prouter_ip, + self._prouter_password, + self._device_name) + # Run image upgrade playbook + self.image_upgrade(image, device) + except Exception as ex: + self._exit_with_error( + "Image upgrade test failed due to unexpected error: %s" + % str(ex)) + + # clean device and image from DB + self.cleanup_image_prouter(self._image_name, self._device_name) + # end test + + def _getmd5(self, filepath): + hasher = hashlib.md5() + with open(filepath, 'rb') as afile: + buf = afile.read(65536) + while len(buf) > 0: + hasher.update(buf) + buf = afile.read(65536) + return hasher.hexdigest() + + def _getsha1(self, filepath): + hasher = hashlib.sha1() + with open(filepath, 'rb') as afile: + buf = afile.read(65536) + while len(buf) > 0: + hasher.update(buf) + buf = afile.read(65536) + return hasher.hexdigest() + + +if __name__ == "__main__": + SanityTest02(config.load('config/test_config.yml', + 'config/image_config.yml')).test_image_upgrade() +# end __main__ +