Skip to content

Commit

Permalink
[DM] Sanity test for image upgrade
Browse files Browse the repository at this point in the history
Sanity test for the entire image upgrade workflow

Change-Id: I0035b19511440b4f18279cd5b3c6be49efe512d8
Closes-Bug: #1768159
  • Loading branch information
sahanaspc committed May 8, 2018
1 parent 4bdb266 commit c66b3ca
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 3 deletions.
Expand Up @@ -4,10 +4,11 @@ flexmock
coverage
pyaml
cfgm_common
fabric_ansible
vnc_api
sandesh
sandesh_common
junitxml
timeout-decorator
requests
yaml
python-swiftclient
@@ -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


@@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore
102 changes: 102 additions & 0 deletions src/config/fabric-ansible/ansible-playbooks/test/sanity/sanity_base.py
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -369,10 +454,27 @@ 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)
# end _exit_with_error

# end SanityBase class


Expand Up @@ -38,20 +38,40 @@ def _validate_discovered_prouters(self, prouters):
assert prouters[i].physical_router_vendor_name
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):
Expand Down Expand Up @@ -81,3 +101,4 @@ def test(self):
if __name__ == "__main__":
SanityTest01(config.load('config/test_config.yml')).test()
# end __main__

@@ -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__

0 comments on commit c66b3ca

Please sign in to comment.