diff --git a/ucsmsdk/ucssession.py b/ucsmsdk/ucssession.py index 1aa0a23b..fbc87dec 100644 --- a/ucsmsdk/ucssession.py +++ b/ucsmsdk/ucssession.py @@ -61,6 +61,9 @@ def __init__(self, ip, username, password, port=None, secure=None, self.__threaded = False self.__driver = UcsDriver(proxy=self.__proxy) + self.__udi = "PID:%sVID:%sSN:%s" %("-", "", "") + self.__pid = None + @property def ip(self): return self.__ip @@ -126,6 +129,15 @@ def last_update_time(self): def threaded(self): return self.__threaded + @property + def udi(self): + return self.__udi + + @property + def pid(self): + return self.__pid + + def _freeze(self): save = { "ip": self.__ip, diff --git a/ucsmsdk/utils/asd/__init__.py b/ucsmsdk/utils/asd/__init__.py new file mode 100755 index 00000000..62af685c --- /dev/null +++ b/ucsmsdk/utils/asd/__init__.py @@ -0,0 +1,12 @@ +# Copyright 2017 Cisco Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/ucsmsdk/utils/asd/asddatatypes.py b/ucsmsdk/utils/asd/asddatatypes.py new file mode 100644 index 00000000..fba7b8c4 --- /dev/null +++ b/ucsmsdk/utils/asd/asddatatypes.py @@ -0,0 +1,111 @@ +# Copyright 2017 Cisco Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This module has helper methods to facilitate image download from ASD +""" + +from __future__ import print_function +from __future__ import unicode_literals + +import urllib +import logging +import json + +from ...ucsdriver import UcsDriver + +log = logging.getLogger('ucs') + + +class AsdConsts: + CLIENT_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx" + CLIENT_SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx" + GRANT_TYPE = "password" + DEFAULT = "Default" + B_SERIES = "B-Series" + INFRASTRUCTURE = "Infrastructure" + DRIVERS = "Drivers" + FIRMWARE = "Firmware" + E_SERIES = "E-Series" + UCS_CENTRAL = "UcsCentral" + UCS_Handle = "UcsHandle" + IMC_HANDLE = "ImcHandle" + UCS_CENTRAL_HANDLE = "UcsCentralHandle" + TOKEN_URL = "https://cloudsso.cisco.com/as/token.oauth2" + IMAGE_URL = "https://api.cisco.com/software/v2.0/metadata/udi/{0}/mdf_id/{1}/software_type_id/{2}/current_release/{3}?page_index={4}" + DOWNLOAD_URL = "https://api.cisco.com/software/v2.0/downloads/urls/udi/{0}/mdf_id/{1}/metadata_trans_id/{2}/image_guids/{3}" + EULA_URL = "https://api.cisco.com/software/v2.0/compliance/forms/eula" + + +class Platform(object): + def __init__(self, software_id, mdf_id, version, udi, page_index="1"): + self.__software_type_id = software_id + self.__mdf_context_id = mdf_id + self.__min_release = version + self.__udi = udi + self.__operating_platform = None + self.__output_release = None + self.page_index = page_index + + @property + def software_type_id(self): + return self.__software_type_id + + @property + def mdf_context_id(self): + return self.__mdf_context_id + + @property + def min_release(self): + return self.__min_release + + @property + def udi(self): + return self.__udi + + @property + def operating_platform(self): + return self.__operating_platform + + @property + def output_release(self): + return self.__output_release + + +class UcsAsdImage(object): + def __init__(self, d): + for key in d: + setattr(self, key, d[key]) + + def __str__(self): + """ + Overridden str + """ + tab_size = 8 + out_str = "\n" + out_str += str("name").ljust(tab_size * 4) + ':' + str( + self.image_name) + "\n" + out_str += str("version").ljust(tab_size * 4) + ':' + str( + self.image_version) + "\n" + out_str += str("size").ljust(tab_size * 4) + ':' + str( + self.image_size) + "\n" + out_str += str("description").ljust(tab_size * 4) + ':' + str( + self.image_description) + "\n" + out_str += str("checksum_md5").ljust(tab_size * 4) + ':' + str( + self.image_checksums['md5_checksum']) + "\n" + out_str += str("access_token").ljust(tab_size * 4) + ':' + str( + self.access_token) + "\n" + out_str += str("proxy").ljust(tab_size * 4) + ':' + str( + self.proxy) + "\n" + out_str += "\n" + return out_str diff --git a/ucsmsdk/utils/asd/asdutils.py b/ucsmsdk/utils/asd/asdutils.py new file mode 100644 index 00000000..acdf8cb1 --- /dev/null +++ b/ucsmsdk/utils/asd/asdutils.py @@ -0,0 +1,259 @@ +# Copyright 2017 Cisco Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +""" +This module has helper methods to facilitate image download from ASD +""" + +from __future__ import print_function +from __future__ import unicode_literals + +from collections import defaultdict +from operator import attrgetter + +import os +import logging +import urllib +import json + +from ...ucscoremeta import UcsVersion +from ...ucsdriver import UcsDriver +from ... import ucsxmlcodec +from . import asddatatypes as adt + +log = logging.getLogger('ucs') + + +def get_image_meta_list(): + cur_dir = os.path.dirname(os.path.abspath(__file__)) + fpath = os.path.join(cur_dir, "imagemeta.xml") + if not os.path.exists(fpath): + raise UcsOperationError("get_image_meta_list", + "File <%s> does not exist" % fpath) + + with open(fpath, 'r') as fh: + data = fh.read() + root = ucsxmlcodec.extract_root_elem(data) + + return root.findall('CcoImageMeta') + + +def filter_image_meta_category(meta_list, mode, category): + for meta in meta_list: + if meta.attrib['Mode'] == mode and meta.attrib['Category'] == category: + return meta + + +def filter_image_meta_mdf(meta_list, mdf_id): + for meta in meta_list: + meta_mdf_id = meta.attrib['MdfId'] + if meta_mdf_id == mdf_id: + return meta + + +def get_access_token(username, password=None, proxy=None): + import getpass + + if password is None: + password = getpass.getpass() + + driver = UcsDriver(proxy) + + values = { + 'client_id': adt.AsdConsts.CLIENT_ID, + 'client_secret': adt.AsdConsts.CLIENT_SECRET, + 'grant_type': adt.AsdConsts.GRANT_TYPE, + 'username': username, + 'password': password + } + + data = urllib.urlencode(values) + data = data.encode('utf-8') + + resp = driver.post(uri=adt.AsdConsts.TOKEN_URL, + data=data, + dump_xml=False, + read=True) + resp_dict = json.loads(resp) + return resp_dict['access_token'] + + + +def _get_url_json_response(url, access_token, proxy=None, data=None): + log.debug(url) + driver = UcsDriver(proxy) + driver.add_header("Authorization", "Bearer %s" % access_token) + resp = driver.post(uri=url, data=data) + resp_dict = json.loads(resp) + return resp_dict + + +def _get_image_store_url(platform): + url = adt.AsdConsts.IMAGE_URL + image_store_url = url.format(platform.udi, + platform.mdf_context_id, + platform.software_type_id, + platform.min_release, + platform.page_index) + + if platform.operating_platform: + image_store_url += "&operating_platform=%s" % platform.operating_platform + + if platform.output_release: + image_store_url += "&output_release=%s" % platform.output_release + + return image_store_url + + +def _get_images_by_udi(platform, access_token, proxy=None): + asd_image_list = None + url = _get_image_store_url(platform) + resp_dict = _get_url_json_response(url, access_token, proxy) + + metadata_response = resp_dict['metadata_response'] + metadata_id_list = metadata_response['metadata_id_list'] + asd_metadata_exception = metadata_id_list['asd_metadata_exception'] + if asd_metadata_exception: + raise Exception + + software_list = metadata_id_list['software_list'] + platform_list = software_list['platform_list'] + + for pltform in platform_list: + release_list = pltform['release_list'] + for release in release_list: + images = release['image_details'] + for img in images: + image = adt.UcsAsdImage(img) + asd_image_exception = image.asd_image_exception + if asd_image_exception and len(asd_image_exception) >= 1: + if asd_image_exception[0]['exception_code'] == "is_Deleted": + continue + image.is_suggested_release = release['is_suggested_rel'] + image.image_version = release['release_version'] + image.access_token = access_token + image.platform = platform + image.metadata_transaction_id = metadata_response['metadata_trans_id'] + image.operating_platform = pltform['operating_platform'] + image.proxy = proxy + + if asd_image_list is None: + asd_image_list = [] + asd_image_list.append(image) + + pagination_response_record = resp_dict['pagination_response_record'] + log.debug("page_index is <%s>" % str(pagination_response_record['page_index'])) + log.debug("last_index is <%s>" % str(pagination_response_record['last_index'])) + + if pagination_response_record['page_index'] != pagination_response_record['last_index']: + page_index = int(pagination_response_record['page_index']) + page_index += 1 + platform.page_index = str(page_index) + images = _get_images_by_udi(platform, access_token, proxy) + if images and len(images) > 0: + asd_image_list.extend(images) + + return asd_image_list + +def _remove_duplicate_image(images): + imagedict = defaultdict(list) + for image in images: + imagedict[image.image_name].append(image) + + imagelist = [] + for imglist in imagedict.values(): + imagelist.append(max(imglist, key=attrgetter('image_version'))) + + return sorted(imagelist, key=attrgetter('image_version')) + + +def get_asd_images(platform, access_token, proxy=None): + asdimages = _get_images_by_udi(platform, access_token, proxy) + if asdimages and len(asdimages) > 1: + return _remove_duplicate_image(asdimages) + + +def filter_asd_images(images): + if not images or len(images) < 1: + return None + + # grouping image by version + image_groups_by_version = defaultdict(list) + for image in images: + image_groups_by_version[image.image_version].append(image) + + # find max version under each "major.minor" combination + version_groups = defaultdict(list) + for version in image_groups_by_version: + ver = UcsVersion(version) + version_groups["%s.%s" %(ver.major, ver.minor)].append(version) + + # hvg - highest version in each group + hvg = [max(i, key=lambda version: UcsVersion(version)) for i in version_groups.values()] + + return [image for ver in hvg for image in image_groups_by_version[ver]] + + +def _get_image_download_url(image): + url = adt.AsdConsts.DOWNLOAD_URL + image_download_url = url.format(image.platform.udi, + image.platform.mdf_context_id, + image.metadata_transaction_id, + image.image_guid) + return image_download_url + + +def get_image_download_info(image): + url = _get_image_download_url(image) + resp_dict = _get_url_json_response(url, image.access_token, image.proxy) + download_info_list = resp_dict['download_info_list'] + asd_download_url_exception = download_info_list[0]['asd_download_url_exception'] + if asd_download_url_exception and len(asd_download_url_exception) > 1: + exception_code = asd_download_url_exception[0]['exception_code'] + if exception_code != "SW_ADV_IMAGE": + raise Exception(asd_download_url_exception[0]['exception_message']) + else: + download_list = download_info_list[0] + log.debug(download_list) + return download_list + + +def accept_eula_agreement(user_action, access_token, proxy): + url = adt.AsdConsts.EULA_URL + data = urllib.urlencode({"user_action": user_action}) + data = data.encode('utf-8') + resp_dict = _get_url_json_response(url, access_token, proxy, data) + log.debug(resp_dict) + return resp_dict and resp_dict['status_number'] == "0" + + +def is_correct_image(full_image_path, image): + from ... import ucsgenutils + + file_md5 = ucsgenutils.get_md5_sum(full_image_path) + image_md5 = image.image_checksums['md5_checksum'] + + if not file_md5 or file_md5 != image_md5: + UcsWarning("Unable to generate md5sum for file <%s>" % full_image_path) + UcsWarning("Deleting file <%s> ....." % full_image_path) + os.remove(full_image_path) + return False + return True + + +def is_eula_accepted(access_token, proxy): + return accept_eula_agreement(user_action.declined, access_token, proxy) + + + diff --git a/ucsmsdk/utils/asd/imagemeta.xml b/ucsmsdk/utils/asd/imagemeta.xml new file mode 100644 index 00000000..96d39349 --- /dev/null +++ b/ucsmsdk/utils/asd/imagemeta.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ucsmsdk/utils/asd/ucsfirmwareimage.py b/ucsmsdk/utils/asd/ucsfirmwareimage.py new file mode 100644 index 00000000..93f98308 --- /dev/null +++ b/ucsmsdk/utils/asd/ucsfirmwareimage.py @@ -0,0 +1,186 @@ +# Copyright 2017 Cisco Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +""" +This module has helper methods to facilitate image download from CCO +""" + +from __future__ import print_function +from __future__ import unicode_literals + +import os +import logging + +from . import asdutils as au +from . import asddatatypes as adt +from ... import ucsgenutils +from ...ucsexception import UcsOperationError, UcsValidationException, UcsWarning +from ...ucsdriver import UcsDriver + +log = logging.getLogger('ucs') + + +def get_ucs_software_image_list(handle, username, password=None, + mdf_id=None, software_id=None, + category=adt.AsdConsts.DEFAULT, + type=adt.AsdConsts.FIRMWARE, + model=None, all_releases=False, proxy=None): + import getpass + + if password is None: + password = getpass.getpass() + + udi = handle.udi # Todo + pid = handle.pid if model is None else model + + log.debug("Get meta for software images") + image_meta_list = au.get_image_meta_list() + + if mdf_id and software_id: + # use mdf_id to fetch image list + image_meta = au.filter_image_meta_mdf(image_meta_list, mdf_id) + if image_meta is None: + raise UcsOperationError("get_ucs_software_image_list", "Invalid mdf_id <%s>." % mdf_id) + + image_firmware_id = image_meta.attrib['FirmwareId'] + image_firmware_version = image_meta.attrib['FirmwareVersion'] + + image_driver_id = image_meta.attrib['DriverId'] + image_driver_version = image_meta.attrib['DriverVersion'] + + if software_id == image_firmware_id: + version = image_firmware_version + elif software_id == image_driver_id: + version = image_driver_version + else: + raise UcsOperationError("get_ucs_software_image_list", + "Invalid Combination of mdf_id <%s> and software_id <%s>" % + (mdf_id, software_id)) + else: + if model: + UcsWarning("model parameter is ignored.") + + image_meta = au.filter_image_meta_category(image_meta_list, + mode=adt.AsdConsts.B_SERIES, + category=category) + + image_category = image_meta.attrib['Category'] + image_firmware_id = image_meta.attrib['FirmwareId'] + image_firmware_version = image_meta.attrib['FirmwareVersion'] + image_driver_id = image_meta.attrib['DriverId'] + image_driver_version = image_meta.attrib['DriverVersion'] + image_mdf_id = image_meta.attrib['MdfId'] + + mdf_id = image_mdf_id + + if image_category == adt.AsdConsts.INFRASTRUCTURE: + software_id = image_firmware_id + version = image_firmware_version + + if type == adt.AsdConsts.DRIVERS: + UcsWarning("type parameter is ignored.") + elif type == adt.AsdConsts.DRIVERS: + software_id = image_driver_id + version = image_driver_version + else: + software_id = image_firmware_id + version = image_firmware_version + + + access_token = au.get_access_token(username, password, proxy) + if not access_token: + raise UcsOperationError("get_ucs_software_image_list", + "Failed to get access token.") + + log.debug("Access Token is <%s>" % access_token) + host_platform = adt.Platform(software_id=software_id, + mdf_id=mdf_id, + version=version, + udi=udi) + + log.debug("Get Images.....") + asd_image_list = au.get_asd_images(host_platform, access_token, proxy) + + if not all_releases: + asd_image_list = au.filter_asd_images(asd_image_list) + + return asd_image_list + + +def get_ucs_software_image(image, file_dir): + """ + Downloads ucs firmware image + + Args: + image (UcsSoftwareImage): object of type UcsSoftwareImage + file_dir (str): directory to download image to + + Returns: + None + + Example: + image_list = get_ucs_cco_image_list("username", "password")\n + get_ucs_software_image(image=image_list[0], + file_dir="/home/user/images")\n + """ + + if not isinstance(image, adt.UcsAsdImage): + raise UcsValidationException("Invalid Image.") + + if not os.path.isdir(file_dir): + raise UcsValidationException( + "Not a valid directory <%s>" % file_dir) + + local_file = os.path.join(file_dir, str(image.image_name)) + if os.path.exists(local_file): + if au.is_correct_image(local_file, image): + UcsWarning("File <%s> already exist. Exiting." % local_file) + return + else: + os.remove(local_file) + log.info("Invalid Image. Removing it.") + + # accept eula agreement explicitly : TO DO - Need to be removed + if not au.accept_eula_agreement("Accepted", image.access_token, + image.proxy): + raise UcsOperationError("get_ucs_software_image", + "Unable to accept end user license agreement.") + + download_info = au.get_image_download_info(image) + download_url = download_info['download_url'] + if not download_url: + raise UcsOperationError("get_ucs_software_image", + "Download URL is not available.") + + log.info("Processing image " + str(image.image_name)) + image_url = download_url + "&access_token=%s" % image.access_token + log.debug(image_url) + driver = UcsDriver(image.proxy) + ucsgenutils.download_file(driver, + file_url=image_url, + file_dir=file_dir, + file_name=str(image.image_name)) + + local_file = os.path.join(file_dir, str(image.image_name)) + if not os.path.exists(local_file): + raise UcsOperationError("get_ucs_software_image", + "File not downloaded.") + + if not au.is_correct_image(local_file, image): + raise UcsOperationError("get_ucs_software_image", + "Invalid Image.Deleting it.") + + log.info("Image processing complete.") + +