python-xclarityclient-0.1.7/0000777000000000000000000000000013330023226014220 5ustar 00000000000000python-xclarityclient-0.1.7/PKG-INFO0000666000000000000000000000105313330023226015314 0ustar 00000000000000Metadata-Version: 1.1 Name: python-xclarityclient Version: 0.1.7 Summary: This is a Python library for controlling XClarity by REST API. Home-page: UNKNOWN Author: Finix Lei Author-email: leilei4@lenovo.com License: Apache License, Version 2.0 Description-Content-Type: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python python-xclarityclient-0.1.7/python_xclarityclient.egg-info/0000777000000000000000000000000013330023226022351 5ustar 00000000000000python-xclarityclient-0.1.7/python_xclarityclient.egg-info/dependency_links.txt0000666000000000000000000000000113330023224026415 0ustar 00000000000000 python-xclarityclient-0.1.7/python_xclarityclient.egg-info/entry_points.txt0000666000000000000000000000010613330023224025642 0ustar 00000000000000[console_scripts] python_xclarityclient = python_xclarityclient:main python-xclarityclient-0.1.7/python_xclarityclient.egg-info/not-zip-safe0000666000000000000000000000000213330022421024574 0ustar 00000000000000 python-xclarityclient-0.1.7/python_xclarityclient.egg-info/PKG-INFO0000666000000000000000000000105313330023224023443 0ustar 00000000000000Metadata-Version: 1.1 Name: python-xclarityclient Version: 0.1.7 Summary: This is a Python library for controlling XClarity by REST API. Home-page: UNKNOWN Author: Finix Lei Author-email: leilei4@lenovo.com License: Apache License, Version 2.0 Description-Content-Type: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python python-xclarityclient-0.1.7/python_xclarityclient.egg-info/SOURCES.txt0000666000000000000000000000103713330023224024234 0ustar 00000000000000README.md setup.py python_xclarityclient.egg-info/PKG-INFO python_xclarityclient.egg-info/SOURCES.txt python_xclarityclient.egg-info/dependency_links.txt python_xclarityclient.egg-info/entry_points.txt python_xclarityclient.egg-info/not-zip-safe python_xclarityclient.egg-info/top_level.txt xclarity_client/__init__.py xclarity_client/client.py xclarity_client/constants.py xclarity_client/exceptions.py xclarity_client/xclarity_client.py xclarity_client/v1_2_2/__init__.py xclarity_client/v1_2_2/boot_order.py xclarity_client/v1_2_2/client.pypython-xclarityclient-0.1.7/python_xclarityclient.egg-info/top_level.txt0000666000000000000000000000002013330023224025071 0ustar 00000000000000xclarity_client python-xclarityclient-0.1.7/README.md0000666000000000000000000000064413330021512015477 0ustar 000000000000001. Run ./bootstrap Then virtual environment will be done. It is ./.venv-xclarity-client 2. Run "souce activate-xclarity-client" You will enter xclarity-client virtual environment. 3. Run "python test_client.py" and access the URL described in test_client.py. You will see the feedback, and you can check status on the URL. 4. Run "deactivate" You will exit the virtual environment. python-xclarityclient-0.1.7/setup.cfg0000666000000000000000000000005213330023226016036 0ustar 00000000000000[egg_info] tag_build = tag_date = 0 python-xclarityclient-0.1.7/setup.py0000666000000000000000000000175413330021512015735 0ustar 00000000000000from setuptools import setup, find_packages PACKAGE = "xclarity_client" NAME = "python-xclarityclient" DESCRIPTION = "This is a Python library for controlling XClarity by REST API. " AUTHOR = "Finix Lei" AUTHOR_EMAIL = "leilei4@lenovo.com" # URL = "http://www.lenovo.com.cn/" VERSION = __import__(PACKAGE).__version__ setup( name=NAME, version=VERSION, description=DESCRIPTION, # long_description=read("README.md"), author=AUTHOR, author_email=AUTHOR_EMAIL, license="Apache License, Version 2.0", # url=URL, packages=find_packages(), classifiers=[ "Development Status :: 3 - Alpha", "Environment :: Web Environment", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", ], entry_points={ 'console_scripts': [ 'python_xclarityclient = python_xclarityclient:main', ] }, zip_safe=False, ) python-xclarityclient-0.1.7/xclarity_client/0000777000000000000000000000000013330023226017415 5ustar 00000000000000python-xclarityclient-0.1.7/xclarity_client/client.py0000666000000000000000000000143213330021512021241 0ustar 00000000000000import sys import traceback def import_class(import_str): mod_str, _sep, class_str = import_str.rpartition('.') __import__(mod_str) try: return getattr(sys.modules[mod_str], class_str) except AttributeError: raise ImportError('Class %s cannot be found (%s)' % (class_str, traceback.format_exception(*sys.exc_info()))) def Client(username=None, password=None, ip='127.0.0.1', port=80, version='xclarity_1.2.2'): version = version.lower().replace('xclarity_', '') version_mod_name = 'v%s' % version.replace('.', '_') c = import_class("xclarity_client.%s.client.Client" % version_mod_name) return c(version=version, username=username, password=password, ip=ip, port=port) python-xclarityclient-0.1.7/xclarity_client/constants.py0000666000000000000000000000260113330021512021776 0ustar 00000000000000STATE_UNKNOWN = "unknown" STATE_POWER_ON = "power on" STATE_POWER_OFF = "power off" STATE_POWERING_ON = "powering_on" STATE_POWERING_OFF = "powering_off" STATE_STANDBY = "standby" ACTION_POWER_ON = "power on" ACTION_POWER_OFF = "power off" ACTION_REBOOT = "rebooting" ACTION_POWER_CYCLE_SOFT = "soft rebooting" power_status_list = { 0: STATE_UNKNOWN, 5: STATE_POWER_OFF, 8: STATE_POWER_ON, 18: STATE_STANDBY } power_status_action = { ACTION_POWER_ON: 'powerOn', # powers on the server ACTION_POWER_OFF: 'powerOff', # powers off the server immediately ACTION_REBOOT: 'powerCycleSoft', # restarts the server immediately ACTION_POWER_CYCLE_SOFT: 'powerCycleSoftGrace', # restarts the server gracefully # 'VIRTUAL_RESEAT': 'virtualReseat', # calls the CMM function to simulate removing power from the bay # 'POWER_NMI': 'powerNMI', # restarts the server with non-maskable interrupt (performs a diagnostic interrupt) # 'BOOT_TO_F1': 'bootToF1', # (Lenovo endpoints only) Powers on to UEFI(F1) } """ Supported Machine Types are as below """ SYSTEM_X3550_M5 = 'SYSTEM X3550 M5' SYSTEM_X3650_M5 = 'SYSTEM X3650 M5' THINKSYSTEM_SD530 = 'THINKSYSTEM SD530' THINKSYSTEM_SR630 = 'THINKSYSTEM SR630' SUPPORTED_MACHINE_TYPES = [ SYSTEM_X3550_M5, SYSTEM_X3650_M5, THINKSYSTEM_SD530, THINKSYSTEM_SR630 ]python-xclarityclient-0.1.7/xclarity_client/exceptions.py0000666000000000000000000000420613330021512022146 0ustar 00000000000000import sys import six class XClarityError(Exception): """Base XClarity Client Exception To correctly use this class, inherit from it and define a 'msg_fmt' property. That msg_fmt will get printf'd with the keyword arguments provided to the constructor. """ msg_fmt = "An exception occurred." code = 500 def __init__(self, message=None, **kwargs): self.kwargs = kwargs if 'code' not in self.kwargs: try: self.kwargs['code'] = self.code except AttributeError: pass if message is None: try: message = self.msg_fmt % kwargs except Exception: exc_info = sys.exc_info() six.reraise(*exc_info) self.message = message super(XClarityError, self).__init__(message) def get_message(self): return self.message def get_code(self): return self.code class ConnectionFailureException(XClarityError): msg_fmt = 'Connection Error or Timeout for node %(node_id)s: %(detail)s' class NodeDetailsException(XClarityError): msg_fmt = 'Failed to get the details of node %(node_id)s: %(detail)s' class BadPowerStatusSettingException(XClarityError): msg_fmt = 'Bad PowerStatus setting: %(action)s' code = 400 class FailToSetPowerStatusException(XClarityError): msg_fmt = 'Failed to set the power status of node %(node_id)s: %(action)s' class FailToSetBootInfoException(XClarityError): msg_fmt = 'Failed to set boot info for node %(node_id)s.' class FailToGetBootOrderException(XClarityError): msg_fmt = 'Failed to get boot order info for node %(node_id)s. ' \ 'Perhaps this node is not registered to the configured ' \ 'XClarity server.' # class UnsupportedMachineType(XClarityError): # msg_fmt = 'Such machine type "%(machine_type)s" is not supported by XClarity Driver. ' class FailToGetAllJobs(XClarityError): msg_fmt = 'Fail to get all the jobs from XClarity server. ' \ 'status_code is %(status_code)s. 'python-xclarityclient-0.1.7/xclarity_client/v1_2_2/0000777000000000000000000000000013330023226020405 5ustar 00000000000000python-xclarityclient-0.1.7/xclarity_client/v1_2_2/boot_order.py0000666000000000000000000000413313330021512023112 0ustar 00000000000000from ..constants import (SYSTEM_X3550_M5, SYSTEM_X3650_M5, THINKSYSTEM_SD530, THINKSYSTEM_SR630) """ Boot Order Priority """ boot_order_priority = [ {'name': 'singleuse', 'value': None}, {'name': 'permanent', 'value': None}, {'name': 'wakeonlan', 'value': None} ] setting_boot_type = { 'SingleUse': 'BootOrder.SingleUse', 'Permanent': 'BootOrder.BootOrder' } """ Standard Boot Devices' names These name should be consistent with the ones defined in ironic/common/boot_devices.py """ PXE = 'pxe' "Boot from PXE boot" DISK = 'disk' "Boot from default Hard-drive" CDROM = 'cdrom' "Boot from CD/DVD" BIOS = 'bios' "Boot into BIOS setup" SAFE = 'safe' "Boot from default Hard-drive, request Safe Mode" WANBOOT = 'wanboot' "Boot from Wide Area Network" """ Boot Devices per Machine types """ BOOT_DEVICES = { SYSTEM_X3550_M5: { 'singleuse': { PXE: 'PXE Network', DISK: 'Hard Disk 0', CDROM: 'CD/DVD Rom' }, 'permanent': { PXE: 'PXE Network', DISK: 'Hard Disk 0', CDROM: 'CD/DVD Rom' } }, SYSTEM_X3650_M5: { 'singleuse': { PXE: 'PXE Network', DISK: 'Hard Disk 0', CDROM: 'CD/DVD Rom' }, 'permanent': { PXE: 'PXE Network', DISK: 'Hard Disk 0', CDROM: 'CD/DVD Rom' } }, THINKSYSTEM_SD530: { 'singleuse': { PXE: 'PXE Network', DISK: 'Hard Disk 0', CDROM: 'CD/DVD Rom' }, 'permanent': { PXE: 'Network', DISK: 'Hard Disk', CDROM: 'CD/DVD Rom' } }, THINKSYSTEM_SR630: { 'singleuse': { PXE: 'PXE Network', DISK: 'Hard Disk 0', CDROM: 'CD/DVD Rom' }, 'permanent': { PXE: 'Network', DISK: 'Hard Disk', CDROM: 'CD/DVD Rom' } } } python-xclarityclient-0.1.7/xclarity_client/v1_2_2/client.py0000666000000000000000000003532113330021512022235 0ustar 00000000000000import json import copy import os import time from multiprocessing import Process import requests from requests.exceptions import (ConnectionError, ConnectTimeout) from ..constants import power_status_list, power_status_action from ..constants import (ACTION_POWER_ON, ACTION_POWER_OFF, ACTION_REBOOT) from ..constants import (STATE_UNKNOWN, STATE_POWER_ON, STATE_POWER_OFF, STATE_POWERING_ON, STATE_POWERING_OFF) from ..constants import SUPPORTED_MACHINE_TYPES from .boot_order import boot_order_priority, setting_boot_type from .boot_order import BOOT_DEVICES from ..exceptions import * from oslo_log import log as logging LOG = logging.getLogger(__name__) POWER_ACTION_TIMEOUT = 600 # 10 minutes UNKNOWN_MACHINE_TYPE = 'Unknown' requests.packages.urllib3.disable_warnings() class Client(object): def __init__(self, username=None, password=None, version=None, ip='127.0.0.1', port=443): self._version = version self._username = username self._password = password self._url = 'https://%s' % ip def _gen_node_action_url(self, node_id): return '{url}/node/{node_id}'.format(url=self._url, node_id=node_id) def _get_node_details(self, node_id): url = self._gen_node_action_url(node_id) try: r = requests.get(url, auth=(self._username, self._password), verify=False, timeout=(10, 10)) result = { 'status_code': r.status_code, 'encoding': r.encoding, 'headers': r.headers, 'body': r.json() } return result except ConnectionError or ConnectTimeout as ex: raise ConnectionFailureException(node_id=node_id, detail=str(ex)) except Exception as ex: raise NodeDetailsException(message=str(ex), node_id=node_id) def is_node_managed(self, node_id): url = '{url}/node'.format(url=self._url) try: r = requests.get(url, auth=(self._username, self._password), verify=False, timeout=(10, 10)) except ConnectionError or ConnectTimeout as ex: raise ConnectionFailureException(node_id=node_id, detail=str(ex)) except Exception as ex: raise NodeDetailsException(message=str(ex), node_id=node_id) if r.status_code == 200: nodeList = r.json().get('nodeList') for node in nodeList: if node['uuid'] == node_id: return True return False def get_node_status(self, node_id): response = self._get_node_details(node_id) if response['status_code'] == 200: return response['body']['accessState'].lower() else: return 'unknown' def get_node_product_name(self, node_id): response = self._get_node_details(node_id) product_name = 'Unknown' if response['status_code'] == 200: body = response['body'] product_name = body.get('productName', 'Unknown') return product_name def get_node_info(self, node_id): response = self._get_node_details(node_id) info = {} if response['status_code'] == 200: body = response['body'] info['uuid'] = body.get('uuid', None) info['name'] = body.get('hostname', 'Unknown') info['product_name'] = body.get('productName', 'Unknown') info['power_state'] = power_status_list.get( body.get('powerStatus', 0), 0) info['chassis_id'] = None # nullable info['target_power_state'] = None # nullable info['provision_state'] = None # nullable info['target_provision_state'] = None # nullable info['provision_updated_at'] = None # nullable info['last_error'] = None # nullable info['instance_uuid'] = None # nullable info['instance_info'] = None # nullable info['raid_config'] = body.get('raidSettings', []) # An array info['target_raid_config'] = [] info['maintenance'] = False \ if body.get('accessState', 'unknown') == 'online' else True info['maintenance_reason'] = None # nullable info['console_enabled'] = False # False is by default, what's this info['extra'] = {} # What's this? info['properties'] = {} # What's this? else: err_msg = "Fail to get node info, http status code is %s, " \ "http response is %s" % (response['status_code'], response['body']) raise NodeDetailsException(node_id=node_id, detail=err_msg) return info def _gen_power_action_file_path(self, node_id, action): file_name = "{action}_{node_id}_in_progress".format(action=action, node_id=node_id) pre_path = "/tmp" if os.path.exists("/tmp") else "./" # resolve Windows OS problem path = os.path.join(pre_path, file_name) return path def _check_power_action_running(self, node_id): power_on_file = self._gen_power_action_file_path(node_id, ACTION_POWER_ON) power_off_file = self._gen_power_action_file_path(node_id, ACTION_POWER_OFF) if os.path.exists(power_on_file) and os.path.exists(power_off_file): now = time.time() delta_on = now - os.path.getmtime(power_on_file) delta_off = now - os.path.getmtime(power_off_file) if (delta_on > POWER_ACTION_TIMEOUT and delta_off > POWER_ACTION_TIMEOUT) or \ (delta_on <= POWER_ACTION_TIMEOUT and delta_off <= POWER_ACTION_TIMEOUT): os.remove(power_on_file) os.remove(power_off_file) result = None elif delta_on > POWER_ACTION_TIMEOUT: os.remove(power_on_file) result = STATE_POWER_OFF elif delta_off > POWER_ACTION_TIMEOUT: os.remove(power_off_file) result = STATE_POWER_ON elif os.path.exists(power_on_file): delta = time.time() - os.path.getmtime(power_on_file) if delta > POWER_ACTION_TIMEOUT: os.remove(power_on_file) result = None else: result = STATE_POWERING_ON elif os.path.exists(power_off_file): delta = time.time() - os.path.getmtime(power_off_file) if delta > POWER_ACTION_TIMEOUT: os.remove(power_off_file) result = None else: result = STATE_POWERING_OFF else: result = None return result def get_node_power_status(self, node_id): result = self._check_power_action_running(node_id) if result: return result response = self._get_node_details(node_id) if response['status_code'] == 200: result = power_status_list[response['body']['powerStatus']] else: result = STATE_UNKNOWN return result def _set_power_status(self, node_id, action): url = self._gen_node_action_url(node_id) data = {'powerState': action} try: power_action_file = self._gen_power_action_file_path(node_id, action) out_file = open(power_action_file, "w") out_file.close() requests.put(url, auth=(self._username, self._password), data=json.dumps(data), verify=False) except ConnectionError or ConnectTimeout as ex: raise ConnectionFailureException(node_id=node_id, detail=str(ex)) finally: if os.path.exists(power_action_file): os.remove(power_action_file) def set_node_power_status(self, node_id, action): if action not in power_status_action: raise BadPowerStatusSettingException(action=action) action = power_status_action[action] p = Process(target=self._set_power_status, args=(node_id, action)) p.start() def get_node_all_boot_info(self, node_id): response = self._get_node_details(node_id) if response['status_code'] == 200: try: boot_info = {'bootOrder': response['body']['bootOrder']} return boot_info except KeyError as ex: raise FailToGetBootOrderException(node_id=node_id) # This method is not used in XClarity Driver def get_node_boot_info(self, node_id): response = self._get_node_details(node_id) if response['status_code'] == 200: boot_order_list = response['body']['bootOrder']['bootOrderList'] boot_order_table = copy.deepcopy(boot_order_priority) for boot_item in boot_order_list: boot_item_type = boot_item['bootType'].lower() for item in boot_order_table: if item['name'] == boot_item_type: item['value'] = boot_item break first_boot_item = None for item in boot_order_table: if item['value'] is not None: first_boot_item = item['value'] break return first_boot_item else: return None """ Change the primary boot device to the real name by checking machine type """ def _gen_real_boot_order(self, machine_type, boot_order, target_device, permanent): for item in boot_order['bootOrder']['bootOrderList']: if item['bootType'].lower() == 'singleuse': singleuse_item = item elif item['bootType'].lower() in ['permanent', 'bootorder']: # for 3250/3650, permanent boot type is 'Permanent' # for SR630, permanent boot type is 'BootOrder' permanent_item = item if permanent: current = permanent_item['currentBootOrderDevices'] # real_target = BOOT_DEVICES[machine_type]['permanent'][target_device] else: current = singleuse_item['currentBootOrderDevices'] # real_target = BOOT_DEVICES[machine_type]['singleuse'][target_device] if target_device in current: current.remove(target_device) current.insert(0, target_device) if not permanent: if 'None' in current: current.remove('None') if 'none' in current: current.remove('None') LOG.info("Final Boot Info after set boot device is: %s" % json.dumps(boot_order)) return boot_order """ :param node_id, the id of the node :param boot_order, a dict which is like below: { "bootOrder":{ "bootOrderList":[ { "bootType":"SingleUse", "currentBootOrderDevices":["PXE Network"], "possibleBootOrderDevices":[ "None", "PXE Network", "HardDisk0", "Diagnostics", "CD/DVDRom", "BootToF1", "Hypervisor", "FloppyDisk" ] }, # one boot item ... ] # boot order list } } :param target_device, the device will be set as primary boot device :param permanent, Bool, point out this is configured to permanent item (True) or singleuse item """ def set_node_boot_info(self, node_id, boot_order, target_device, singleuse): permanent = not singleuse try: product_name = self.get_node_product_name(node_id) machine_type = UNKNOWN_MACHINE_TYPE for smt in SUPPORTED_MACHINE_TYPES: if smt in product_name.upper(): machine_type = smt break if machine_type == UNKNOWN_MACHINE_TYPE: # raise UnsupportedMachineType(machine_type=product_name) raise Exception("Unsupported Machine Type: %s" % machine_type) input_boot_order = self._gen_real_boot_order(machine_type, boot_order, target_device, permanent) url = self._gen_node_action_url(node_id) r = requests.put(url, auth=(self._username, self._password), data=json.dumps(input_boot_order), verify=False) if r.status_code != 200: raise Exception("Fail to set node boot info. status code = %s" % r.status_code) except ConnectionError or ConnectTimeout as ex: raise ConnectionFailureException(node_id=node_id, detail=str(ex)) # except UnsupportedMachineType: # raise except Exception as ex: print("Exception! - %s" % str(ex)) def _get_all_xclarity_jobs(self): url = '{url}/jobs'.format(url=self._url) r = requests.get(url, auth=(self._username, self._password), verify=False, timeout=(10, 10)) if r.status_code != 200: raise FailToGetAllJobs(status_code=r.status_code) else: return r.json() def node_has_jobs(self, node_id): all_jobs = self._get_all_xclarity_jobs() count = 0 for job in all_jobs: if job.get('uuid', None) == node_id: count += 1 print("jobs number = %d" % count) return True if count > 0 else False def ready_for_deployment(self, node_id): response = self._get_node_details(node_id) result = False if response['status_code'] == 200: power_status = power_status_list[response['body']['powerStatus']] if power_status in ['on', 'off']: result = True if result: result = self.node_has_jobs(node_id) return resultpython-xclarityclient-0.1.7/xclarity_client/v1_2_2/__init__.py0000666000000000000000000000000013330021512022500 0ustar 00000000000000python-xclarityclient-0.1.7/xclarity_client/xclarity_client.py0000666000000000000000000000031213330021512023154 0ustar 00000000000000def main(): msg = """ This is XClarity client. The library function is ready. The tool function is in development. Stay tuned. """ print(msg) if __name__ == '__main__': main() python-xclarityclient-0.1.7/xclarity_client/__init__.py0000666000000000000000000000005713330021512021524 0ustar 00000000000000__version__ = "0.1.7" __author__ = "Finix Lei"