├── scaleiopy ├── util │ ├── __init__.py │ ├── tls1adapter.py │ └── installerfsm.py ├── api │ ├── im │ │ ├── mapping │ │ │ ├── __init__.py │ │ │ ├── secondary_mdm.py │ │ │ ├── sds_device.py │ │ │ ├── syslog_configuration.py │ │ │ ├── sdc.py │ │ │ ├── im_generic_object.py │ │ │ ├── tb.py │ │ │ ├── primary_mdm.py │ │ │ ├── mdm.py │ │ │ ├── node.py │ │ │ ├── callhome_configuration.py │ │ │ └── sds.py │ │ ├── __init__.py │ │ └── system.py │ ├── __init__.py │ └── scaleio │ │ ├── cluster │ │ ├── __init__.py │ │ ├── cluster.py │ │ ├── storagepool.py │ │ ├── faultset.py │ │ ├── protectiondomain.py │ │ ├── sdc.py │ │ └── sds.py │ │ ├── common │ │ ├── __init__.py │ │ └── connection.py │ │ ├── mapping │ │ ├── __init__.py │ │ ├── ip_list.py │ │ ├── link.py │ │ ├── sio_generic_object.py │ │ ├── faultset.py │ │ ├── vtree.py │ │ ├── sdc.py │ │ ├── snapshotspecification.py │ │ ├── protection_domain.py │ │ ├── volume.py │ │ ├── sds.py │ │ ├── storage_pool.py │ │ └── statistics.py │ │ ├── metering │ │ ├── __init__.py │ │ └── statistics.py │ │ ├── provisioning │ │ ├── __init__.py │ │ └── volume.py │ │ ├── __init__.py │ │ └── system.py ├── __init__.py └── scaleio.py ├── install_files.txt ├── examples ├── delete-snapshot.py ├── get-cluster-configuration.py ├── unmap_delete-volume.py ├── unmap-volume.py ├── map-volume.py ├── delete-volume.py ├── create_mapall-volume.py ├── connect-cluster-test.py ├── create-volume.py ├── connect-cluster.py ├── create-snapshot-of-volume.py ├── install-cluster.py ├── install-cluster-faultsets.py ├── install-cluster-syslog_v1_32.py └── install-cluster-syslog.py ├── .gitignore ├── setup.py ├── doc ├── minimal-cluster_working.json ├── minimal-cluster.json ├── cache.json └── minimal-cluster_notworking.json ├── Changelog.md ├── README.md └── LICENSE /scaleiopy/util/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scaleiopy/api/im/mapping/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scaleiopy/api/__init__.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | -------------------------------------------------------------------------------- /scaleiopy/api/im/__init__.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | -------------------------------------------------------------------------------- /scaleiopy/api/im/mapping/secondary_mdm.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/cluster/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/common/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/mapping/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/metering/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scaleiopy/util/tls1adapter.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/provisioning/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /install_files.txt: -------------------------------------------------------------------------------- 1 | /Library/Python/2.7/site-packages/ScaleIO_py-0.3-py2.7.egg 2 | -------------------------------------------------------------------------------- /scaleiopy/__init__.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | 3 | #from api.im import im_generic_object 4 | #import im_generic_object as Im_Generic_Object 5 | #from api.im import system #ScaleIO_System_Object 6 | 7 | -------------------------------------------------------------------------------- /examples/delete-snapshot.py: -------------------------------------------------------------------------------- 1 | from scaleiopy.scaleio import ScaleIO 2 | from pprint import pprint 3 | import sys 4 | 5 | # How to run: 6 | # python create-snapshot-of-volume.py ip-to-gw user pass consistency_group_id 7 | 8 | sio = ScaleIO("https://" + sys.argv[1] + "/api",sys.argv[2],sys.argv[3],False,"ERROR") 9 | 10 | print "* Delete Snapshot" 11 | result = sio.provisioning.delete_snapshot(sio.get_system_id(), sys.argv[4]) 12 | pprint (result) 13 | 14 | 15 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/mapping/ip_list.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | 3 | class SIO_IP_List(object): 4 | def __init__(self, ip, role): 5 | self.ip = ip 6 | self.role = role 7 | 8 | def __str__(self): 9 | """ 10 | A convenience method to pretty print the contents of the class instance 11 | """ 12 | # to show include all variables in sorted order 13 | return "{} : IP: {} Role: {}".format("IP",self.ip,self.role) 14 | 15 | def __repr__(self): 16 | return self.__str__() 17 | -------------------------------------------------------------------------------- /examples/get-cluster-configuration.py: -------------------------------------------------------------------------------- 1 | from scaleiopy import im 2 | from scaleiopy import scaleioobject as sioobj 3 | #from scaleio import installerfsm as instfsm 4 | import time 5 | import json 6 | from pprint import pprint 7 | 8 | imconn = im.Im("https://192.168.102.12","admin","Scaleio123",verify_ssl=False) # "Password1!") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 9 | imconn._login() 10 | imconn.retrieve_scaleio_cluster_configuration("192.168.102.12", "Scaleio123", "Scaleio123") -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/mapping/link.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | 3 | 4 | class SIO_Link(object): 5 | def __init__(self, href, rel): 6 | self.href = href 7 | self.rel = rel 8 | 9 | def __str__(self): 10 | """ 11 | A convenience method to pretty print the contents of the class instance 12 | """ 13 | # to show include all variables in sorted order 14 | return "{} : Target: '{}' Relative: '{}'".format("Link", self.href, self.rel) 15 | 16 | def __repr__(self): 17 | return self.__str__() 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/unmap_delete-volume.py: -------------------------------------------------------------------------------- 1 | from scaleiopy.scaleio import ScaleIO 2 | from pprint import pprint 3 | import sys 4 | 5 | # How to run: 6 | # python unmap_delete-volume.py ip-to-mdm user pass volume_name 7 | 8 | # Whats this code doing: 9 | # Deletes specified volume and unmap from all mapped SDCs 10 | 11 | sio = ScaleIO("https://" + sys.argv[1] + "/api",sys.argv[2],sys.argv[3],False,"ERROR") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 12 | 13 | sio.provisioning.delete_volume(sio.provisioning.get_volume_by_name(sys.argv[4]), 'ONLY_ME', autoUnmap=True) -------------------------------------------------------------------------------- /examples/unmap-volume.py: -------------------------------------------------------------------------------- 1 | from scaleiopy.scaleio import ScaleIO 2 | from pprint import pprint 3 | import sys 4 | 5 | # How to run: 6 | # python unmap-volume.py ip-to-mdm user pass volume_name sdc_ip 7 | 8 | # Whats this code doing: 9 | # Unmaps specified volume from specified SDC IP 10 | 11 | sio = ScaleIO("https://" + sys.argv[1] + "/api",sys.argv[2],sys.argv[3],False, "ERROR") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 12 | 13 | sio.provisioning.unmap_volume_from_sdc(sio.provisioning.get_volume_by_name(sys.argv[4]), sio.sdc.get_sdc_by_ip(sys.argv[5])) -------------------------------------------------------------------------------- /examples/map-volume.py: -------------------------------------------------------------------------------- 1 | from scaleiopy.scaleio import ScaleIO 2 | from pprint import pprint 3 | import sys 4 | 5 | # How to run: 6 | # python map-volume.py ip-to-mdm user pass volume_name sdc_ip 7 | 8 | # Whats this code doing: 9 | # Maps specified volume to ip address of specified SDC 10 | 11 | sio = ScaleIO("https://" + sys.argv[1] + "/api",sys.argv[2],sys.argv[3],False,"ERROR") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 12 | 13 | sio.provisioning.map_volume_to_sdc(sio.provisioning.get_volume_by_name(sys.argv[4]), sio.sdc.sdc.get_sdc_by_ip(sys.argv[5]), True) 14 | 15 | -------------------------------------------------------------------------------- /examples/delete-volume.py: -------------------------------------------------------------------------------- 1 | from scaleiopy.scaleio import ScaleIO 2 | from pprint import pprint 3 | import sys 4 | 5 | # How to run: 6 | # python delete-volume.py ip-to-mdm user pass volume_name 7 | 8 | # Whats this code doing: 9 | # Unmaps specified volume from any SDCs using it and then delete the volume 10 | 11 | sio = ScaleIO("https://" + sys.argv[1] + "/api",sys.argv[2],sys.argv[3],False,"ERROR") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 12 | 13 | sio.provisioning.delete_volume(sio.provisioning.get_volume_by_name(sys.argv[4]), 'ONLY_ME', autoUnmap=True) 14 | 15 | pprint(sio.volumes) 16 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/metering/statistics.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | #import logging 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | # None 9 | 10 | #log = logging.getLogger(__name__) 11 | 12 | class Statistics(object): 13 | 14 | def __init__(self, connection): 15 | """ 16 | Initialize a new instance 17 | """ 18 | self.conn = connection 19 | 20 | def get(self): 21 | self.conn.connection._check_login() 22 | response = self.conn.connection._do_get("{}/{}{}/{}".format(self.conn.connection._api_url, "instances/System::", str(self.conn.get_system_id()), "relationships/Statistics")).json() 23 | return response 24 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/__init__.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | 3 | 4 | class SIO_Generic_Object(object): 5 | @classmethod 6 | def get_class_name(cls): 7 | """ 8 | A helper method that returns the name of the class. Used by __str__ below 9 | """ 10 | return cls.__name__ 11 | 12 | def __str__(self): 13 | """ 14 | A convenience method to pretty print the contents of the class instance 15 | """ 16 | # to show include all variables in sorted order 17 | return "<{}> @ {}:\n".format(self.get_class_name(), id(self)) + "\n".join( 18 | [" %s: %s" % (key.rjust(22), self.__dict__[key]) for key in sorted(set(self.__dict__))]) 19 | 20 | def __repr__(self): 21 | return self.__str__() 22 | 23 | -------------------------------------------------------------------------------- /examples/create_mapall-volume.py: -------------------------------------------------------------------------------- 1 | from scaleiopy.scaleio import ScaleIO 2 | from pprint import pprint 3 | import sys 4 | 5 | # How to run: 6 | # python create_mapall-volume.py ip-to-mdm user pass volume_name size_mb protection_domain_name storage_pool_name 7 | 8 | # Whats this code doing: 9 | # Create a volume of x MB [min 8192MB] inside specified protectiondomain and storagepool, then map it to all registered SDCs 10 | 11 | sio = ScaleIO("https://" + sys.argv[1] + "/api",sys.argv[2],sys.argv[3],False,"ERROR") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 12 | 13 | sio.create_volume(sys.argv[4], sys.argv[5], sio.get_pd_by_name(sys.argv[6]), sio.get_storage_pool_by_name(sys.argv[7]), enableMapAllSdcs=True) 14 | 15 | pprint(sio.volumes) 16 | -------------------------------------------------------------------------------- /scaleiopy/api/im/mapping/sds_device.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | from im_generic_object import Im_Generic_Object 3 | 4 | class Sds_Device_Object(Im_Generic_Object): 5 | """ 6 | Python object representation of a SDS Device 7 | 8 | """ 9 | 10 | def __init__(self, 11 | devicePath=None, 12 | storagePool=None, 13 | deviceName=None 14 | ): 15 | self.devicePath=devicePath 16 | self.storagePool=storagePool 17 | self.deviceName=deviceName 18 | 19 | @staticmethod 20 | def from_dict(dict): 21 | """ 22 | A convinience method that directly creates a new instance from a passed dictionary (that probably came from a 23 | JSON response from the server. 24 | """ 25 | return Sds_Device_Object(**dict) 26 | 27 | 28 | -------------------------------------------------------------------------------- /scaleiopy/api/im/mapping/syslog_configuration.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | from im_generic_object import Im_Generic_Object 3 | 4 | class Remote_Syslog_Configuration_Object(Im_Generic_Object): 5 | """ 6 | Python object representation of a Remote Syslog Logging. 7 | """ 8 | 9 | def __init__(self, 10 | ip=None, 11 | port=None, 12 | facility=None 13 | ): 14 | self.ip=ip 15 | self.port=port 16 | self.facility=facility #Must be a number between 1-23 17 | 18 | @staticmethod 19 | def from_dict(dict): 20 | """ 21 | A convinience method that directly creates a new instance from a passed dictionary (that probably came from a 22 | JSON response from the server. 23 | """ 24 | return Remote_Syslog_Configuration_Object(**dict) 25 | -------------------------------------------------------------------------------- /scaleiopy/api/im/mapping/sdc.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | from im_generic_object import Im_Generic_Object 3 | from node import Node_Object 4 | 5 | class Sdc_Object(Im_Generic_Object): 6 | """ 7 | Python object representation of a MDM (primary or secondary look eactly the same configuration wise). 8 | """ 9 | 10 | def __init__(self, 11 | node=None, 12 | nodeInfo=None, 13 | splitterRpaIp=None 14 | ): 15 | self.node=Node_Object.from_dict(node) 16 | self.nodeInfo=nodeInfo 17 | self.splitterRpaIp=splitterRpaIp 18 | 19 | @staticmethod 20 | def from_dict(dict): 21 | """ 22 | A convinience method that directly creates a new instance from a passed dictionary (that probably came from a 23 | JSON response from the server. 24 | """ 25 | return Sdc_Object(**dict) 26 | -------------------------------------------------------------------------------- /examples/connect-cluster-test.py: -------------------------------------------------------------------------------- 1 | from scaleiopy.scaleio import ScaleIO 2 | from pprint import pprint 3 | import time 4 | import sys 5 | 6 | sio = ScaleIO("https://" + sys.argv[1] + "/api",sys.argv[2], sys.argv[3], False, "ERROR") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 7 | print "{}{}".format("--- Current ScaleIO API version is: ", sio.connection.get_api_version()) 8 | 9 | print "--- ScaleIO System ---" 10 | pprint(sio.system) 11 | print "--- ScaleIO SDC ---" 12 | pprint(sio.sdc) 13 | print "--- ScaleIO SDS ---" 14 | pprint(sio.sds) 15 | print "--- ScaleiO Volumes ---" 16 | pprint(sio.volumes) 17 | print "--- ScaleIO Protection Domains ---" 18 | pprint(sio.protection_domains) 19 | print "--- ScaleIO Fault Sets ---" 20 | pprint(sio.fault_sets) 21 | print "--- ScaleIO Statistics ---" 22 | pprint(sio.statistics.get()) 23 | 24 | -------------------------------------------------------------------------------- /examples/create-volume.py: -------------------------------------------------------------------------------- 1 | from scaleiopy.scaleio import ScaleIO 2 | from pprint import pprint 3 | import sys 4 | 5 | # How to run: 6 | # python create-volume.py ip-to-mdm user pass volume_name size_mb protection_domain_name storage_pool_name 7 | # Minimal volume size is 8192MB, 8GB and then increments of 8GB 8 | # Whats this code doing: 9 | # Create a volume of x MB inside specified protectiondomain and storagepool 10 | sio = ScaleIO("https://" + sys.argv[1] + "/api",sys.argv[2],sys.argv[3],False,"ERROR") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 11 | sio.provisioning.create_volume(sys.argv[4], sys.argv[5], sio.cluster_pd.get_pd_by_name(sys.argv[6]), sio.cluster_sp.get_storage_pool_by_name(sys.argv[7])) 12 | pprint (sio.cluster_pd.get_pd_by_name(sys.argv[6])) 13 | pprint (sio.cluster_sp.get_storage_pool_by_name(sys.argv[7])) 14 | pprint(sio.volumes) 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | # C extensions 5 | *.so 6 | # Distribution / packaging 7 | .Python 8 | env/ 9 | build/ 10 | develop-eggs/ 11 | dist/ 12 | downloads/ 13 | eggs/ 14 | lib/ 15 | lib64/ 16 | parts/ 17 | build/ 18 | sdist/ 19 | dist/ 20 | var/ 21 | venv/ 22 | *.egg-info/ 23 | *.komodotools/ 24 | *.komodoproject 25 | .installed.cfg 26 | *.egg 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | # Translations 43 | *.mo 44 | *.pot 45 | # Django stuff: 46 | *.log 47 | # Sphinx documentation 48 | docs/_build/ 49 | # PyBuilder 50 | target/ -------------------------------------------------------------------------------- /scaleiopy/api/im/mapping/im_generic_object.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | class Im_Generic_Object(object): 4 | @classmethod 5 | def get_class_name(cls): 6 | """ 7 | A helper method that returns the name of the class. Used by __str__ below 8 | """ 9 | return cls.__name__ 10 | 11 | def __str__(self): 12 | """ 13 | A convinience method to pretty print the contents of the class instance 14 | """ 15 | # to show include all variables in sorted order 16 | return "<{}> @ {}:\n".format(self.get_class_name(), id(self)) + "\n".join( 17 | [" %s: %s" % (key.rjust(22), self.__dict__[key]) for key in sorted(set(self.__dict__))]) 18 | 19 | def __repr__(self): 20 | return self.__str__() 21 | 22 | def to_JSON(self): 23 | return json.dumps(self, default=lambda o: o.__dict__) 24 | 25 | def to_DICT(self): 26 | return self.__dict__ 27 | 28 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/mapping/sio_generic_object.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | class SIO_Generic_Object(object): 4 | @classmethod 5 | def get_class_name(cls): 6 | """ 7 | A helper method that returns the name of the class. Used by __str__ below 8 | """ 9 | return cls.__name__ 10 | 11 | def __str__(self): 12 | """ 13 | A convinience method to pretty print the contents of the class instance 14 | """ 15 | # to show include all variables in sorted order 16 | return "<{}> @ {}:\n".format(self.get_class_name(), id(self)) + "\n".join( 17 | [" %s: %s" % (key.rjust(22), self.__dict__[key]) for key in sorted(set(self.__dict__))]) 18 | 19 | def __repr__(self): 20 | return self.__str__() 21 | 22 | def to_JSON(self): 23 | return json.dumps(self, default=lambda o: o.__dict__) 24 | 25 | def to_DICT(self): 26 | return self.__dict__ 27 | 28 | -------------------------------------------------------------------------------- /examples/connect-cluster.py: -------------------------------------------------------------------------------- 1 | from scaleiopy.scaleio import ScaleIO 2 | from pprint import pprint 3 | import time 4 | import sys 5 | 6 | sio = ScaleIO("https://" + sys.argv[1] + "/api",sys.argv[2], sys.argv[3], False, "ERROR") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 7 | print "{}{}".format("--- Current ScaleIO API version is: ", sio.connection.get_api_version()) 8 | print "--- ScaleIO System ---" 9 | pprint(sio.system) 10 | print "--- ScaleIO SDC ---" 11 | pprint(sio.sdc) 12 | print "--- ScaleIO SDS ---" 13 | pprint(sio.sds) 14 | print "--- ScaleiO Volumes ---" 15 | pprint(sio.volumes) 16 | print "--- ScaleIO Protection Domains ---" 17 | pprint(sio.protection_domains) 18 | print "--- ScaleIO Fault Sets ---" 19 | pprint(sio.fault_sets) 20 | print "--- ScaleIO Storage Pools ---" 21 | pprint(sio.storage_pools) 22 | print "--- ScaleIO Statistics ---" 23 | #pprint(sio.statistics) 24 | pprint(sio.statistics.get()) -------------------------------------------------------------------------------- /scaleiopy/api/im/mapping/tb.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | from im_generic_object import Im_Generic_Object 3 | from node import Node_Object 4 | 5 | class Tb_Object(Im_Generic_Object): 6 | """ 7 | Python object representation of a TB. 8 | """ 9 | 10 | def __init__(self, 11 | node=None, 12 | nodeInfo=None, 13 | tbIPs=None 14 | ): 15 | self.node=Node_Object.from_dict(node) 16 | self.nodeInfo=nodeInfo 17 | self.tbIPs=[] 18 | if tbIPs: 19 | for tbIp in tbIPs: 20 | self.tbIPs.append(tbIp) 21 | 22 | @staticmethod 23 | def from_dict(dict): 24 | """ 25 | A convinience method that directly creates a new instance from a passed dictionary (that probably came from a 26 | JSON response from the server. 27 | """ 28 | #print "*** Class Tb_Object, from_dict(**dict) method:" 29 | #pprint (dict) 30 | return Tb_Object(**dict) 31 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from setuptools import find_packages 4 | from distutils.core import setup 5 | 6 | # requests 2.4.3 and 2.5.1 is known to work. Not sure about 2.5.1+ 7 | install_reqs =[ 8 | 'requests>=2.4.3', 9 | 'requests-toolbelt==0.3.1', 10 | 'wsgiref==0.1' 11 | ] 12 | 13 | setup( 14 | author='Magnus Nilsson', 15 | author_email='magnus@karabas.nu', 16 | name='ScaleIO-py', 17 | description='Python interface to ScaleIO 1.31+ REST API', 18 | version="0.4.0-0", 19 | url='https://github.com/swevm/scaleio-py/', 20 | license='Apache License', 21 | packages=find_packages(), 22 | 23 | classifiers=[ 24 | 'Development Status :: 4 - Beta', 25 | 'Intended Audience :: Developers', 26 | 'Operating System :: OS Independent', 27 | 'Programming Language :: Python', 28 | 'Topic :: Software Development :: Libraries', 29 | 'Topic :: Software Development :: Libraries :: Python Modules', 30 | ], 31 | install_requires=install_reqs, 32 | ) -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/mapping/faultset.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | 3 | # Project imports 4 | from scaleiopy.api.scaleio.mapping.sio_generic_object import SIO_Generic_Object 5 | from scaleiopy.api.scaleio.mapping.link import SIO_Link 6 | 7 | 8 | #class ScaleIO_Fault_Set(SIO_Generic_Object): 9 | class SIO_Fault_Set(SIO_Generic_Object): 10 | 11 | """ ScaleIO Faultset Class repreentation """ 12 | 13 | def __init__(self, 14 | id=None, 15 | name=None, 16 | protectionDomainId=None, 17 | links=None 18 | 19 | ): 20 | self.id=id 21 | self.name=name 22 | self.protectionDomainId=protectionDomainId 23 | self.links = [] 24 | for link in links: 25 | self.links.append(SIO_Link(link['href'],link['rel'])) 26 | 27 | @staticmethod 28 | def from_dict(dict): 29 | """ 30 | A convenience method that directly creates a new instance from a passed dictionary (that probably came from a 31 | JSON response from the server. 32 | """ 33 | return SIO_Fault_Set(**dict) 34 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/cluster/cluster.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | # None 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | from scaleiopy.api.scaleio.system import SIO_System 9 | # None 10 | 11 | 12 | class Cluster(object): 13 | 14 | def __init__(self, connection): 15 | """ 16 | Initialize a new instance 17 | """ 18 | self.conn = connection 19 | 20 | @property 21 | def get(self): 22 | """ 23 | Returns a `list` of all the `System` objects to the cluster. Updates every time - no caching. 24 | :return: a `list` of all the `System` objects known to the cluster. 25 | :rtype: list 26 | """ 27 | self.conn.connection._check_login() 28 | response = self.conn.connection._do_get("{}/{}".format(self.conn.connection._api_url, "types/System/instances")).json() 29 | all_system_objects = [] 30 | for system_object in response: 31 | all_system_objects.append(SIO_System.from_dict(system_object)) 32 | return all_system_objects 33 | -------------------------------------------------------------------------------- /scaleiopy/api/im/mapping/primary_mdm.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | from im_generic_object import Im_Generic_Object 3 | 4 | class Primary_Mdm_Object(Im_Generic_Object): 5 | """ 6 | Python object representation of a MDM (Primary and Secondary look the same configuration wise. 7 | """ 8 | 9 | def __init__(self, 10 | node=None, 11 | nodeInfo=None, 12 | managementIPs=None, 13 | mdmIPs=None 14 | ): 15 | # Data retrieved is a JSON representation of a primary MDM with 'node' as its root 16 | self.managementIPs=[] 17 | if managementIPs: 18 | for mgmtIP in managementIPs: 19 | self.managementIPs.append(mgmtIP) 20 | self.node=ScaleIO_Node_Object.from_dict(node) 21 | self.nodeInfo=nodeInfo 22 | 23 | @staticmethod 24 | def from_dict(dict): 25 | """ 26 | A convinience method that directly creates a new instance from a passed dictionary (that probably came from a 27 | JSON response from the server. 28 | """ 29 | return Primary_Mdm_Object(**dict) 30 | 31 | 32 | -------------------------------------------------------------------------------- /scaleiopy/api/im/mapping/mdm.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | from im_generic_object import Im_Generic_Object 3 | from node import Node_Object 4 | 5 | class Mdm_Object(Im_Generic_Object): 6 | """ 7 | Python object representation of a MDM (primary or secondary look eactly the same configuration wise). 8 | """ 9 | 10 | def __init__(self, 11 | node=None, 12 | nodeInfo=None, 13 | managementIPs=None, 14 | mdmIPs = None 15 | ): 16 | self.managementIPs=[] 17 | if managementIPs: 18 | for mgmtIP in managementIPs: 19 | self.managementIPs.append(mgmtIP) 20 | self.node=Node_Object.from_dict(node) 21 | self.nodeInfo=nodeInfo 22 | self.mdmIPs = [] 23 | if mdmIPs: 24 | for mdmIP in mdmIPs: 25 | self.mdmIPs.append(mdmIP) 26 | 27 | @staticmethod 28 | def from_dict(dict): 29 | """ 30 | A convinience method that directly creates a new instance from a passed dictionary (that probably came from a 31 | JSON response from the server. 32 | """ 33 | return Mdm_Object(**dict) 34 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/mapping/vtree.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | 3 | # Project imports 4 | from scaleiopy.api.scaleio.mapping.sio_generic_object import SIO_Generic_Object 5 | from scaleiopy.api.scaleio.mapping.link import SIO_Link 6 | 7 | 8 | #class ScaleIO_Vtree(SIO_Generic_Object): 9 | class SIO_Vtree(SIO_Generic_Object): 10 | """ ScaleIO VTree Class repreentation 11 | For every Volume created there alway at least one VTree 12 | """ 13 | 14 | def __init__(self, 15 | id=None, 16 | name=None, 17 | baseVolumeId=None, 18 | storagePoolId=None, 19 | links=None 20 | ): 21 | self.id=id 22 | self.name=name 23 | self.baseVolumeId=baseVolumeId 24 | self.storagePoolId=storagePoolId 25 | self.links = [] 26 | for link in links: 27 | self.links.append(SIO_Link(link['href'],link['rel'])) 28 | 29 | @staticmethod 30 | def from_dict(dict): 31 | """ 32 | A convenience method that directly creates a new instance from a passed dictionary (that probably came from a 33 | JSON response from the server. 34 | """ 35 | return SIO_Vtree(**dict) 36 | 37 | -------------------------------------------------------------------------------- /scaleiopy/api/im/mapping/node.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | from im_generic_object import Im_Generic_Object 3 | 4 | #class ScaleIO_Node_Object(Im_Generic_Object): 5 | class Node_Object(Im_Generic_Object): 6 | """ 7 | Do not use. Will be the common denominator for ScaleIO configuration nodes. 8 | All config object should inherit this base 9 | """ 10 | 11 | def __init__(self, 12 | domain=None, 13 | liaPassword=None, 14 | nodeIPs=None, 15 | nodeName=None, 16 | ostype=None, 17 | password=None, 18 | userName=None 19 | ): 20 | self.domain=domain 21 | self.liaPassword=liaPassword 22 | self.nodeIPs=[] 23 | if nodeIPs: 24 | for nodeIp in nodeIPs: 25 | self.nodeIPs.append(nodeIp) 26 | self.nodeName=nodeName 27 | self.ostype=ostype 28 | self.password=password 29 | self.userName=userName 30 | 31 | @staticmethod 32 | def from_dict(dict): 33 | """ 34 | A convinience method that directly creates a new instance from a passed dictionary (that probably came from a 35 | JSON response from the server. 36 | """ 37 | return Node_Object(**dict) 38 | 39 | -------------------------------------------------------------------------------- /examples/create-snapshot-of-volume.py: -------------------------------------------------------------------------------- 1 | from scaleiopy.scaleio import ScaleIO 2 | from scaleiopy.api.scaleio.mapping.snapshotspecification import SIO_SnapshotSpecification 3 | from pprint import pprint 4 | import sys 5 | 6 | # How to run: 7 | # python create-snapshot-of-volume.py ip-to-gw user pass volume_name 8 | 9 | sio = ScaleIO("https://" + sys.argv[1] + "/api",sys.argv[2],sys.argv[3],False,"ERROR") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 10 | 11 | #sio.create_volume_by_pd_name(sys.argv[4], 8192, sio.get_pd_by_name(sys.argv[5]), mapAll=True) 12 | #pprint(sio.volumes) 13 | 14 | 15 | snapSpec = SIO_SnapshotSpecification() 16 | snapSpec.addVolume(sio.provisioning.get_volume_by_name(sys.argv[4])) 17 | print "**********" 18 | print "* Volume *" 19 | print "**********" 20 | pprint (sio.provisioning.get_volume_by_name(sys.argv[4])) 21 | 22 | print "**********************" 23 | print "Snapshot specification" 24 | print "**********************" 25 | pprint (snapSpec) 26 | 27 | print "* Creating Snapshot" 28 | #print "systemId = " + str(sio.get_system_id()) 29 | #pprint (sio.get_system_id()) 30 | result = sio.provisioning.create_snapshot(sio.get_system_id(), snapSpec) 31 | pprint (result) 32 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/mapping/sdc.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | 3 | # Project imports 4 | from scaleiopy.api.scaleio.mapping.sio_generic_object import SIO_Generic_Object 5 | from scaleiopy.api.scaleio.mapping.link import SIO_Link 6 | 7 | #class ScaleIO_SDC(SIO_Generic_Object): 8 | class SIO_SDC(SIO_Generic_Object): 9 | """ ScaleIO SDC Class representation """ 10 | 11 | def __init__(self, 12 | id=None, 13 | links=None, 14 | mdmConnectionState=None, 15 | name=None, 16 | onVmWare=None, 17 | sdcApproved=False, 18 | sdcGuid=None, 19 | sdcIp=None, 20 | systemId=None 21 | ): 22 | 23 | self.id = id 24 | self.name = name 25 | self.mdmConnectionState = mdmConnectionState 26 | self.sdcIp = sdcIp 27 | self.guid = sdcGuid 28 | self.links = [] 29 | for link in links: 30 | self.links.append(SIO_Link(link['href'], link['rel'])) 31 | 32 | @staticmethod 33 | def from_dict(dict): 34 | """ 35 | A convenience method that directly creates a new instance from a passed dictionary (that probably came from a 36 | JSON response from the server. 37 | """ 38 | return SIO_SDC(**dict) 39 | -------------------------------------------------------------------------------- /scaleiopy/api/im/mapping/callhome_configuration.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | from im_generic_object import Im_Generic_Object 3 | 4 | class Call_Home_Configuration_Object(Im_Generic_Object): 5 | """ 6 | Python object representation of a MDM (primary or secondary look eactly the same configuration wise). 7 | """ 8 | 9 | def __init__(self, 10 | emailFrom=None, 11 | mdmUsername=None, 12 | mdmPassword=None, 13 | customerName=None, 14 | host=None, 15 | port=None, 16 | tls=None, 17 | smtpUsername=None, 18 | smtpPassword=None, 19 | alertEmailTo=None, 20 | severity=None 21 | ): 22 | self.emailFrom=emailFrom 23 | self.mdmUsername=mdmUsername 24 | self.mdmPassword=mdmPassword 25 | self.customerName=customerName 26 | self.host=host 27 | self.port=port 28 | self.tls=tls 29 | self.smtpUsername=smtpUsername 30 | self.smtpPassword=smtpPassword 31 | self.alertEmailTo=alertEmailTo 32 | self.severity=severity 33 | 34 | @staticmethod 35 | def from_dict(dict): 36 | """ 37 | A convinience method that directly creates a new instance from a passed dictionary (that probably came from a 38 | JSON response from the server. 39 | """ 40 | return Call_Home_Configuration_Object(**dict) 41 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/mapping/snapshotspecification.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | 3 | # Project imports 4 | from scaleiopy.api.scaleio.mapping.sio_generic_object import SIO_Generic_Object 5 | 6 | 7 | class SIO_SnapshotSpecification(SIO_Generic_Object): 8 | """ 9 | Input: list of SIO Snapshot definitions 10 | For example: { "snapshotDefs": [ {" volumeId":"2dd9132300000000", "snapshotName":"000_snap1"}, {"volumeId":"2dd9132300000004", "snapshotName":"004_snap1" }]} 11 | If adding more than one Volume to a Snapshot definition it will autoamtically be trated as a consistency group 12 | 13 | Return: 14 | volumeIdList snapshotGroupId 15 | for example: 16 | {"volumeIdList":[ "2dd9132400000001"], "snapshotGroupId":"d2e53daf00000001"} 17 | """ 18 | def __init__(self): 19 | self._snapshotList = [] 20 | 21 | def addVolume(self, volObj, snapName=None): 22 | if snapName is None: 23 | self._snapshotList.append({"volumeId": volObj.id, "snapshotName": volObj.name + "snapshot"}) 24 | else: 25 | self._snapshotList.append({"volumeId": volObj.id, "snapshotName": snapName}) 26 | 27 | def removeVolume(self, volObj): 28 | for i in range(len(self._snapshotList)): 29 | if self._snapshotList[i]['volumeId'] == volObj.id: 30 | del self._snapshotList[i] 31 | break 32 | 33 | def __to_dict__(self): 34 | return {"snapshotDefs" : self._snapshotList} 35 | 36 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/cluster/storagepool.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | # None 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | # None 9 | 10 | 11 | class StoragePool(object): 12 | 13 | def __init__(self, connection): 14 | """ 15 | Initialize a new instance 16 | """ 17 | self.conn = connection 18 | 19 | @property 20 | def get(self): 21 | pass 22 | 23 | def get_storage_pool_by_name(self, name): 24 | """ 25 | Get ScaleIO StoragePool object by its name 26 | :param name: Name of StoragePool 27 | :return: ScaleIO StoragePool object 28 | :raise KeyError: No StoragePool with specified name found 29 | :rtype: StoragePool object 30 | """ 31 | for storage_pool in self.conn.storage_pools: 32 | if storage_pool.name == name: 33 | return storage_pool 34 | raise KeyError("Storage pool of that name not found") 35 | 36 | def get_storage_pool_by_id(self, id): 37 | """ 38 | Get ScaleIO SDS ofbject by its id 39 | :param name: Name of StoragePool 40 | :return: ScaleIO StoraegPool object 41 | :raise KeyError: No StoragePool with specified id found 42 | :rtype: StoragePool object 43 | """ 44 | for storage_pool in self.conn.storage_pools: 45 | if storage_pool.id == id: 46 | return storage_pool 47 | raise KeyError("Storage Pool with that ID not found") -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/mapping/protection_domain.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | 3 | # Project imports 4 | from scaleiopy.api.scaleio.mapping.sio_generic_object import SIO_Generic_Object 5 | from scaleiopy.api.scaleio.mapping.link import SIO_Link 6 | 7 | 8 | class SIO_Protection_Domain(SIO_Generic_Object): 9 | #class SIO_Protection_Domain(SIO_Generic_Object): 10 | """ ScaleIO Protection Domain Class representation """ 11 | 12 | def __init__(self, 13 | id=None, 14 | links=None, 15 | name=None, 16 | overallIoNetworkThrottlingEnabled=None, 17 | overallIoNetworkThrottlingInKbps=None, 18 | protectionDomainState=None, 19 | rebalanceNetworkThrottlingEnabled=None, 20 | rebalanceNetworkThrottlingInKbps=None, 21 | rebuildNetworkThrottlingEnabled=None, 22 | rebuildNetworkThrottlingInKbps=None, 23 | systemId=None 24 | ): 25 | self.id=id 26 | self.links = [] 27 | for link in links: 28 | self.links.append(SIO_Link(link['href'], link['rel'])) 29 | self.name=name 30 | self.overall_network_throttle_enabled=overallIoNetworkThrottlingEnabled 31 | self.overall_network_throttle_kbps=overallIoNetworkThrottlingInKbps 32 | self.protection_domain_state=protectionDomainState 33 | self.rebalance_network_throttle_enabled=rebalanceNetworkThrottlingEnabled 34 | self.rebalance_network_throttle_kbps=rebalanceNetworkThrottlingInKbps 35 | self.rebuild_network_throttle_enabled=rebuildNetworkThrottlingEnabled 36 | self.rebuild_network_throttle_kbps=rebuildNetworkThrottlingInKbps 37 | self.system_id=systemId 38 | 39 | 40 | @staticmethod 41 | def from_dict(dict): 42 | """ 43 | A convenience method that directly creates a new instance from a passed dictionary (that probably came from a 44 | JSON response from the server. 45 | """ 46 | return SIO_Protection_Domain(**dict) 47 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/mapping/volume.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | 3 | # Project imports 4 | from scaleiopy.api.scaleio.mapping.sio_generic_object import SIO_Generic_Object 5 | from scaleiopy.api.scaleio.mapping.link import SIO_Link 6 | import time 7 | 8 | #class ScaleIO_Volume(SIO_Generic_Object): 9 | class SIO_Volume(SIO_Generic_Object): 10 | 11 | """ ScaleIO Volume Class representation """ 12 | 13 | def __init__(self, 14 | ancestorVolumeId=None, 15 | consistencyGroupId=None, 16 | creationTime=None, 17 | id=None, 18 | isObfuscated=None, 19 | links=False, 20 | mappedScsiInitiatorInfo=None, 21 | mappedSdcInfo=None, 22 | mappingToAllSdcsEnabled=None, 23 | name=None, 24 | sizeInKb=0, 25 | storagePoolId=None, 26 | useRmcache=False, 27 | volumeType=None, 28 | vtreeId=None 29 | ): 30 | self.id = id 31 | self.links = [] 32 | for link in links: 33 | self.links.append(SIO_Link(link['href'], link['rel'])) 34 | self.ancestor_volume = ancestorVolumeId 35 | self.consistency_group_id=consistencyGroupId 36 | self.creation_time=time.gmtime(creationTime) 37 | self.id=id 38 | self.name = name 39 | self.is_obfuscated = isObfuscated 40 | self.mapped_scsi_initiators = mappedScsiInitiatorInfo 41 | self.mapped_sdcs = mappedSdcInfo 42 | self.size_kb = sizeInKb 43 | self.storage_pool_id = storagePoolId 44 | self.use_cache = useRmcache 45 | self.volume_type = volumeType 46 | self.vtree_id = vtreeId 47 | self.mappingToAllSdcsEnabled = mappingToAllSdcsEnabled 48 | 49 | 50 | @staticmethod 51 | def from_dict(dict): 52 | """ 53 | A convenience method that directly creates a new instance from a passed dictionary (that probably came from a 54 | JSON response from the server. 55 | """ 56 | return SIO_Volume(**dict) 57 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/mapping/sds.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | 3 | # Project imports 4 | from scaleiopy.api.scaleio.mapping.sio_generic_object import SIO_Generic_Object 5 | from scaleiopy.api.scaleio.mapping.ip_list import SIO_IP_List 6 | from scaleiopy.api.scaleio.mapping.link import SIO_Link 7 | 8 | #class ScaleIO_SDS(SIO_Generic_Object): 9 | class SIO_SDS(SIO_Generic_Object): 10 | """ ScaleIO SDS Class representation """ 11 | 12 | def __init__(self, 13 | drlMode=None, 14 | ipList=None, 15 | faultSetId=None, 16 | id=None, 17 | links=None, 18 | mdmConnectionState=None, 19 | membershipState=None, 20 | name=None, 21 | numOfIoBuffers=None, 22 | onVmWare=None, 23 | port=None, 24 | protectionDomainId=None, 25 | rmcacheEnabled=None, 26 | rmcacheFrozen=None, 27 | rmcacheMemoryAllocationState=None, 28 | rmcacheSizeInKb=None, 29 | sdsState=None 30 | ): 31 | self.drl_mode = drlMode 32 | self.ip_list = [] 33 | for ip in ipList: 34 | self.ip_list.append(SIO_IP_List(ip['ip'],ip['role'])) 35 | self.fault_set_id = faultSetId 36 | self.id = id 37 | self.links = [] 38 | for link in links: 39 | self.links.append(SIO_Link(link['href'],link['rel'])) 40 | self.mdm_connection_state = mdmConnectionState 41 | self.membership_state = membershipState 42 | self.name = name 43 | self.number_io_buffers = int(numOfIoBuffers) 44 | self.on_vmware = onVmWare 45 | self.port = port 46 | self.protection_domain_id = protectionDomainId 47 | self.rm_cache_enabled = rmcacheEnabled 48 | self.rm_cache_frozen = rmcacheFrozen 49 | self.rm_cachem_memory_allocation = rmcacheMemoryAllocationState 50 | self.rm_cache_size_kb = rmcacheSizeInKb 51 | self.sds_state=sdsState 52 | 53 | @staticmethod 54 | def from_dict(dict): 55 | """ 56 | A convenience method that directly creates a new instance from a passed dictionary (that probably came from a 57 | JSON response from the server. 58 | """ 59 | return SIO_SDS(**dict) 60 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/cluster/faultset.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | # None 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | # None 9 | 10 | 11 | class FaultSet(object): 12 | 13 | def __init__(self, connection): 14 | """ 15 | Initialize a new instance 16 | """ 17 | self.conn = connection 18 | 19 | @property 20 | def get(self): 21 | """ 22 | Returns a `list` of all the `System` objects to the cluster. Updates every time - no caching. 23 | :return: a `list` of all the `System` objects known to the cluster. 24 | :rtype: list 25 | """ 26 | self.conn.connection._check_login() 27 | response = self.conn.connection._do_get("{}/{}".format(self.conn.connection._api_url, "types/System/instances")).json() 28 | all_system_objects = [] 29 | for system_object in response: 30 | all_system_objects.append(self.conn.System.from_dict(system_object)) 31 | return all_system_objects 32 | 33 | def get_faultset_by_id(self, id): 34 | for fs in self.conn.fault_sets: 35 | if fs.id == id: 36 | return fs 37 | raise KeyError("FaultSet with ID " + id + " not found") 38 | 39 | def get_faultset_by_name(self,name): 40 | for fs in self.conn.fault_sets: 41 | if fs.name == name: 42 | return fs 43 | raise KeyError("FaultSet with NAME " + name + " not found") 44 | 45 | def set_faultset_name(self, name, fsObj): 46 | """ 47 | Set name for Faultset 48 | :param name: Name of Faultset 49 | :param fsObj: ScaleIO FS object 50 | :return: POST request response 51 | :rtype: Requests POST response object 52 | """ 53 | # Set name of FaultSet 54 | self.conn.connection._check_login() 55 | faultSetNameDict = {'Name': name} 56 | # This one is the most logical name comparing to other methods. 57 | response = self.conn.connection._do_post("{}/{}{}/{}".format(self.conn.connection._api_url, "types/FaultSet::", fsObj.id, 'instances/action/setFaultSetName'), json=faultSetNameSdcDict) 58 | 59 | # This is how its documented in REST API Chapter 60 | #response = self._do_post("{}/{}{}/{}".format(self._api_url, "types/FaultSet::", fsObj.id, 'instances/action/setFaultSetName'), json=faultsetNameSdcDict) 61 | return response -------------------------------------------------------------------------------- /scaleiopy/api/im/mapping/sds.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | from im_generic_object import Im_Generic_Object 3 | from node import Node_Object 4 | from sds_device import Sds_Device_Object 5 | 6 | class Sds_Object(Im_Generic_Object): 7 | """ 8 | Python object representation of a SDS. 9 | To add a SDS to a faultset this is where its done. SDSs cannot be added to FaultSets after they have been added to protectiondomain. API wise they can be removed and re added (not sure how re-add is done though) 10 | 11 | """ 12 | 13 | def __init__(self, 14 | node=None, 15 | nodeInfo=None, 16 | sdsName=None, 17 | protectionDomain=None, 18 | faultSet=None, 19 | allIPs=None, 20 | sdsOnlyIPs=None, 21 | sdcOnlyIPs=None, 22 | devices=None, 23 | optimized=None, 24 | port=None 25 | ): 26 | self.node=Node_Object.from_dict(node) 27 | self.nodeInfo=nodeInfo 28 | self.sdsName=sdsName 29 | self.protectionDomain = protectionDomain 30 | self.faultSet=faultSet # Is this as easy as giving a string with a Faultset name??? (Its not documented) 31 | self.allIPs=[] 32 | for allIp in allIPs: 33 | self.allIPs.append(allIp) 34 | self.sdsOnlyIPs=[] 35 | if sdsOnlyIPs: 36 | for sdsOnlyIp in sdsOnlyIPs: 37 | self.sdsOnlyIPs.append(sdsOnlyIp) 38 | self.sdcOnlyIPs=[] 39 | if sdcOnlyIPs: 40 | for sdcOnlyIp in sdcOnlyIPs: 41 | self.sdcOnlyIPs.append(sdcOnlyIp) 42 | self.devices=[] 43 | if devices: 44 | for device in devices: 45 | self.devices.append(Sds_Device_Object.from_dict(device)) 46 | self.optimized=optimized 47 | self.port=port 48 | 49 | def addDevice(self, devicePath, storagePool, deviceName): 50 | #print "Add Device:" 51 | device_dict = {'devicePath': devicePath, 'storagePool': storagePool, 'deviceName': deviceName} 52 | #pprint (device_dict) #(Sds_Device_Object(devicePath, storagePool, deviceName).to_JSON()) 53 | self.devices.append(Sds_Device_Object.from_dict(device_dict)) 54 | 55 | def removeDevice(devObject): 56 | pass 57 | 58 | 59 | @staticmethod 60 | def from_dict(dict): 61 | """ 62 | A convinience method that directly creates a new instance from a passed dictionary (that probably came from a 63 | JSON response from the server. 64 | """ 65 | return Sds_Object(**dict) 66 | -------------------------------------------------------------------------------- /doc/minimal-cluster_working.json: -------------------------------------------------------------------------------- 1 | {"installationId":null,"mdmIPs":["192.168.102.12","192.168.102.13"],"mdmPassword":"Scaleio123","liaPassword":"Scaleio123","licenseKey":null,"primaryMdm":{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.12"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"managementIPs":null,"mdmIPs":["192.168.102.12"]},"secondaryMdm":{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.13"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"managementIPs":null,"mdmIPs":["192.168.102.13"]},"tb":{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.11"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"tbIPs":["192.168.102.11"]},"sdsList":[{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.11"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"sdsName":"SDS_[192.168.102.11]","protectionDomain":"default","faultSet":null,"allIPs":["192.168.102.11"],"sdsOnlyIPs":null,"sdcOnlyIPs":null,"devices":[{"devicePath":"/dev/sdb","storagePool":null,"deviceName":null}],"optimized":false,"port":7072},{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.12"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"sdsName":"SDS_[192.168.102.12]","protectionDomain":"default","faultSet":null,"allIPs":["192.168.102.12"],"sdsOnlyIPs":null,"sdcOnlyIPs":null,"devices":[{"devicePath":"/dev/sdb","storagePool":null,"deviceName":null}],"optimized":false,"port":7072},{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.13"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"sdsName":"SDS_[192.168.102.13]","protectionDomain":"default","faultSet":null,"allIPs":["192.168.102.13"],"sdsOnlyIPs":null,"sdcOnlyIPs":null,"devices":[{"devicePath":"/dev/sdb","storagePool":null,"deviceName":null}],"optimized":false,"port":7072}],"sdcList":[{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.11"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"splitterRpaIp":null},{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.12"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"splitterRpaIp":null},{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.13"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"splitterRpaIp":null}],"callHomeConfiguration":null,"remoteSyslogConfiguration":null} 2 | -------------------------------------------------------------------------------- /doc/minimal-cluster.json: -------------------------------------------------------------------------------- 1 | {"installationId":null,"mdmIPs":["192.168.102.12","192.168.102.13"],"mdmPassword":"Scaleio123","liaPassword":"Scaleio123","licenseKey":null,"primaryMdm":{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.12"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"managementIPs":null,"mdmIPs":["192.168.102.12"]},"secondaryMdm":{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.13"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"managementIPs":null,"mdmIPs":["192.168.102.13"]},"tb":{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.11"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"tbIPs":["192.168.102.11"]},"sdsList":[{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.11"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"sdsName":"SDS_[192.168.102.11]","protectionDomain":"default","faultSet":null,"allIPs":["192.168.102.11"],"sdsOnlyIPs":null,"sdcOnlyIPs":null,"devices":[{"devicePath":"/home/vagrant/scaleio1","storagePool":null,"deviceName":null}],"optimized":false,"port":7072},{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.12"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"sdsName":"SDS_[192.168.102.12]","protectionDomain":"default","faultSet":null,"allIPs":["192.168.102.12"],"sdsOnlyIPs":null,"sdcOnlyIPs":null,"devices":[{"devicePath":"/home/vagrant/scaleio1","storagePool":null,"deviceName":null}],"optimized":false,"port":7072},{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.13"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"sdsName":"SDS_[192.168.102.13]","protectionDomain":"default","faultSet":null,"allIPs":["192.168.102.13"],"sdsOnlyIPs":null,"sdcOnlyIPs":null,"devices":[{"devicePath":"/home/vagrant/scaleio1","storagePool":null,"deviceName":null}],"optimized":false,"port":7072}],"sdcList":[{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.11"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"splitterRpaIp":null},{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.12"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"splitterRpaIp":null},{"node":{"ostype":"linux","nodeName":null,"nodeIPs":["192.168.102.13"],"domain":null,"userName":"root","password":"vagrant","liaPassword":null},"nodeInfo":null,"splitterRpaIp":null}],"callHomeConfiguration":null,"remoteSyslogConfiguration":null} 2 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/cluster/protectiondomain.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | # None 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | # None 9 | 10 | 11 | class ProtectionDomain(object): 12 | 13 | def __init__(self, connection): 14 | """ 15 | Initialize a new instance 16 | """ 17 | self.conn = connection 18 | 19 | @property 20 | def get(self): 21 | """ 22 | Returns a `list` of all the `System` objects to the cluster. Updates every time - no caching. 23 | :return: a `list` of all the `System` objects known to the cluster. 24 | :rtype: list 25 | """ 26 | self.conn.connection._check_login() 27 | response = self.conn.connection._do_get("{}/{}".format(self.conn.connection._api_url, "types/System/instances")).json() 28 | all_system_objects = [] 29 | for system_object in response: 30 | all_system_objects.append(self.conn.System.from_dict(system_object)) 31 | return all_system_objects 32 | 33 | def get_pd_by_name(self, name): 34 | """ 35 | Get ScaleIO ProtectionDomain object by its name 36 | :param name: Name of ProtectionDomain 37 | :return: ScaleIO ProtectionDomain object 38 | :raise KeyError: No ProtetionDomain with specified name found 39 | :rtype: ProtectionDomain object 40 | """ 41 | for pd in self.conn.protection_domains: 42 | if pd.name == name: 43 | return pd 44 | raise KeyError("Protection Domain NAME " + name + " not found") 45 | 46 | def get_pd_by_id(self, id): 47 | """ 48 | Get ScaleIO ProtectionDomain object by its id 49 | :param name: ID of ProtectionDomain 50 | :return: ScaleIO ProctectionDomain object 51 | :raise KeyError: No ProtectionDomain with specified name found 52 | :rtype: ProtectionDomain object 53 | """ 54 | for pd in self.conn.protection_domains: 55 | if pd.id == id: 56 | return pd 57 | raise KeyError("Protection Domain with ID " + id + " not found") 58 | 59 | def create_protection_domain(self, pdObj, **kwargs): 60 | # TODO: 61 | # Check if object parameters are the correct ones 62 | self.conn.connection._check_login() 63 | response = self.conn.connection._do_post("{}/{}".format(self.conn.connection._api_url, "types/Volume/instances"), json=pdObj.__to_dict__()()) 64 | return response 65 | 66 | def delete_potection_domain(self, pdObj): 67 | """ 68 | :param pdObj: ID of ProtectionDomain 69 | 70 | type: POST 71 | Required: 72 | Protection Domain Object 73 | Return: 74 | """ 75 | self.conn.connection._check_login() 76 | response = self.conn.connection._do_post("{}/{}{}/{}".format(self.conn.connection._api_url, "instances/ProtectionDomain::", pdObj.id, 'action/removeProtectionDomain')) 77 | return response -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # ScaleIO SDK Changelog 2 | 3 | Authors: Magnus Nilsson & Matt Cowger 4 | 5 | ## v0.0.1 6 | Initial code to test integration against ScaleIO API 7 | 8 | ## v0.1 (only in Master branch) 9 | * Rewrote module significantly to use classes for nearly all objects 10 | * Rewrote GET methods to be pore pythonic 11 | * Added support for lookups of objects by name or ID 12 | * Added requirements.txt 13 | * Added setup.py 14 | * Added README with examples 15 | * Added .gitignore 16 | * Changed structure of package to follow community guidelines 17 | * Removed old examples because of rewrite of module and some of them not working (like create volumes) 18 | 19 | ## v0.2 (only in master branch, never tagged as branch) 20 | * Create volume 21 | * Delete Volume 22 | * Map Volume to SDC 23 | * Unmap volume from SDC 24 | * Additions to Volume and SDC Class 25 | 26 | ## v0.3 (will get its own branch) 27 | * Map to SDC(s) at the same time of creating a volume 28 | * Auto unmap of SDC when deleting volume that have existing mappings 29 | * Error checking (basics) - Might be pushed to v0.3+ 30 | * Make naming of methods and attributes consistent (match to ScaleIO API documentation) - Might be pushed to v0.3+ 31 | * Statistics gathering (maybe in 0.3+ likely 0.4) 32 | * Add a changelog 33 | * IM integration to install new cluster 34 | * Add examples 35 | 36 | ## v0.31 (beta1) 37 | * Create Snapshot by Volume Name 38 | * Create Snapshot by Volume Id 39 | * Delete Snapshot 40 | * Expand Volume 41 | * Clean up API call methods. Now GET and POST calls happen in own methods and all other dependencies in the module uses same methods 42 | * Logging capabilities 43 | * Update examples to use new logging facility 44 | * Create ProtectionDomain (WIP) 45 | * Delete ProtectionDomain (WIP) 46 | * Create FaultSet (WIP) 47 | * Delete FaultSet (WIP) 48 | * set name functionality for FS, PD, SDC and SDS 49 | 50 | ## v0.32 (beta2) 51 | * Faultset folded into IM integration as it depend on removing SDS to change - Create Faultset can be done at install time 52 | * Protection Domain Mgmt removed - Pushed to v0.4 53 | * Remove and Manage Faultsets removed - Pushed to v0.4 54 | * Better logging - Configurable at __init__ 55 | 56 | ## v0.33 (beta3) 57 | * Support for 1.32 - Create Volume 58 | * PIP package - Install with: pip install ScaleIO-py 59 | * Store API version to optimize code path for different versions - Not needed to be compatible with basic Mgmt for both 1.31 and 1.32 (need to be used for Metric collection) 60 | * delete_volume() - Obey kwargs 'autoUnmap' 61 | * map_volume_to_sdc() and unmap_volume_from_sdc() - Changed kwargs to 'enableMapAllSdcs' 62 | 63 | ## v0.34 (beta4) WIP 64 | * Make logging consisent 65 | * Error handling - Find a consistent way to return errors to caller (caller have to use try/catch???) 66 | * Never released 67 | 68 | ## v0.4 69 | * Restructure of code - will be backwards compatible with v0.3beta3 70 | 71 | ## v0.4+ 72 | * Unit Testing 73 | * Add SDS 74 | * Remove SDS 75 | * IM integration to automate upgrade of cluster software 76 | * IM integration to allow expansion of cluster with new nodes (SDS, SDCs 77 | * Register/Unregister SDC in cluster 78 | * Statistics gathering 79 | * Move classes (at least the bigger ones) into own files -------------------------------------------------------------------------------- /scaleiopy/api/im/system.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | 3 | from mapping.im_generic_object import Im_Generic_Object 4 | 5 | from scaleiopy.api.im.mapping.node import Node_Object 6 | from scaleiopy.api.im.mapping.mdm import Mdm_Object 7 | from scaleiopy.api.im.mapping.callhome_configuration import Call_Home_Configuration_Object 8 | from scaleiopy.api.im.mapping.tb import Tb_Object 9 | from scaleiopy.api.im.mapping.syslog_configuration import Remote_Syslog_Configuration_Object 10 | from scaleiopy.api.im.mapping.sdc import Sdc_Object 11 | from scaleiopy.api.im.mapping.sds import Sds_Object 12 | from scaleiopy.api.im.mapping.sds_device import Sds_Device_Object 13 | #from scaleiopy.api.im.system import System_Object 14 | 15 | class System_Object(Im_Generic_Object): 16 | """ 17 | Root configuration object 18 | """ 19 | 20 | def __init__(self, 21 | installationId=None, 22 | mdmIPs=None, 23 | mdmPassword=None, 24 | liaPassword=None, 25 | licenseKey=None, 26 | primaryMdm=None, 27 | secondaryMdm=None, 28 | tb=None, 29 | sdsList=None, 30 | sdcList=None, 31 | callHomeConfiguration=None, 32 | remoteSyslogConfiguration=None 33 | 34 | ): 35 | self.installationId=installationId 36 | self.mdmIPs = [] 37 | for mdmIP in mdmIPs: 38 | self.mdmIPs.append(mdmIP) 39 | self.mdmPassword=mdmPassword 40 | self.liaPassword=liaPassword 41 | self.licenseKey=licenseKey 42 | self.primaryMdm=Mdm_Object.from_dict(primaryMdm) 43 | self.secondaryMdm=Mdm_Object.from_dict(secondaryMdm) 44 | self.tb=Tb_Object.from_dict(tb) 45 | self.sdsList=[] 46 | for sds in sdsList: 47 | self.sdsList.append(Sds_Object.from_dict(sds)) 48 | self.sdcList=[] 49 | for sdc in sdcList: 50 | self.sdcList.append(Sdc_Object.from_dict(sdc)) 51 | if callHomeConfiguration is None: 52 | self.callHomeConfiguration = None 53 | else: 54 | self.callHomeConfiguration = callHomeConfiguration 55 | if remoteSyslogConfiguration is None: 56 | self.remoteSyslogConfiguration = None 57 | else: 58 | # Might be a good idea to check type(remoteSyslogConfiguration) and verify class type 59 | self.remoteSyslogConfiguration = remoteSyslogConfiguration 60 | def setLiaPassword(self, value): 61 | self.liaPassword = value 62 | 63 | def setMdmPassword(self, value): 64 | self.mdmPassword = value 65 | 66 | def addSds(self, sdsObj): 67 | self.sdsList.append(sdsObj) 68 | 69 | def removeSds(self, sdsObj): 70 | pass 71 | 72 | def addSdc(self, sdcObj): 73 | pass 74 | 75 | def removeSdc(self, sdcObj): 76 | pass 77 | 78 | def addCallHomeConfiguration(self, callhomeConfObj): 79 | self.callHomeConfiguration = callhomeConfObj.to_JSON() 80 | 81 | def removeCallHomeConfiguration(self): 82 | self.callHomeConfiguration = None 83 | 84 | def addSyslogConfiguration(self, syslogConfObj): 85 | self.remoteSyslogConfiguration = callhomeConfObj.to_JSON() 86 | 87 | def removeSyslogConfiguration(self): 88 | self.remoteSyslogConfiguration = None 89 | 90 | def addPrimaryMdm(self, mdmObj): 91 | pass 92 | 93 | def addSecondaryMdm(self, mdmObj): 94 | pass 95 | 96 | def addTb(self, tbObj): 97 | pass 98 | 99 | 100 | 101 | @staticmethod 102 | def from_dict(dict): 103 | """ 104 | A convenience method that directly creates a new instance from a passed dictionary (that probably came from a 105 | JSON response from the server. 106 | """ 107 | return ScaleIO_System_Object(**dict) 108 | 109 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/mapping/storage_pool.py: -------------------------------------------------------------------------------- 1 | # Import 2 | 3 | # Project imports 4 | from scaleiopy.api.scaleio.mapping.sio_generic_object import SIO_Generic_Object 5 | from scaleiopy.api.scaleio.mapping.link import SIO_Link 6 | 7 | 8 | class SIO_Storage_Pool(SIO_Generic_Object): 9 | """ ScaleIO Storage Pool Class representation """ 10 | 11 | def __init__(self, 12 | id=None, 13 | name=None, 14 | links=None, 15 | sparePercentage=None, 16 | rebuildEnabled=None, 17 | rebalanceEnabled=None, 18 | rebuildIoPriorityPolicy=None, #unlimited or limitNumOfConcurrentIos or favorAppIos or dynamicBwThrottling 19 | rebalanceIoPriorityPolicy=None, 20 | rebuildIoPriorityNumOfConcurrentIosPerDevice=None, 21 | rebalanceIoPriorityNumOfConcurrentIosPerDevice=None, 22 | rebuildIoPriorityBwLimitPerDeviceInKbps=None, 23 | rebalanceIoPriorityBwLimitPerDeviceInKbps=None, 24 | rebuildIoPriorityAppIopsPerDeviceThreshold=None, 25 | rebalanceIoPriorityAppIopsPerDeviceThreshold=None, 26 | rebuildIoPriorityAppBwPerDeviceThresholdInKbps=None, 27 | rebalanceIoPriorityAppBwPerDeviceThresholdInKbps=None, 28 | rebuildIoPriorityQuietPeriodInMsec=None, 29 | rebalanceIoPriorityQuietPeriodInMsec=None, 30 | numOfParallelRebuildRebalanceJobsPerDevice=None, 31 | protectionDomainId=None, 32 | zeroPaddingEnabled=None, 33 | useRmcache=None, 34 | rmcacheWriteHandlingMode=None, #Passthrough or Cached 35 | # v1.32 specific 36 | backgroundScannerMode = None, 37 | backgroundScannerBWLimitKBps = None 38 | ): 39 | self.id=id 40 | self.name=name 41 | self.links = [] 42 | for link in links: 43 | self.links.append(SIO_Link(link['href'], link['rel'])) 44 | self.spare_percentage=sparePercentage 45 | self.rebuild_enabled=rebuildEnabled 46 | self.rebalance_enabled=rebalanceEnabled 47 | self.revuild_io_prioroti_policy=rebuildIoPriorityPolicy #unlimited or limitNumOfConcurrentIos or favorAppIos or dynamicBwThrottling 48 | self.rebalance_prioritiy_policy=rebalanceIoPriorityPolicy 49 | self.rebuild_io_prioroity_num_of_concurrent_ios_per_device=rebuildIoPriorityNumOfConcurrentIosPerDevice 50 | self.rebalance_io_priority_num_of_concurrent_ios_per_device=rebalanceIoPriorityNumOfConcurrentIosPerDevice 51 | self.revuild_io_priority_bw_limits_per_device_in_kbps=rebuildIoPriorityBwLimitPerDeviceInKbps 52 | self.rebalance_io_priority_bw_limit_per_device_in_kbps=rebalanceIoPriorityBwLimitPerDeviceInKbps 53 | self.rebuild_io_priority_app_iops_per_device_threshold=rebuildIoPriorityAppIopsPerDeviceThreshold 54 | self.rebalance_io_priority_app_iops_per_devicE_threshold=rebalanceIoPriorityAppIopsPerDeviceThreshold 55 | self.rebuild_io_priority_app_bw_per_device_threshold_in_kbps=rebuildIoPriorityAppBwPerDeviceThresholdInKbps 56 | self.rebalance_priority_apps_bw_per_device_threshold_in_kbps=rebalanceIoPriorityAppBwPerDeviceThresholdInKbps 57 | self.rebuild_io_priority_quite_period_in_msec=rebuildIoPriorityQuietPeriodInMsec 58 | self.reabalance_io_priority_quiet_period_in_msec=rebalanceIoPriorityQuietPeriodInMsec 59 | self.num_of_parallel_rebuild_rebalance_job_per_device=numOfParallelRebuildRebalanceJobsPerDevice 60 | self.protection_domain_id=protectionDomainId 61 | self.zero_padding_enabled=zeroPaddingEnabled 62 | self.use_rm_cache=useRmcache 63 | self.rmcache_write_handling_mode=rmcacheWriteHandlingMode 64 | # v1.32 specific 65 | self.backgroundScanneMode = backgroundScannerMode 66 | self.backgroundScannerBWLimitKBps = backgroundScannerBWLimitKBps 67 | @staticmethod 68 | def from_dict(dict): 69 | """ 70 | A convenience method that directly creates a new instance from a passed dictionary (that probably came from a 71 | JSON response from the server. 72 | """ 73 | return SIO_Storage_Pool(**dict) 74 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/system.py: -------------------------------------------------------------------------------- 1 | # Imports 2 | 3 | # External imports 4 | 5 | 6 | # Project imports 7 | from scaleiopy.api.scaleio.mapping.sio_generic_object import SIO_Generic_Object 8 | 9 | from scaleiopy.api.scaleio.mapping.faultset import SIO_Fault_Set 10 | from scaleiopy.api.scaleio.mapping.ip_list import SIO_IP_List 11 | from scaleiopy.api.scaleio.mapping.link import SIO_Link 12 | from scaleiopy.api.scaleio.mapping.protection_domain import SIO_Protection_Domain 13 | from scaleiopy.api.scaleio.mapping.sdc import SIO_SDC 14 | from scaleiopy.api.scaleio.mapping.sds import SIO_SDS 15 | from scaleiopy.api.scaleio.mapping.snapshotspecification import SIO_SnapshotSpecification 16 | from scaleiopy.api.scaleio.mapping.storage_pool import SIO_Storage_Pool 17 | from scaleiopy.api.scaleio.mapping.volume import SIO_Volume 18 | from scaleiopy.api.scaleio.mapping.vtree import SIO_Vtree 19 | 20 | #class ScaleIO_System(SIO_Generic_Object): 21 | class SIO_System(SIO_Generic_Object): 22 | 23 | """ Represents one ScaleIO cluster/installation as a class object - Owns other classes that represents differenct ScaleIO components """ 24 | 25 | def __init__(self, 26 | id=None, 27 | name=None, 28 | systemVersionName=None, 29 | primaryMdmActorIpList = None, #List 30 | primaryMdmActorPort = None, 31 | secondaryMdmActorIpList = None, #List 32 | secondaryMdmActorPort = None, 33 | tiebreakerMdmIpList = None, #List 34 | tiebreakerMdmPort = None, # This one is defined in ScaleIO 1.30 API, but seem not present in 1.31?? 35 | tiebreakerMdmActorPort = None, 36 | mdmMode = None, #Single or Cluster 37 | mdmClusterState = None, # NotClustered or ClusteredNormal or ClusteredDegraded or ClusteredTiebreakerDown or ClusteredDegradedTiebreakerDown 38 | mdmManagementIpList = None, # List 39 | mdmManagementPort = None, 40 | capacityAlertHighThresholdPercent = None, 41 | capacityAlertCriticalThresholdPercent = None, 42 | installId = None, 43 | swid = None, # This one seem not to return anything. Its define din 1.30. What about 1.31???? 44 | daysInstalled = None, 45 | maxCapacityInGb = None, 46 | capacityTimeLeftInDays = None, 47 | enterpriseFeaturesEnabled = None, 48 | defaultIsVolumeObfuscated = None, 49 | isInitialLicense = None, 50 | restrictedSdcModeEnabled = None, 51 | remoteReadOnlyLimitState = None, 52 | links = None 53 | 54 | ): 55 | self.id=id 56 | self.name=None 57 | self.system_version_name = systemVersionName 58 | self.primary_mdm_actor_ip_list = [] 59 | for ip in primaryMdmActorIpList: 60 | self.primary_mdm_actor_ip_list.append(ip) 61 | self.primary_mdm_actor_port = primaryMdmActorPort 62 | self.secondary_mdm_actor_ip_list = secondaryMdmActorIpList 63 | self.secondary_mdm_actor_port = secondaryMdmActorPort 64 | self.tiebreaker_mdm_ip_list = tiebreakerMdmIpList 65 | self.tiebreaker_mdm_port = tiebreakerMdmPort 66 | self.tiebreaker_mdm_actor_port = tiebreakerMdmPort 67 | self.mdm_mode = mdmMode 68 | self.mdm_cluster_state = mdmClusterState 69 | self.mdm_management_ip_list = mdmManagementIpList 70 | self.mdm_management_port = mdmManagementPort 71 | self.capacity_alert_high_threshold_percent = capacityAlertHighThresholdPercent 72 | self.capacity_alert_critical_threshold_percent = capacityAlertCriticalThresholdPercent 73 | self.install_id = installId 74 | self.swid = swid 75 | self.days_installed = daysInstalled 76 | self.max_capacity_in_gb = maxCapacityInGb 77 | self.capacity_time_left_in_days = capacityTimeLeftInDays 78 | self.enterprise_features_enabled = enterpriseFeaturesEnabled 79 | self.default_is_volume_obfuscated = defaultIsVolumeObfuscated 80 | self.is_initial_license = isInitialLicense 81 | self.restricted_sdc_mode_enabled = restrictedSdcModeEnabled 82 | self.remote_readonly_limit_state = remoteReadOnlyLimitState 83 | self.links = [] 84 | for link in links: 85 | self.links.append(SIO_Link(link['href'],link['rel'])) 86 | 87 | 88 | @staticmethod 89 | def from_dict(dict): 90 | """ 91 | A convenience method that directly creates a new instance from a passed dictionary (that probably came from a 92 | JSON response from the server. 93 | """ 94 | return SIO_System(**dict) 95 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/cluster/sdc.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | # None 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | # None 9 | 10 | 11 | class Sdc(object): 12 | 13 | def __init__(self, connection): 14 | """ 15 | Initialize a new instance 16 | """ 17 | self.conn = connection 18 | 19 | @property 20 | def get(self): 21 | """ 22 | Returns a `list` of all the `System` objects to the cluster. Updates every time - no caching. 23 | :return: a `list` of all the `System` objects known to the cluster. 24 | :rtype: list 25 | """ 26 | self.conn.connection._check_login() 27 | response = self.conn.connection._do_get("{}/{}".format(self.conn.connection._api_url, "types/System/instances")).json() 28 | all_system_objects = [] 29 | for system_object in response: 30 | all_system_objects.append(self.conn.System.from_dict(system_object)) 31 | return all_system_objects 32 | 33 | def set_sdc_name(self, name, sdcObj): 34 | """ 35 | Set name for SDC 36 | :param name: Name of SDC 37 | :param sdcObj: ScaleIO SDC object 38 | :return: POST request response 39 | :rtype: Requests POST response object 40 | """ 41 | # TODO: 42 | # Check if object parameters are the correct ones, otherwise throw error 43 | self.conn.connection._check_login() 44 | sdcNameDict = {'sdcName': name} 45 | response = self.conn.connection._do_post("{}/{}{}/{}".format(self.conn.connection._api_url, "instances/Sdc::", sdcObj.id, 'action/setSdcName'), json=sdcNameDict) 46 | return response 47 | 48 | 49 | def get_sdc_by_name(self, name): 50 | """ 51 | Get ScaleIO SDC object by its name 52 | :param name: Name of SDC 53 | :return: ScaleIO SDC object 54 | :raise KeyError: No SDC with specified name found 55 | :rtype: SDC object 56 | """ 57 | for sdc in self.sdc: 58 | if sdc.name == name: 59 | return sdc 60 | raise KeyError("SDC of that name not found") 61 | 62 | def get_sdc_by_id(self, id): 63 | """ 64 | Get ScaleIO SDC object by its id 65 | :param name: id of SDC 66 | :return: ScaleIO SDC object 67 | :raise KeyError: No SDC with specified id found 68 | :rtype: SDC object 69 | """ 70 | for sdc in self.sdc: 71 | if sdc.id == id: 72 | return sdc 73 | raise KeyError("SDC with that ID not found") 74 | 75 | def get_sdc_by_guid(self, guid): 76 | """ 77 | Get ScaleIO SDC object by its id 78 | :param name: guid of SDC 79 | :return: ScaleIO SDC object 80 | :raise KeyError: No SDC with specified id found 81 | :rtype: SDC object 82 | """ 83 | for sdc in self.sdc: 84 | if sdc.guid == guid: 85 | return sdc 86 | raise KeyError("SDC with that GUID not found") 87 | 88 | def get_sdc_by_ip(self, ip): 89 | """ 90 | Get ScaleIO SDC object by its ip 91 | :param name: IP address of SDC 92 | :return: ScaleIO SDC object 93 | :raise KeyError: No SDC with specified IP found 94 | :rtype: SDC object 95 | """ 96 | if self.conn.is_ip_addr(ip): 97 | for sdc in self.sdc: 98 | if sdc.sdcIp == ip: 99 | return sdc 100 | raise KeyError("SDS of that name not found") 101 | else: 102 | raise ValueError("Malformed IP address - get_sdc_by_ip()") 103 | 104 | def unregisterSdc(self, sdcObj): 105 | """ 106 | Unregister SDC from MDM/SIO Cluster 107 | :param sdcObj: ScaleIO SDC object 108 | :return: POST request response 109 | :rtype: Requests POST response object 110 | """ 111 | # TODO: 112 | # Add code that unmap volume if mapped 113 | self.conn.connection._check_login() 114 | response = self.conn.connection._do_post("{}/{}{}/{}".format(self.conn.connection._api_url, "instances/Sdc::", sdcObj.id, 'action/removeSdc')) 115 | return response 116 | 117 | def registerSdc(self, sdcObj, **kwargs): 118 | # Register existing SDS running SDS binary (need to be installed manually but not added to MDM) 119 | # 120 | self.conn.connection._check_login() 121 | 122 | response = self.conn.connection._do_post("{}/{}".format(self.conn.connection._api_url, "types/Sdc/instances"), json=sdcObj.__to_dict__()) 123 | return response 124 | 125 | 126 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/cluster/sds.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | # None 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | # None 9 | from scaleiopy.api.scaleio.mapping.sds import SIO_SDS 10 | 11 | class Sds(object): 12 | 13 | def __init__(self, connection): 14 | """ 15 | Initialize a new instance 16 | """ 17 | self.conn = connection 18 | 19 | @property 20 | def get(self): 21 | """ 22 | Returns a `list` of all the `System` objects to the cluster. Updates every time - no caching. 23 | :return: a `list` of all the `System` objects known to the cluster. 24 | :rtype: list 25 | """ 26 | self.conn.connection._check_login() 27 | response = self.conn.connection._do_get("{}/{}".format(self.conn.connection._api_url, "types/System/instances")).json() 28 | all_system_objects = [] 29 | for system_object in response: 30 | all_system_objects.append(self.conn.System.from_dict(system_object)) 31 | return all_system_objects 32 | 33 | def set_sds_name(self, name, sdsObj): 34 | """ 35 | Set name for SDS 36 | :param name: Name of SDS 37 | :param sdsObj: ScaleIO SDS object 38 | :return: POST request response 39 | :rtype: Requests POST response object 40 | """ 41 | # TODO: 42 | # Check if object parameters are the correct type, otherwise throw error 43 | # UNSURE IF THIS IS CORRECT WAY TO SET SDS NAME 44 | self.conn.connection._check_login() 45 | sdsNameDict = {'sdsName': name} 46 | response = self.conn.connection._do_post("{}/{}{}/{}".format(self.conn.connection._api_url, "instances/Sds::", sdcObj.id, 'action/setSdsName'), json=sdsNameDict) 47 | return response 48 | 49 | 50 | def unregisterSds(self, sdsObj): 51 | """ 52 | Unregister SDS from MDM/SIO Cluster 53 | :param sdsObj: ScaleIO SDS objecty 54 | :return: POST request response 55 | :rtype: Requests POST response object 56 | """ 57 | self.conn.connection._check_login() 58 | response = self.conn.connection._do_post("{}/{}{}/{}".format(self.conn.connection._api_url, "instances/Sds::", sdsObj.id, 'action/removeSds')) 59 | return response 60 | 61 | # /api/types/Sds/instance s 62 | def registerSds(self, sdsObj, **kwargs): 63 | """ 64 | Register SDS with MDM/SIO Cluster 65 | :param sdsObj: ScaleIO SDS object 66 | :return: POST request response 67 | :rtype: Requests POST response object 68 | """ 69 | # Register existing SDS running SDS binary (need to be installed manually but not added to MDM) 70 | # 71 | self.conn.connection._check_login() 72 | 73 | response = self.conn.connection._do_post("{}/{}".format(self.conn.connection._api_url, "types/Sds/instances"), json=sdsObj.__to_dict__()) 74 | return response 75 | 76 | def get_sds_in_faultset(self, faultSetObj): 77 | """ 78 | Get list of SDS objects attached to a specific ScaleIO Faultset 79 | :param faultSetObj: ScaleIO Faultset object 80 | :rtype: list of SDS in specified Faultset 81 | """ 82 | self.conn.connection._check_login() 83 | response = self.conn.connection._do_get("{}/{}{}/{}".format(self.conn.connection._api_url, 'types/FaultSet::', faultSetObj.id, 'relationships/Sds')).json() 84 | all_sds = [] 85 | for sds in response: 86 | all_sds.append( 87 | SIO_SDS.from_dict(sds) 88 | ) 89 | return all_sds 90 | 91 | def get_sds_by_name(self,name): 92 | """ 93 | Get ScaleIO SDS object by its name 94 | :param name: Name of SDS 95 | :return: ScaleIO SDS object 96 | :raise KeyError: No SDS with specified name found 97 | :rtype: SDS object 98 | """ 99 | for sds in self.sds: 100 | if sds.name == name: 101 | return sds 102 | raise KeyError("SDS of that name not found") 103 | 104 | def get_sds_by_ip(self,ip): 105 | """ 106 | Get ScaleIO SDS object by its ip address 107 | :param name: IP address of SDS 108 | :return: ScaleIO SDS object 109 | :raise KeyError: No SDS with specified ip found 110 | :rtype: SDS object 111 | """ 112 | if self.conn.is_ip_addr(ip): 113 | for sds in self.sds: 114 | for sdsIp in sds.ipList: 115 | if sdsIp == ip: 116 | return sds 117 | raise KeyError("SDS of that name not found") 118 | else: 119 | raise ValueError("Malformed IP address - get_sds_by_ip()") 120 | 121 | def get_sds_by_id(self,id): 122 | """ 123 | Get ScaleIO SDS object by its id 124 | :param name: ID of SDS 125 | :return: ScaleIO SDS object 126 | :raise KeyError: No SDS with specified id found 127 | :rtype: SDS object 128 | """ 129 | for sds in self.sds: 130 | if sds.id == id: 131 | return sds 132 | raise KeyError("SDS with that ID not found") -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScaleIO-py 2 | 3 | #### A Python module for interacting with the EMC ScaleIO 1.3+ REST API. 4 | 5 | Authors: Magnus Nilsson & Matt Cowger 6 | 7 | Requirements: 8 | 9 | * Python 2.7+ 10 | * [Requests](http://docs.python-requests.org/en/latest/) 11 | * [Requests-Toolbelt](https://github.com/sigmavirus24/requests-toolbelt) 12 | * ScaleIO 1.3 or 1.31 installation with REST API Gateway configured (note, the [Vagrantfile](https://github.com/virtualswede/vagrant-scaleio) from @virtualswede works fine to deploy ScaleIO with for development and testing) 13 | 14 | ## Module status 15 | Goal is to resemble the ScaleIO API (not in detail) in a Pythonic way. Atm ScaleIO-py is in early beta stage and focus will be on getting basic features become stable (especially the to/from object mapping) before adding fancy functionality. 16 | 17 | ### Update 2015-06-08: Now compatible with v1.31 and v1.32 18 | 19 | 20 | ## Installation 21 | * Use "pip install ScaleIO-py" to install v0.3beta3 which is the latest released version. Master tree is going through lots of changes atm and is not stable. 22 | 23 | For bleeding edge users: 24 | * git clone https://github.com/swevm/scaleio-py.git 25 | * cd scaleio-py 26 | * python setup.py install 27 | 28 | 29 | ## Supported CRUD functionality 30 | ### Retrieve methods 31 | * Get list of SDS objects 32 | * Get list of SDC objects 33 | * Get list of Protection Domain objects 34 | * Get list of Volume objects 35 | * Get lsit of Fault Set objects 36 | * Get list of Storage Pool objects 37 | * Get list of System objects 38 | * Get volume by id or name 39 | * Get SDC by id, name or ip 40 | * Get SDS by id, name or ip 41 | * Get Storage Pool by id or name 42 | * Get Protection Domain by id or name 43 | * Get Volume by id or name 44 | * Get list of SDC(s) mapped to Volume by volumeObject 45 | * Get statistics (Thanks Kevin) 46 | 47 | ### Create 48 | * Create Volume by Protection Domain name [rename??] (Take PD object not PD name as argument) 49 | * Map Volume to SDC 50 | * Map Volume to all SDCs 51 | * Unmap Volume from SDC 52 | * Install new ScaleIO cluster (binaries and basic configuration using IM API) 53 | * Create Faultset 54 | * Create Volume snapshot 55 | 56 | ### Delete 57 | * Delete Volume 58 | * Delete Volume snapshot (by Volume Object) [Need testing] 59 | * Delete SDC from cluster (same as unregister SDC from cluster) [remove one of them] 60 | * Create Volume snapshot byt System id [Need testing] 61 | 62 | ### Update 63 | * Set SDC name 64 | * Upload binaries to be installed by IM 65 | 66 | ## Code examples 67 | Have a look in examples directory for complete code examples. 68 | 69 | ### Connect to ScaleIO API 70 | ``` 71 | from scaleiopy.scaleio import ScaleIO 72 | # Logging level can be change by adjusting last parameter [DEBUG, FATAL, ERROR, CRITICAL, WARNING, INFO]. If left out of class init DEBUG is assumed 73 | sio = ScaleIO("https://192.168.50.12/api","admin","Scaleio123",False, "ERROR) 74 | ``` 75 | 76 | #### Get a list of all attributes related to each SDC known by your ScaleIO cluster 77 | ``` 78 | #print all the known SDCs: 79 | pprint(sio.sdc) 80 | ``` 81 | 82 | #### Get list of attributes related to all SDS 83 | ``` 84 | #print all the known SDSs: 85 | pprint(sio.sds) 86 | ``` 87 | 88 | #### Get list of attributes related to all known Volumes 89 | ``` 90 | #print all the known Volumes: 91 | pprint(sio.volumes) 92 | ``` 93 | 94 | #### Get list of attributes related to each protection domain 95 | ``` 96 | #print all the known Protection Domains: 97 | pprint(sio.protection_domains) 98 | ``` 99 | 100 | #### Create a new Volume in Protection Domain 101 | ``` 102 | #Create a new Volume (of 8192Mb, smallest possible) 103 | sio.create_volume_by_pd_name('testvol001', 8192, sio.get_pd_by_name('default')) 104 | 105 | #Create Volume and Map to single SDC in one operation 106 | sio.create_volume_by_pd_name('testvol001', 8192, sio.get_pd_by_name('default'), mapToSdc=sio.get_sdc_by_id('ce4d7e2a00000001')) 107 | 108 | #Create Volume and Map to all SDC in one operation 109 | sio.create_volume_by_pd_name('testvol001', 8192, sio.get_pd_by_name('default'), mapAll=True) 110 | 111 | ``` 112 | 113 | #### Map existing Volume to a SDC by its ID 114 | ``` 115 | # method get_sdc_by_ip('ipaddr') if you want to map an Vol to SDC using its IP address 116 | sio.map_volume_to_sdc(sio.get_volume_by_name('testvol'), sio.get_sdc_by_id('ce4d7e2a00000001'), False) 117 | 118 | # Map Volume to all SDCs 119 | sio.map_volume_to_sdc(sio.get_volume_by_name('testvol'), mapAll=True) 120 | ``` 121 | 122 | #### Unmap volume from SDC 123 | ``` 124 | #Unmap Volume from SDC 125 | sio.unmap_volume_from_sdc(sio.get_volume_by_name('testvol'), sio.get_sdc_by_id('ce4d7e2a00000001')) 126 | ``` 127 | 128 | #### Delete a Volume from ScaleIO cluster 129 | ``` 130 | #Delete Volume 131 | sio.delete_volume(sio.get_volume_by_name('testvol'), 'ONLY_ME') 132 | ``` 133 | 134 | #### Create Snapshot of Volume 135 | ``` 136 | snapSpec = scaleio.SnapshotSpecification() 137 | snapSpec.addVolume(sio.get_volume_by_name('volume_name')) 138 | sio.create_snapshot(sio.get_system_id(), snapSpec) 139 | ``` 140 | 141 | #### Delete Snapshot 142 | ``` 143 | # Consistency group Id can be found by parsing result from get_volume_by_name(). 144 | sio.delete_snapshot(sio.get_system_id(), 'consistency_group_id') 145 | ``` 146 | 147 | ### Install ScaleIO using IM API 148 | ``` 149 | #Install cluster using 'private' IM API 150 | #Look in examples/install-cluster-mac.py for a complete example 151 | ``` 152 | 153 | #### Get statistics data from ScaleIO 154 | ``` 155 | #print all statistics data: 156 | pprint(sio.statistics) 157 | ``` 158 | -------------------------------------------------------------------------------- /doc/cache.json: -------------------------------------------------------------------------------- 1 | { 2 | "callHomeConfiguration": null, 3 | "installationId": "7ecf7b904f498606", 4 | "liaPassword": "Scaleio123", 5 | "licenseKey": null, 6 | "mdmIPs": [ 7 | "192.168.102.13", 8 | "192.168.102.12" 9 | ], 10 | "mdmPassword": "Scaleio123", 11 | "primaryMdm": { 12 | "managementIPs": [ 13 | "192.168.102.13", 14 | "192.168.102.12" 15 | ], 16 | "node": { 17 | "domain": null, 18 | "liaPassword": null, 19 | "nodeIPs": [ 20 | "192.168.102.12" 21 | ], 22 | "nodeName": null, 23 | "ostype": "unknown", 24 | "password": null, 25 | "userName": null 26 | }, 27 | "nodeInfo": null 28 | }, 29 | "remoteSyslogConfiguration": null, 30 | "sdcList": [ 31 | { 32 | "node": { 33 | "domain": null, 34 | "liaPassword": null, 35 | "nodeIPs": [ 36 | "192.168.102.13" 37 | ], 38 | "nodeName": null, 39 | "ostype": "unknown", 40 | "password": null, 41 | "userName": null 42 | }, 43 | "nodeInfo": null, 44 | "splitterRpaIp": null 45 | }, 46 | { 47 | "node": { 48 | "domain": null, 49 | "liaPassword": null, 50 | "nodeIPs": [ 51 | "192.168.102.12" 52 | ], 53 | "nodeName": null, 54 | "ostype": "unknown", 55 | "password": null, 56 | "userName": null 57 | }, 58 | "nodeInfo": null, 59 | "splitterRpaIp": null 60 | }, 61 | { 62 | "node": { 63 | "domain": null, 64 | "liaPassword": null, 65 | "nodeIPs": [ 66 | "192.168.102.11" 67 | ], 68 | "nodeName": null, 69 | "ostype": "unknown", 70 | "password": null, 71 | "userName": null 72 | }, 73 | "nodeInfo": null, 74 | "splitterRpaIp": null 75 | } 76 | ], 77 | "sdsList": [ 78 | { 79 | "allIPs": [ 80 | "192.168.102.11" 81 | ], 82 | "devices": [], 83 | "faultSet": null, 84 | "node": { 85 | "domain": null, 86 | "liaPassword": null, 87 | "nodeIPs": [ 88 | "192.168.102.11" 89 | ], 90 | "nodeName": null, 91 | "ostype": "unknown", 92 | "password": null, 93 | "userName": null 94 | }, 95 | "nodeInfo": null, 96 | "optimized": false, 97 | "port": 7072, 98 | "protectionDomain": "default", 99 | "sdcOnlyIPs": [], 100 | "sdsName": "SDS_[192.168.102.11]", 101 | "sdsOnlyIPs": [] 102 | }, 103 | { 104 | "allIPs": [ 105 | "192.168.102.12" 106 | ], 107 | "devices": [], 108 | "faultSet": null, 109 | "node": { 110 | "domain": null, 111 | "liaPassword": null, 112 | "nodeIPs": [ 113 | "192.168.102.12" 114 | ], 115 | "nodeName": null, 116 | "ostype": "unknown", 117 | "password": null, 118 | "userName": null 119 | }, 120 | "nodeInfo": null, 121 | "optimized": false, 122 | "port": 7072, 123 | "protectionDomain": "default", 124 | "sdcOnlyIPs": [], 125 | "sdsName": "SDS_[192.168.102.12]", 126 | "sdsOnlyIPs": [] 127 | }, 128 | { 129 | "allIPs": [ 130 | "192.168.102.13" 131 | ], 132 | "devices": [], 133 | "faultSet": null, 134 | "node": { 135 | "domain": null, 136 | "liaPassword": null, 137 | "nodeIPs": [ 138 | "192.168.102.13" 139 | ], 140 | "nodeName": null, 141 | "ostype": "unknown", 142 | "password": null, 143 | "userName": null 144 | }, 145 | "nodeInfo": null, 146 | "optimized": false, 147 | "port": 7072, 148 | "protectionDomain": "default", 149 | "sdcOnlyIPs": [], 150 | "sdsName": "SDS_[192.168.102.13]", 151 | "sdsOnlyIPs": [] 152 | } 153 | ], 154 | "secondaryMdm": { 155 | "managementIPs": [ 156 | "192.168.102.13", 157 | "192.168.102.12" 158 | ], 159 | "node": { 160 | "domain": null, 161 | "liaPassword": null, 162 | "nodeIPs": [ 163 | "192.168.102.13" 164 | ], 165 | "nodeName": null, 166 | "ostype": "unknown", 167 | "password": null, 168 | "userName": null 169 | }, 170 | "nodeInfo": null 171 | }, 172 | "tb": { 173 | "node": { 174 | "domain": null, 175 | "liaPassword": null, 176 | "nodeIPs": [ 177 | "192.168.102.11" 178 | ], 179 | "nodeName": null, 180 | "ostype": "unknown", 181 | "password": null, 182 | "userName": null 183 | }, 184 | "nodeInfo": null, 185 | "tbIPs": [ 186 | "192.168.102.11" 187 | ] 188 | } 189 | } -------------------------------------------------------------------------------- /doc/minimal-cluster_notworking.json: -------------------------------------------------------------------------------- 1 | { 2 | "callHomeConfiguration": null, 3 | "installationId": null, 4 | "liaPassword": "Scaleio123", 5 | "licenseKey": null, 6 | "mdmIPs": [ 7 | "192.168.102.13", 8 | "192.168.102.12" 9 | ], 10 | "mdmPassword": "Scaleio123", 11 | "primaryMdm": { 12 | "managementIPs": [ 13 | "192.168.102.13", 14 | "192.168.102.12" 15 | ], 16 | "node": { 17 | "domain": null, 18 | "liaPassword": null, 19 | "nodeIPs": [ 20 | "192.168.102.12" 21 | ], 22 | "nodeName": null, 23 | "ostype": "unknown", 24 | "password": null, 25 | "userName": null 26 | }, 27 | "nodeInfo": null 28 | }, 29 | "remoteSyslogConfiguration": null, 30 | "sdcList": [ 31 | { 32 | "node": { 33 | "domain": null, 34 | "liaPassword": null, 35 | "nodeIPs": [ 36 | "192.168.102.13" 37 | ], 38 | "nodeName": null, 39 | "ostype": "unknown", 40 | "password": null, 41 | "userName": null 42 | }, 43 | "nodeInfo": null, 44 | "splitterRpaIp": null 45 | }, 46 | { 47 | "node": { 48 | "domain": null, 49 | "liaPassword": null, 50 | "nodeIPs": [ 51 | "192.168.102.12" 52 | ], 53 | "nodeName": null, 54 | "ostype": "unknown", 55 | "password": null, 56 | "userName": null 57 | }, 58 | "nodeInfo": null, 59 | "splitterRpaIp": null 60 | }, 61 | { 62 | "node": { 63 | "domain": null, 64 | "liaPassword": null, 65 | "nodeIPs": [ 66 | "192.168.102.11" 67 | ], 68 | "nodeName": null, 69 | "ostype": "unknown", 70 | "password": null, 71 | "userName": null 72 | }, 73 | "nodeInfo": null, 74 | "splitterRpaIp": null 75 | } 76 | ], 77 | "sdsList": [ 78 | { 79 | "allIPs": [ 80 | "192.168.102.11" 81 | ], 82 | "devices": [], 83 | "faultSet": null, 84 | "node": { 85 | "domain": null, 86 | "liaPassword": null, 87 | "nodeIPs": [ 88 | "192.168.102.11" 89 | ], 90 | "nodeName": null, 91 | "ostype": "unknown", 92 | "password": null, 93 | "userName": null 94 | }, 95 | "nodeInfo": null, 96 | "optimized": false, 97 | "port": 7072, 98 | "protectionDomain": "default", 99 | "sdcOnlyIPs": [], 100 | "sdsName": "SDS_[192.168.102.11]", 101 | "sdsOnlyIPs": [] 102 | }, 103 | { 104 | "allIPs": [ 105 | "192.168.102.12" 106 | ], 107 | "devices": [], 108 | "faultSet": null, 109 | "node": { 110 | "domain": null, 111 | "liaPassword": null, 112 | "nodeIPs": [ 113 | "192.168.102.12" 114 | ], 115 | "nodeName": null, 116 | "ostype": "unknown", 117 | "password": null, 118 | "userName": null 119 | }, 120 | "nodeInfo": null, 121 | "optimized": false, 122 | "port": 7072, 123 | "protectionDomain": "default", 124 | "sdcOnlyIPs": [], 125 | "sdsName": "SDS_[192.168.102.12]", 126 | "sdsOnlyIPs": [] 127 | }, 128 | { 129 | "allIPs": [ 130 | "192.168.102.13" 131 | ], 132 | "devices": [], 133 | "faultSet": null, 134 | "node": { 135 | "domain": null, 136 | "liaPassword": null, 137 | "nodeIPs": [ 138 | "192.168.102.13" 139 | ], 140 | "nodeName": null, 141 | "ostype": "unknown", 142 | "password": null, 143 | "userName": null 144 | }, 145 | "nodeInfo": null, 146 | "optimized": false, 147 | "port": 7072, 148 | "protectionDomain": "default", 149 | "sdcOnlyIPs": [], 150 | "sdsName": "SDS_[192.168.102.13]", 151 | "sdsOnlyIPs": [] 152 | } 153 | ], 154 | "secondaryMdm": { 155 | "managementIPs": [ 156 | "192.168.102.13", 157 | "192.168.102.12" 158 | ], 159 | "node": { 160 | "domain": null, 161 | "liaPassword": null, 162 | "nodeIPs": [ 163 | "192.168.102.13" 164 | ], 165 | "nodeName": null, 166 | "ostype": "unknown", 167 | "password": null, 168 | "userName": null 169 | }, 170 | "nodeInfo": null 171 | }, 172 | "tb": { 173 | "node": { 174 | "domain": null, 175 | "liaPassword": null, 176 | "nodeIPs": [ 177 | "192.168.102.11" 178 | ], 179 | "nodeName": null, 180 | "ostype": "unknown", 181 | "password": null, 182 | "userName": null 183 | }, 184 | "nodeInfo": null, 185 | "tbIPs": [ 186 | "192.168.102.11" 187 | ] 188 | } 189 | } -------------------------------------------------------------------------------- /examples/install-cluster.py: -------------------------------------------------------------------------------- 1 | from scaleiopy import im 2 | from scaleiopy import scaleioobject as sioobj 3 | #from scaleio import installerfsm as instfsm 4 | import time 5 | import json 6 | from pprint import pprint 7 | 8 | ########################### 9 | # Create a ScaleIO System # 10 | ########################### 11 | # 12 | # Prereq: 3 x CentOS 6.5 or RHEL 6.5 13 | # 14 | # Flow: 15 | # Create Nodes 16 | # Create basic info. mdmPass, liaPass and some others 17 | # Construct MDM and TB and basic info 18 | # Create list of SDS 19 | # Create list of SDC 20 | 21 | 22 | ################### 23 | # Construct nodes # 24 | ################### 25 | nodeUsername = 'root' # Username for ScaleIO Node OS (these machines need to be pre installed) 26 | nodePassword = 'vagrant' # Password for ScaleIO Node OS 27 | node1 = sioobj.ScaleIO_Node_Object(None, None, ['192.168.102.11'], None, 'linux', nodePassword, nodeUsername) 28 | node2 = sioobj.ScaleIO_Node_Object(None, None, ['192.168.102.12'], None, 'linux', nodePassword, nodeUsername) 29 | node3 = sioobj.ScaleIO_Node_Object(None, None, ['192.168.102.13'], None, 'linux', nodePassword, nodeUsername) 30 | 31 | ########################################## 32 | # Construct basic info for System_Object # 33 | ########################################## 34 | mdmIPs = ['192.168.102.12','192.168.102.13'] 35 | sdcList = [] 36 | sdsList = [] 37 | mdmPassword = 'Scaleio123' 38 | liaPassword = 'Scaleio123' 39 | licenseKey = None 40 | installationId = None 41 | 42 | ######################################## 43 | # Create MDMs and TB for System_Object # 44 | ######################################## 45 | primaryMdm = sioobj.Mdm_Object(json.loads(node2.to_JSON()), None, None, node2.nodeIPs) 46 | secondaryMdm = sioobj.Mdm_Object(json.loads(node3.to_JSON()), None, None, node3.nodeIPs) 47 | tb = sioobj.Tb_Object(json.loads(node1.to_JSON()), None, node1.nodeIPs) 48 | callHomeConfiguration = None # {'callHomeConfiguration':'None'} 49 | remoteSyslogConfiguration = None # {'remoteSysogConfiguration':'None'} 50 | 51 | ################################################################ 52 | #Create SDS objects - To be added to SDS list in System_Object # 53 | ################################################################ 54 | # Adjust addDevice() to match local block device you have in your node 55 | # Define SDS that belong to a FaultSet - Not tested! 56 | #sds1 = sioobj.Sds_Object(json.loads(node1.to_JSON()), None, 'SDS_' + str(node1.nodeIPs[0]), 'default', 'faultset1', node1.nodeIPs, None, None, None, False, '7072') 57 | 58 | sds1 = sioobj.Sds_Object(json.loads(node1.to_JSON()), None, 'SDS_' + str(node1.nodeIPs[0]), 'default', None, node1.nodeIPs, None, None, None, False, '7072') 59 | sds1.addDevice("/home/vagrant/scaleio1", None, None) 60 | sds2 = sioobj.Sds_Object(json.loads(node2.to_JSON()), None, 'SDS_' + str(node2.nodeIPs[0]), 'default', None, node2.nodeIPs, None, None, None, False, '7072') 61 | sds2.addDevice("/home/vagrant/scaleio1", None, None) 62 | sds3 = sioobj.Sds_Object(json.loads(node3.to_JSON()), None, 'SDS_' + str(node3.nodeIPs[0]), 'default', None, node3.nodeIPs, None, None, None, False, '7072') 63 | sds3.addDevice("/home/vagrant/scaleio1", None, None) 64 | sdsList.append(json.loads(sds1.to_JSON())) 65 | sdsList.append(json.loads(sds2.to_JSON())) 66 | sdsList.append(json.loads(sds3.to_JSON())) 67 | 68 | ############################################################# 69 | # Create SDC objects - To be added as list to System_Object # 70 | ############################################################# 71 | # Decide which nodes in your cluster should become a SDC 72 | """ 73 | node=None, 74 | nodeInfo=None, 75 | splitterRpaIp=None 76 | """ 77 | sdc1 = sioobj.Sdc_Object(json.loads(node1.to_JSON()), None, None) 78 | sdc2 = sioobj.Sdc_Object(json.loads(node2.to_JSON()), None, None) 79 | sdc3 = sioobj.Sdc_Object(json.loads(node3.to_JSON()), None, None) 80 | 81 | sdcList.append(json.loads(sdc1.to_JSON())) 82 | sdcList.append(json.loads(sdc2.to_JSON())) 83 | sdcList.append(json.loads(sdc3.to_JSON())) 84 | 85 | ###################################################### 86 | # Construct a complete ScaleIO cluster configuration # 87 | ###################################################### 88 | sioobj = sioobj.ScaleIO_System_Object(installationId, 89 | mdmIPs, 90 | mdmPassword, 91 | liaPassword, 92 | licenseKey, 93 | json.loads(primaryMdm.to_JSON()), 94 | json.loads(secondaryMdm.to_JSON()), 95 | json.loads(tb.to_JSON()), 96 | sdsList, 97 | sdcList, 98 | callHomeConfiguration, 99 | remoteSyslogConfiguration 100 | ) 101 | 102 | # Export sioobj to JSON (should upload clean in IM) 103 | 104 | 105 | ########################################################################### 106 | # Push System_Object JSON - To be used by IM to install ScaleIO on nodes # 107 | ########################################################################### 108 | 109 | 110 | 111 | ####################### 112 | # LOGIN TO SCALEIO IM # 113 | ####################### 114 | imconn = im.Im("https://192.168.102.12","admin","Scaleio123",verify_ssl=False) # "Password1!") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 115 | imconn._login() 116 | 117 | ### UPLOAD RPM PACKAGES TO BE DEPLOYED BY IM ### 118 | imconn.uploadPackages('/Users/swevm/Downloads/RHEL6_1277/') # Adjust to your needs. All RPMs for RHEL6 should exist in this dir except for GUI and Gateway 119 | 120 | #################### 121 | # INSTALLER STAGES # 122 | #################### 123 | 124 | # Initialize Installer 125 | im_installer = im.InstallerFSM(imconn, True) 126 | 127 | time.sleep(10) # Wait a few seconds before continuing - Not necessary 128 | 129 | print "Create minimal cluster as Python objects" 130 | imconn.push_cluster_configuration(sioobj.to_JSON()) 131 | 132 | print "Start Install process!!!" 133 | im_installer.Execute() # Start install process 134 | -------------------------------------------------------------------------------- /examples/install-cluster-faultsets.py: -------------------------------------------------------------------------------- 1 | from scaleiopy import im 2 | from scaleiopy import scaleioobject as sioobj 3 | #from scaleio import installerfsm as instfsm 4 | import time 5 | import json 6 | from pprint import pprint 7 | 8 | ########################### 9 | # Create a ScaleIO System # 10 | ########################### 11 | # 12 | # Prereq: 3 x CentOS 6.5 or RHEL 6.5 13 | # 14 | # Flow: 15 | # Create Nodes 16 | # Create basic info. mdmPass, liaPass and some others 17 | # Construct MDM and TB and basic info 18 | # Create list of SDS 19 | # Create list of SDC 20 | 21 | 22 | ################### 23 | # Construct nodes # 24 | ################### 25 | nodeUsername = 'root' # Username for ScaleIO Node OS (these machines need to be pre installed) 26 | nodePassword = 'vagrant' # Password for ScaleIO Node OS 27 | node1 = sioobj.ScaleIO_Node_Object(None, None, ['192.168.102.11'], None, 'linux', nodePassword, nodeUsername) 28 | node2 = sioobj.ScaleIO_Node_Object(None, None, ['192.168.102.12'], None, 'linux', nodePassword, nodeUsername) 29 | node3 = sioobj.ScaleIO_Node_Object(None, None, ['192.168.102.13'], None, 'linux', nodePassword, nodeUsername) 30 | 31 | ########################################## 32 | # Construct basic info for System_Object # 33 | ########################################## 34 | mdmIPs = ['192.168.102.12','192.168.102.13'] 35 | sdcList = [] 36 | sdsList = [] 37 | mdmPassword = 'Scaleio123' 38 | liaPassword = 'Scaleio123' 39 | licenseKey = None 40 | installationId = None 41 | 42 | ######################################## 43 | # Create MDMs and TB for System_Object # 44 | ######################################## 45 | primaryMdm = sioobj.Mdm_Object(json.loads(node2.to_JSON()), None, None, node2.nodeIPs) 46 | secondaryMdm = sioobj.Mdm_Object(json.loads(node3.to_JSON()), None, None, node3.nodeIPs) 47 | tb = sioobj.Tb_Object(json.loads(node1.to_JSON()), None, node1.nodeIPs) 48 | callHomeConfiguration = None # {'callHomeConfiguration':'None'} 49 | remoteSyslogConfiguration = None # {'remoteSysogConfiguration':'None'} 50 | 51 | ################################################################ 52 | #Create SDS objects - To be added to SDS list in System_Object # 53 | ################################################################ 54 | # Adjust addDevice() to match local block device you have in your node 55 | # Define SDS that belong to a FaultSet - Not tested! 56 | #sds1 = sioobj.Sds_Object(json.loads(node1.to_JSON()), None, 'SDS_' + str(node1.nodeIPs[0]), 'default', 'faultset1', node1.nodeIPs, None, None, None, False, '7072') 57 | 58 | sds1 = sioobj.Sds_Object(json.loads(node1.to_JSON()), None, 'SDS_' + str(node1.nodeIPs[0]), 'default', 'faultset1', node1.nodeIPs, None, None, None, False, '7072') 59 | sds1.addDevice("/home/vagrant/scaleio1", None, None) 60 | sds2 = sioobj.Sds_Object(json.loads(node2.to_JSON()), None, 'SDS_' + str(node2.nodeIPs[0]), 'default', 'faultset2', node2.nodeIPs, None, None, None, False, '7072') 61 | sds2.addDevice("/home/vagrant/scaleio1", None, None) 62 | sds3 = sioobj.Sds_Object(json.loads(node3.to_JSON()), None, 'SDS_' + str(node3.nodeIPs[0]), 'default', 'faultset3', node3.nodeIPs, None, None, None, False, '7072') 63 | sds3.addDevice("/home/vagrant/scaleio1", None, None) 64 | sdsList.append(json.loads(sds1.to_JSON())) 65 | sdsList.append(json.loads(sds2.to_JSON())) 66 | sdsList.append(json.loads(sds3.to_JSON())) 67 | 68 | ############################################################# 69 | # Create SDC objects - To be added as list to System_Object # 70 | ############################################################# 71 | # Decide which nodes in your cluster should become a SDC 72 | """ 73 | node=None, 74 | nodeInfo=None, 75 | splitterRpaIp=None 76 | """ 77 | sdc1 = sioobj.Sdc_Object(json.loads(node1.to_JSON()), None, None) 78 | sdc2 = sioobj.Sdc_Object(json.loads(node2.to_JSON()), None, None) 79 | sdc3 = sioobj.Sdc_Object(json.loads(node3.to_JSON()), None, None) 80 | 81 | sdcList.append(json.loads(sdc1.to_JSON())) 82 | sdcList.append(json.loads(sdc2.to_JSON())) 83 | sdcList.append(json.loads(sdc3.to_JSON())) 84 | 85 | ###################################################### 86 | # Construct a complete ScaleIO cluster configuration # 87 | ###################################################### 88 | sioobj = sioobj.ScaleIO_System_Object(installationId, 89 | mdmIPs, 90 | mdmPassword, 91 | liaPassword, 92 | licenseKey, 93 | json.loads(primaryMdm.to_JSON()), 94 | json.loads(secondaryMdm.to_JSON()), 95 | json.loads(tb.to_JSON()), 96 | sdsList, 97 | sdcList, 98 | callHomeConfiguration, 99 | remoteSyslogConfiguration 100 | ) 101 | 102 | # Export sioobj to JSON (should upload clean in IM) 103 | 104 | 105 | ########################################################################### 106 | # Push System_Object JSON - To be used by IM to install ScaleIO on nodes # 107 | ########################################################################### 108 | 109 | 110 | 111 | ####################### 112 | # LOGIN TO SCALEIO IM # 113 | ####################### 114 | imconn = im.Im("https://192.168.102.12","admin","Scaleio123",verify_ssl=False) # "Password1!") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 115 | imconn._login() 116 | 117 | ### UPLOAD RPM PACKAGES TO BE DEPLOYED BY IM ### 118 | imconn.uploadPackages('/Users/swevm/Downloads/RHEL6_1277/') # Adjust to your needs. All RPMs for RHEL6 should exist in this dir except for GUI and Gateway 119 | 120 | #################### 121 | # INSTALLER STAGES # 122 | #################### 123 | 124 | # Initialize Installer 125 | im_installer = im.InstallerFSM(imconn, True) 126 | 127 | time.sleep(10) # Wait a few seconds before continuing - Not necessary 128 | 129 | print "Create minimal cluster as Python objects" 130 | imconn.push_cluster_configuration(sioobj.to_JSON()) 131 | 132 | print "Start Install process!!!" 133 | im_installer.Execute() # Start install process 134 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/common/connection.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | #import logging 3 | from requests.auth import HTTPBasicAuth 4 | from requests_toolbelt import SSLAdapter 5 | from requests.adapters import HTTPAdapter 6 | from requests.packages.urllib3.poolmanager import PoolManager 7 | import requests 8 | import ssl 9 | import logging 10 | 11 | # Third party imports 12 | # None 13 | 14 | # Project level imports 15 | # None 16 | 17 | #log = logging.getLogger(__name__) 18 | 19 | # How to remove this one. Let Requests inherit from this class??? 20 | class TLS1Adapter(HTTPAdapter): 21 | """ 22 | A custom HTTP adapter we mount to the session to force the use of TLSv1, which is the only thing supported by 23 | the gateway. Python 2.x tries to establish SSLv2/3 first which failed. 24 | """ 25 | def init_poolmanager(self, connections, maxsize, block=False): 26 | self.poolmanager = PoolManager(num_pools=connections, 27 | maxsize=maxsize, 28 | block=block, 29 | ssl_version=ssl.PROTOCOL_TLSv1) 30 | 31 | 32 | class Connection(object): 33 | 34 | def __init__(self, connection, api_url, username, password, verify_ssl=False, debugLevel=None): 35 | """ 36 | Initialize a new instance 37 | """ 38 | self.conn = connection 39 | 40 | self._username = username 41 | self._password = password 42 | self._api_url = api_url 43 | self._session = requests.Session() 44 | self._session.headers.update({'Accept': 'application/json', 'Version': '1.0'}) # Accept only json 45 | self._session.mount('https://', TLS1Adapter()) 46 | self._verify_ssl = verify_ssl 47 | self._logged_in = False 48 | self._api_version = None 49 | requests.packages.urllib3.disable_warnings() # Disable unverified connection warning. 50 | # logging.basicConfig(format='%(asctime)s: %(levelname)s %(module)s:%(funcName)s | %(message)s',level=self._get_log_level(debugLevel)) 51 | # self.logger = logging.getLogger(__name__) 52 | # self.logger.debug("Logger initialized!") 53 | self._check_login() # Login. Otherwise login is called upon first API operation 54 | 55 | 56 | def logout(self): 57 | pass 58 | 59 | def login(self): 60 | self.conn.logger.debug("Logging into " + "{}/{}".format(self._api_url, "login")) 61 | self.conn.logger.debug("With credentials " + "{}/{}".format(self._username, self._password)) 62 | login_response = self._session.get( 63 | "{}/{}".format(self._api_url,"login"), 64 | verify=self._verify_ssl, 65 | auth=HTTPBasicAuth(self._username, self._password) 66 | ).json() 67 | if type(login_response) is dict: 68 | # If we got here, something went wrong during login 69 | for key, value in login_response.iteritems(): 70 | if key == 'errorCode': 71 | self.conn.logger.error('Login error code: %s', login_response['message']) 72 | raise RuntimeError(login_response['message']) 73 | else: 74 | self._auth_token = login_response 75 | self.conn.logger.debug('Authentication token recieved: %s', self._auth_token) 76 | self._session.auth = HTTPBasicAuth('',self._auth_token) 77 | self._logged_in = True 78 | # Set _api_version_ to current version of connected API 79 | self._api_version = login_response = self._session.get( 80 | "{}/{}".format(self._api_url,"version"), 81 | verify=self._verify_ssl, 82 | auth=HTTPBasicAuth(self._username, self._password) 83 | ).json() # Do ScaleIO API really obey versioning???? 84 | 85 | def _check_login(self): 86 | if not self._logged_in: 87 | self.login() 88 | else: 89 | pass 90 | return None 91 | 92 | def get_api_version(self): 93 | self._check_login() 94 | # API version scheme: 95 | # 1.0 = < v1.32 96 | # 1.1 = v1.32 97 | # x.x = v2.0 98 | return self._api_version 99 | 100 | 101 | # Basic data handling transfer methods 102 | 103 | # FIX _do_get method, easier to have one place to do error handling than in all other methods that call _do_get() 104 | def _do_get(self, url, **kwargs): 105 | """ 106 | Convenient method for GET requests 107 | Returns http request status value from a POST request 108 | """ 109 | #TODO: 110 | # Add error handling. Check for HTTP status here would be much more conveinent than in each calling method 111 | scaleioapi_post_headers = {'Content-type':'application/json','Version':'1.0'} 112 | try: 113 | #response = self._session.get("{}/{}".format(self._api_url, uri)).json() 114 | response = self._session.get(url) 115 | if response.status_code == requests.codes.ok: 116 | self.conn.logger.debug('_do_get() - HTTP response OK, data: %s', response.text) 117 | return response 118 | else: 119 | self.conn.logger.error('_do_get() - HTTP response error: %s', response.status_code) 120 | self.conn.logger.error('_do_get() - HTTP response error, data: %s', response.text) 121 | raise RuntimeError("_do_get() - HTTP response error" + response.status_code) 122 | except Exception as e: 123 | self.conn.logger.error("_do_get() - Unhandled Error Occurred: %s" % str(e)) 124 | raise RuntimeError("_do_get() - Communication error with ScaleIO gateway") 125 | return response 126 | 127 | def _do_post(self, url, **kwargs): 128 | """ 129 | Convenient method for POST requests 130 | Returns http request status value from a POST request 131 | """ 132 | #TODO: 133 | # Add error handling. Check for HTTP status here would be much more conveinent than in each calling method 134 | scaleioapi_post_headers = {'Content-type':'application/json','Version':'1.0'} 135 | try: 136 | response = self._session.post(url, headers=scaleioapi_post_headers, **kwargs) 137 | self.conn.logger.debug('_do_post() - HTTP response: %s', response.text) 138 | if response.status_code == requests.codes.ok: 139 | self.conn.logger.debug('_do_post() - HTTP response OK, data: %s', response.text) 140 | return response 141 | else: 142 | self.conn.logger.error('_do_post() - HTTP response error: %s', response.status_code) 143 | self.conn.logger.error('_do_post() - HTTP response error, data: %s', response.text) 144 | raise RuntimeError("_do_post() - HTTP response error" + response.status_code) 145 | except Exception as e: 146 | self.conn.logger.error("_do_post() - Unhandled Error Occurred: %s" % str(e)) 147 | raise RuntimeError("_do_post() - Communication error with ScaleIO gateway") 148 | return response 149 | 150 | 151 | def _do_put(self): 152 | pass 153 | 154 | def _do_delete(self): 155 | pass 156 | 157 | -------------------------------------------------------------------------------- /examples/install-cluster-syslog_v1_32.py: -------------------------------------------------------------------------------- 1 | from scaleiopy import im 2 | from scaleiopy import scaleioobject as sioobj 3 | #from scaleio import installerfsm as instfsm 4 | import time 5 | import json 6 | from pprint import pprint 7 | 8 | ########################### 9 | # Create a ScaleIO System # 10 | ########################### 11 | # 12 | # Prereq: 3 x CentOS 6.5 or RHEL 6.5 13 | # 14 | # Flow: 15 | # Create Nodes 16 | # Create basic info. mdmPass, liaPass and some others 17 | # Construct MDM and TB and basic info 18 | # Create list of SDS 19 | # Create list of SDC 20 | 21 | 22 | ################### 23 | # Construct nodes # 24 | ################### 25 | nodeUsername = 'root' # Username for ScaleIO Node OS (these machines need to be pre installed) 26 | nodePassword = 'vagrant' # Password for ScaleIO Node OS 27 | node1 = sioobj.ScaleIO_Node_Object(None, None, ['192.168.102.11'], None, 'linux', nodePassword, nodeUsername) 28 | node2 = sioobj.ScaleIO_Node_Object(None, None, ['192.168.102.12'], None, 'linux', nodePassword, nodeUsername) 29 | node3 = sioobj.ScaleIO_Node_Object(None, None, ['192.168.102.13'], None, 'linux', nodePassword, nodeUsername) 30 | 31 | ########################################## 32 | # Construct basic info for System_Object # 33 | ########################################## 34 | mdmIPs = ['192.168.102.12','192.168.102.13'] 35 | sdcList = [] 36 | sdsList = [] 37 | mdmPassword = 'Scaleio123' 38 | liaPassword = 'Scaleio123' 39 | licenseKey = None 40 | installationId = None 41 | 42 | # Remote logging parameters 43 | remoteSyslogServer = '192.168.102.1' # IP address to remote syslog server (uses TCP to connect) 44 | remoteSyslogTcpPort = '514' # Remote syslog server TCP port 45 | remoteSyslogFacility = 16 # Syslog Facility 46 | 47 | # Callhome parameters 48 | callhomeEmailFrom = 'scaleio@localhost' # If a from email box is needed, set it here 49 | callhomeMdmUsername = 'admin' # This is the MDM admin username to use to connect/poll status 50 | callhomeMdmPassword = mdmPassword # Same as MDM password defined above 51 | callhomeCustomerName = 'Customer' # Customer name 52 | callhomeHost = 'localhost' # IP or hostname to SMTP server 53 | callhomePort = '25' # TCP port of SMTP server 54 | callhomeTls = True # Encryption method. (True or False) 55 | callhomeSmtpUsername = 'root' # If a username is needed to connect to SMTP server, set it here 56 | callhomeSmtpPassword = 'password' 57 | callhomeAlertEmailTo = 'root@localhost' # Commaseparated list of email addresses as alert recipients 58 | callhomeSeverity = 'ERROR' # What severity level of events should trigger sending emails (investigate format) 59 | 60 | 61 | ######################################## 62 | # Create MDMs and TB for System_Object # 63 | ######################################## 64 | primaryMdm = sioobj.Mdm_Object(json.loads(node2.to_JSON()), None, None, node2.nodeIPs) 65 | secondaryMdm = sioobj.Mdm_Object(json.loads(node3.to_JSON()), None, None, node3.nodeIPs) 66 | tb = sioobj.Tb_Object(json.loads(node1.to_JSON()), None, node1.nodeIPs) 67 | callHomeConfiguration = sioobj.Call_Home_Configuration_Object(callhomeEmailFrom, 68 | callhomeMdmUsername, 69 | callhomeMdmPassword, 70 | callhomeCustomerName, 71 | callhomeHost, 72 | callhomePort, 73 | callhomeTls, 74 | callhomeSmtpUsername, 75 | callhomeAlertEmailTo, 76 | callhomeSeverity 77 | ) 78 | #callHomeConfiguration = None 79 | 80 | remoteSyslogConfiguration = sioobj.Remote_Syslog_Configuration_Object(remoteSyslogServer,remoteSyslogTcpPort, remoteSyslogFacility) 81 | #remoteSyslogConfiguration = None # Set to None if you dont need remote logging for SIO 82 | 83 | ################################################################ 84 | #Create SDS objects - To be added to SDS list in System_Object # 85 | ################################################################ 86 | # Adjust addDevice() to match local block device you have in your node 87 | # Define SDS that belong to a FaultSet - Not tested! 88 | #sds1 = sioobj.Sds_Object(json.loads(node1.to_JSON()), None, 'SDS_' + str(node1.nodeIPs[0]), 'default', 'faultset1', node1.nodeIPs, None, None, None, False, '7072') 89 | 90 | sds1 = sioobj.Sds_Object(json.loads(node1.to_JSON()), None, 'SDS_' + str(node1.nodeIPs[0]), 'default', None, node1.nodeIPs, None, None, None, False, '7072') 91 | sds1.addDevice("/dev/loop0", None, None) 92 | sds2 = sioobj.Sds_Object(json.loads(node2.to_JSON()), None, 'SDS_' + str(node2.nodeIPs[0]), 'default', None, node2.nodeIPs, None, None, None, False, '7072') 93 | sds2.addDevice("/dev/loop0", None, None) 94 | sds3 = sioobj.Sds_Object(json.loads(node3.to_JSON()), None, 'SDS_' + str(node3.nodeIPs[0]), 'default', None, node3.nodeIPs, None, None, None, False, '7072') 95 | sds3.addDevice("/dev/loop0", None, None) 96 | sdsList.append(json.loads(sds1.to_JSON())) 97 | sdsList.append(json.loads(sds2.to_JSON())) 98 | sdsList.append(json.loads(sds3.to_JSON())) 99 | 100 | ############################################################# 101 | # Create SDC objects - To be added as list to System_Object # 102 | ############################################################# 103 | # Decide which nodes in your cluster should become a SDC 104 | """ 105 | node=None, 106 | nodeInfo=None, 107 | splitterRpaIp=None 108 | """ 109 | sdc1 = sioobj.Sdc_Object(json.loads(node1.to_JSON()), None, None) 110 | sdc2 = sioobj.Sdc_Object(json.loads(node2.to_JSON()), None, None) 111 | sdc3 = sioobj.Sdc_Object(json.loads(node3.to_JSON()), None, None) 112 | 113 | sdcList.append(json.loads(sdc1.to_JSON())) 114 | sdcList.append(json.loads(sdc2.to_JSON())) 115 | sdcList.append(json.loads(sdc3.to_JSON())) 116 | 117 | ###################################################### 118 | # Construct a complete ScaleIO cluster configuration # 119 | ###################################################### 120 | sioobj = sioobj.ScaleIO_System_Object(installationId, 121 | mdmIPs, 122 | mdmPassword, 123 | liaPassword, 124 | licenseKey, 125 | json.loads(primaryMdm.to_JSON()), 126 | json.loads(secondaryMdm.to_JSON()), 127 | json.loads(tb.to_JSON()), 128 | sdsList, 129 | sdcList, 130 | callHomeConfiguration, 131 | remoteSyslogConfiguration 132 | ) 133 | 134 | # Export sioobj to JSON (should upload clean in IM)s 135 | 136 | 137 | ########################################################################### 138 | # Push System_Object JSON - To be used by IM to install ScaleIO on nodes # 139 | ########################################################################### 140 | 141 | 142 | 143 | ####################### 144 | # LOGIN TO SCALEIO IM # 145 | ####################### 146 | imconn = im.Im("https://192.168.102.12","admin","Scaleio123",verify_ssl=False) # "Password1!") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 147 | imconn._login() 148 | 149 | ### UPLOAD RPM PACKAGES TO BE DEPLOYED BY IM ### 150 | #imconn.uploadPackages('/Users/swevm/Downloads/RHEL6_1277/') # Adjust to your needs. All RPMs for RHEL6 should exist in this dir except for GUI and Gateway 151 | imconn.uploadPackages('/Users/swevm/Downloads/RHEL6_132_402_1/') # ScaleIO 1.32 FnF for RHEL6 (This is 1.32 GA build) 152 | 153 | #################### 154 | # INSTALLER STAGES # 155 | #################### 156 | 157 | # Initialize Installer 158 | im_installer = im.InstallerFSM(imconn, True) 159 | 160 | time.sleep(5) # Wait a few seconds before continuing - Not necessary 161 | 162 | #print "Create cluster as Python objects" 163 | #pprint(sioobj.to_JSON()) 164 | imconn.push_cluster_configuration(sioobj.to_JSON()) 165 | 166 | print "Start Install process!!!" 167 | im_installer.Execute() # Start install process 168 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/mapping/statistics.py: -------------------------------------------------------------------------------- 1 | from scaleiopy.api.scaleio.mapping.sio_generic_object import SIO_Generic_Object 2 | 3 | 4 | class SIO_Statistics(SIO_Generic_Object): 5 | """ Represents one ScaleIO cluster/installation as a class object """ 6 | 7 | def __init__(self, 8 | primaryReadFromDevBwc = None, 9 | numOfStoragePools = None, 10 | protectedCapacityInKb = None, 11 | movingCapacityInKb = None, 12 | activeFwdRebuildCapacityInKb = None, 13 | degradedHealthyVacInKb = None, 14 | snapCapacityInUseOccupiedInKb = None, 15 | snapCapacityInUseInKb = None, 16 | activeMovingRebalanceJobs = None, 17 | totalReadBwc = None, 18 | maxCapacityInKb = None, 19 | pendingBckRebuildCapacityInKb = None, 20 | activeMovingOutFwdRebuildJobs = None, 21 | secondaryVacInKb = None, 22 | capacityLimitInKb = None, 23 | pendingFwdRebuildCapacityInKb = None, 24 | atRestCapacityInKb = None, 25 | thinCapacityInUseInKb = None, 26 | activeMovingInBckRebuildJobs = None, 27 | numOfScsiInitiators = None, 28 | degradedHealthyCapacityInKb = None, 29 | numOfUnmappedVolumes = None, 30 | secondaryReadFromDevBwc = None, 31 | numOccured = None, 32 | totalWeightInKb = None, 33 | numSeconds = None, 34 | failedCapacityInKb = None, 35 | secondaryWriteBwc = None, 36 | numOfVolumes = None, 37 | activeBckRebuildCapacityInKb = None, 38 | failedVacInKb = None, 39 | pendingMovingCapacityInKb = None, 40 | activeMovingInRebalanceJobs = None, 41 | pendingMovingInRebalanceJobs = None, 42 | bckRebuildReadBwc = None, 43 | degradedFailedVacInKb = None, 44 | numOfSnapshots = None, 45 | rebalanceCapacityInKb = None, 46 | fwdRebuildReadBwc = None, 47 | activeMovingInFwdRebuildJobs = None, 48 | numOfSdc = None, 49 | numOfVtrees = None, 50 | thickCapacityInUseInKb = None, 51 | pendingRebalanceCapacityInKb = None, 52 | protectedVacInKb = None, 53 | capacityAvailableForVolumeAllocationInKb = None, 54 | pendingMovingInBckRebuildJobs = None, 55 | pendingMovingRebalanceJobs = None, 56 | numOfProtectionDomains = None, 57 | numOfSds = None, 58 | capacityInUseInKb = None, 59 | degradedFailedCapacityInKb = None, 60 | bckRebuildWriteBwc = None, 61 | numOfThinBaseVolumes = None, 62 | pendingMovingOutFwdRebuildJobs = None, 63 | secondaryReadBwc = None, 64 | pendingMovingOutBckRebuildJobs = None, 65 | rebalanceWriteBwc = None, 66 | primaryReadBwc = None, 67 | numOfVolumesInDeletion = None, 68 | numOfDevices = None, 69 | inUseVacInKb = None, 70 | rebalanceReadBwc = None, 71 | unreachableUnusedCapacityInKb = None, 72 | totalWriteBwc = None, 73 | spareCapacityInKb = None, 74 | activeMovingOutBckRebuildJobs = None, 75 | primaryVacInKb = None, 76 | bckRebuildCapacityInKb = None, 77 | numOfThickBaseVolumes = None, 78 | numOfMappedToAllVolumes = None, 79 | activeMovingCapacityInKb = None, 80 | pendingMovingInFwdRebuildJobs = None, 81 | rmcacheSizeInKb = None, 82 | activeRebalanceCapacityInKb = None, 83 | fwdRebuildCapacityInKb = None, 84 | fwdRebuildWriteBwc = None, 85 | primaryWriteBwc = None 86 | ): 87 | 88 | self.primary_ReadFromDevBwc = primaryReadFromDevBwc 89 | self.num_OfStoragePools = numOfStoragePools 90 | self.protected_CapacityInKb = protectedCapacityInKb 91 | self.movingCapacityInKb = movingCapacityInKb 92 | self.activeFwdRebuildCapacityInKb = activeFwdRebuildCapacityInKb 93 | self.degradedHealthyVacInKb = degradedHealthyVacInKb 94 | self.snapCapacityInUseOccupiedInKb = snapCapacityInUseOccupiedInKb 95 | self.snapCapacityInUseInKb = snapCapacityInUseInKb 96 | self.activeMovingRebalanceJobs = activeMovingRebalanceJobs 97 | self.totalReadBwc = totalReadBwc 98 | self.maxCapacityInKb = maxCapacityInKb 99 | self.pendingBckRebuildCapacityInKb = pendingBckRebuildCapacityInKb 100 | self.activeMovingOutFwdRebuildJobs = activeMovingOutFwdRebuildJobs 101 | self.secondaryVacInKb = secondaryVacInKb 102 | self.capacityLimitInKb = capacityLimitInKb 103 | self.pendingFwdRebuildCapacityInKb = pendingFwdRebuildCapacityInKb 104 | self.atRestCapacityInKb = atRestCapacityInKb 105 | self.thinCapacityInUseInKb = thinCapacityInUseInKb 106 | self.activeMovingInBckRebuildJobs = activeMovingInBckRebuildJobs 107 | self.numOfScsiInitiators = numOfScsiInitiators 108 | self.degradedHealthyCapacityInKb = degradedHealthyCapacityInKb 109 | self.numOfUnmappedVolumes = numOfUnmappedVolumes 110 | self.secondaryReadFromDevBwc = secondaryReadFromDevBwc 111 | self.failedCapacityInKb = failedCapacityInKb 112 | self.secondaryWriteBwc = secondaryWriteBwc 113 | self.numOfVolumes = numOfVolumes 114 | self.activeBckRebuildCapacityInKb = activeBckRebuildCapacityInKb 115 | self.failedVacInKb = failedVacInKb 116 | self.pendingMovingCapacityInKb = pendingMovingCapacityInKb 117 | self.activeMovingInRebalanceJobs = activeMovingInRebalanceJobs 118 | self.pendingMovingInRebalanceJobs = pendingMovingInRebalanceJobs 119 | self.bckRebuildReadBwc = bckRebuildReadBwc 120 | self.degradedFailedVacInKb = degradedFailedVacInKb 121 | self.numOfSnapshots = numOfSnapshots 122 | self.rebalanceCapacityInKb, = rebalanceCapacityInKb 123 | self.fwdRebuildReadBwc = fwdRebuildReadBwc 124 | self.activeMovingInFwdRebuildJobs = activeMovingInFwdRebuildJobs 125 | self.numOfSdc = numOfSdc 126 | self.numOfVtrees = numOfVtrees 127 | self.thickCapacityInUseInKb = thickCapacityInUseInKb 128 | self.pendingRebalanceCapacityInKb = pendingRebalanceCapacityInKb 129 | self.protectedVacInKb = protectedVacInKb 130 | self.capacityAvailableForVolumeAllocationInKb = capacityAvailableForVolumeAllocationInKb 131 | self.pendingMovingInBckRebuildJobs = pendingMovingInBckRebuildJobs 132 | self.pendingMovingRebalanceJobs = pendingMovingRebalanceJobs 133 | self.numOfProtectionDomains = numOfProtectionDomains 134 | self.numOfSds = numOfSds 135 | self.capacityInUseInKb = capacityInUseInKb 136 | self.degradedFailedCapacityInKb = degradedFailedCapacityInKb 137 | self.bckRebuildWriteBwc = bckRebuildWriteBwc 138 | self.numOfThinBaseVolumes = numOfThinBaseVolumes 139 | self.pendingMovingOutFwdRebuildJobs = pendingMovingOutFwdRebuildJobs 140 | self.secondaryReadBwc = secondaryReadBwc 141 | self.pendingMovingOutBckRebuildJobs = pendingMovingOutBckRebuildJobs 142 | self.rebalanceWriteBwc = rebalanceWriteBwc 143 | self.primaryReadBwc = primaryReadBwc 144 | self.numOfVolumesInDeletion = numOfVolumesInDeletion 145 | self.numOfDevices = numOfDevices 146 | self.inUseVacInKb = inUseVacInKb 147 | self.rebalanceReadBwc = rebalanceReadBwc 148 | self.unreachableUnusedCapacityInKb = unreachableUnusedCapacityInKb 149 | self.totalWriteBwc = totalWriteBwc 150 | self.spareCapacityInKb = spareCapacityInKb 151 | self.activeMovingOutBckRebuildJobs = activeMovingOutBckRebuildJobs 152 | self.primaryVacInKb = primaryVacInKb 153 | self.bckRebuildCapacityInKb = bckRebuildCapacityInKb 154 | self.numOfThickBaseVolumes = numOfThickBaseVolumes 155 | self.numOfMappedToAllVolumes = numOfMappedToAllVolumes 156 | self.activeMovingCapacityInKb = activeMovingCapacityInKb 157 | self.pendingMovingInFwdRebuildJobs = pendingMovingInFwdRebuildJobs 158 | self.rmcacheSizeInKb = rmcacheSizeInKb 159 | self.activeRebalanceCapacityInKb = activeRebalanceCapacityInKb 160 | self.fwdRebuildCapacityInKb = fwdRebuildCapacityInKb 161 | self.fwdRebuildWriteBwc = fwdRebuildWriteBwc 162 | self.primaryWriteBwc = primaryWriteBwc 163 | 164 | 165 | @staticmethod 166 | def from_dict(dict): 167 | """ 168 | A convenience method that directly creates a new instance from a passed dictionary (that probably came from a 169 | JSON response from the server. 170 | """ 171 | return SIO_Statistics(**dict) 172 | -------------------------------------------------------------------------------- /examples/install-cluster-syslog.py: -------------------------------------------------------------------------------- 1 | import time 2 | import json 3 | from pprint import pprint 4 | 5 | # Project imports 6 | from scaleiopy import im as sioobj 7 | from scaleiopy.util.installerfsm import * #installerfsm as instfsm 8 | from scaleiopy.im import Im 9 | 10 | 11 | from scaleiopy.api.im.mapping.node import Node_Object 12 | from scaleiopy.api.im.mapping.mdm import Mdm_Object 13 | from scaleiopy.api.im.mapping.callhome_configuration import Call_Home_Configuration_Object 14 | from scaleiopy.api.im.mapping.tb import Tb_Object 15 | from scaleiopy.api.im.mapping.syslog_configuration import Remote_Syslog_Configuration_Object 16 | from scaleiopy.api.im.mapping.sdc import Sdc_Object 17 | from scaleiopy.api.im.mapping.sds import Sds_Object 18 | from scaleiopy.api.im.mapping.sds_device import Sds_Device_Object 19 | from scaleiopy.api.im.system import System_Object 20 | 21 | ########################### 22 | # Create a ScaleIO System # 23 | ########################### 24 | # 25 | # Prereq: 3 x CentOS 6.5 or RHEL 6.5 26 | # 27 | # Flow: 28 | # Create Nodes 29 | # Create basic info. mdmPass, liaPass and some others 30 | # Construct MDM and TB and basic info 31 | # Create list of SDS 32 | # Create list of SDC 33 | 34 | 35 | ################### 36 | # Construct nodes # 37 | ################### 38 | nodeUsername = 'root' # Username for ScaleIO Node OS (these machines need to be pre installed) 39 | nodePassword = 'vagrant' # Password for ScaleIO Node OS 40 | #node1 = sioobj.ScaleIO_Node_Object(None, None, ['192.168.102.11'], None, 'linux', nodePassword, nodeUsername) 41 | #node2 = sioobj.ScaleIO_Node_Object(None, None, ['192.168.102.12'], None, 'linux', nodePassword, nodeUsername) 42 | #node3 = sioobj.ScaleIO_Node_Object(None, None, ['192.168.102.13'], None, 'linux', nodePassword, nodeUsername) 43 | node1 = sioobj.Node_Object(None, None, ['192.168.102.11'], None, 'linux', nodePassword, nodeUsername) 44 | node2 = sioobj.Node_Object(None, None, ['192.168.102.12'], None, 'linux', nodePassword, nodeUsername) 45 | node3 = sioobj.Node_Object(None, None, ['192.168.102.13'], None, 'linux', nodePassword, nodeUsername) 46 | 47 | ########################################## 48 | # Construct basic info for System_Object # 49 | ########################################## 50 | mdmIPs = ['192.168.102.12','192.168.102.13'] 51 | sdcList = [] 52 | sdsList = [] 53 | mdmPassword = 'Scaleio123' 54 | liaPassword = 'Scaleio123' 55 | licenseKey = None 56 | installationId = None 57 | 58 | # Remote logging parameters 59 | remoteSyslogServer = '192.168.102.1' # IP address to remote syslog server (uses TCP to connect) 60 | remoteSyslogTcpPort = '514' # Remote syslog server TCP port 61 | remoteSyslogFacility = 16 # Syslog Facility 62 | 63 | # Callhome parameters 64 | callhomeEmailFrom = 'scaleio@localhost' # If a from email box is needed, set it here 65 | callhomeMdmUsername = 'admin' # This is the MDM admin username to use to connect/poll status 66 | callhomeMdmPassword = mdmPassword # Same as MDM password defined above 67 | callhomeCustomerName = 'Customer' # Customer name 68 | callhomeHost = 'localhost' # IP or hostname to SMTP server 69 | callhomePort = '25' # TCP port of SMTP server 70 | callhomeTls = True # Encryption method. (True or False) 71 | callhomeSmtpUsername = 'root' # If a username is needed to connect to SMTP server, set it here 72 | callhomeSmtpPassword = 'password' 73 | callhomeAlertEmailTo = 'root@localhost' # Commaseparated list of email addresses as alert recipients 74 | callhomeSeverity = 'ERROR' # What severity level of events should trigger sending emails (investigate format) 75 | 76 | 77 | ######################################## 78 | # Create MDMs and TB for System_Object # 79 | ######################################## 80 | primaryMdm = sioobj.Mdm_Object(json.loads(node2.to_JSON()), None, None, node2.nodeIPs) 81 | secondaryMdm = sioobj.Mdm_Object(json.loads(node3.to_JSON()), None, None, node3.nodeIPs) 82 | tb = sioobj.Tb_Object(json.loads(node1.to_JSON()), None, node1.nodeIPs) 83 | callHomeConfiguration = sioobj.Call_Home_Configuration_Object(callhomeEmailFrom, 84 | callhomeMdmUsername, 85 | callhomeMdmPassword, 86 | callhomeCustomerName, 87 | callhomeHost, 88 | callhomePort, 89 | callhomeTls, 90 | callhomeSmtpUsername, 91 | callhomeAlertEmailTo, 92 | callhomeSeverity 93 | ) 94 | #callHomeConfiguration = None 95 | 96 | remoteSyslogConfiguration = sioobj.Remote_Syslog_Configuration_Object(remoteSyslogServer,remoteSyslogTcpPort, remoteSyslogFacility) 97 | #remoteSyslogConfiguration = None # Set to None if you dont need remote logging for SIO 98 | 99 | ################################################################ 100 | #Create SDS objects - To be added to SDS list in System_Object # 101 | ################################################################ 102 | # Adjust addDevice() to match local block device you have in your node 103 | # Define SDS that belong to a FaultSet - Not tested! 104 | #sds1 = sioobj.Sds_Object(json.loads(node1.to_JSON()), None, 'SDS_' + str(node1.nodeIPs[0]), 'default', 'faultset1', node1.nodeIPs, None, None, None, False, '7072') 105 | 106 | sds1 = sioobj.Sds_Object(json.loads(node1.to_JSON()), None, 'SDS_' + str(node1.nodeIPs[0]), 'default', None, node1.nodeIPs, None, None, None, False, '7072') 107 | sds1.addDevice("/dev/loop0", None, None) 108 | sds2 = sioobj.Sds_Object(json.loads(node2.to_JSON()), None, 'SDS_' + str(node2.nodeIPs[0]), 'default', None, node2.nodeIPs, None, None, None, False, '7072') 109 | sds2.addDevice("/dev/loop0", None, None) 110 | sds3 = sioobj.Sds_Object(json.loads(node3.to_JSON()), None, 'SDS_' + str(node3.nodeIPs[0]), 'default', None, node3.nodeIPs, None, None, None, False, '7072') 111 | sds3.addDevice("/dev/loop0", None, None) 112 | sdsList.append(json.loads(sds1.to_JSON())) 113 | sdsList.append(json.loads(sds2.to_JSON())) 114 | sdsList.append(json.loads(sds3.to_JSON())) 115 | 116 | ############################################################# 117 | # Create SDC objects - To be added as list to System_Object # 118 | ############################################################# 119 | # Decide which nodes in your cluster should become a SDC 120 | """ 121 | node=None, 122 | nodeInfo=None, 123 | splitterRpaIp=None 124 | """ 125 | sdc1 = sioobj.Sdc_Object(json.loads(node1.to_JSON()), None, None) 126 | sdc2 = sioobj.Sdc_Object(json.loads(node2.to_JSON()), None, None) 127 | sdc3 = sioobj.Sdc_Object(json.loads(node3.to_JSON()), None, None) 128 | 129 | sdcList.append(json.loads(sdc1.to_JSON())) 130 | sdcList.append(json.loads(sdc2.to_JSON())) 131 | sdcList.append(json.loads(sdc3.to_JSON())) 132 | 133 | ###################################################### 134 | # Construct a complete ScaleIO cluster configuration # 135 | ###################################################### 136 | sioobj = sioobj.System_Object(installationId, 137 | mdmIPs, 138 | mdmPassword, 139 | liaPassword, 140 | licenseKey, 141 | json.loads(primaryMdm.to_JSON()), 142 | json.loads(secondaryMdm.to_JSON()), 143 | json.loads(tb.to_JSON()), 144 | sdsList, 145 | sdcList, 146 | callHomeConfiguration, 147 | remoteSyslogConfiguration 148 | ) 149 | 150 | # Export sioobj to JSON (should upload clean in IM)s 151 | 152 | ########################################################################### 153 | # Push System_Object JSON - To be used by IM to install ScaleIO on nodes # 154 | ########################################################################### 155 | 156 | 157 | ####################### 158 | # LOGIN TO SCALEIO IM # 159 | ####################### 160 | imconn = Im("https://192.168.102.12","admin","Scaleio123",verify_ssl=False) # "Password1!") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 161 | imconn._login() 162 | 163 | ### UPLOAD RPM PACKAGES TO BE DEPLOYED BY IM ### 164 | imconn.uploadPackages('/Users/swevm/Downloads/RHEL6_1277/') # Adjust to your needs. All RPMs for RHEL6 should exist in this dir except for GUI and Gateway 165 | #imconn.uploadPackages('/Users/swevm/Downloads/RHEL6_132_402_1/') # ScaleIO 1.32 FnF for RHEL6 (This is 1.32 GA build) 166 | 167 | #################### 168 | # INSTALLER STAGES # 169 | #################### 170 | 171 | # Initialize Installer 172 | im_installer = InstallerFSM(imconn, True) 173 | 174 | time.sleep(5) # Wait a few seconds before continuing - Not necessary 175 | 176 | #print "Create cluster as Python objects" 177 | #pprint(sioobj.to_JSON()) 178 | imconn.push_cluster_configuration(sioobj.to_JSON()) 179 | 180 | print "Start Install process!!!" 181 | im_installer.Execute() # Start install process 182 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2015 Magnus Nilsson 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /scaleiopy/util/installerfsm.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | import json 3 | import time 4 | 5 | 6 | """ 7 | TODO: 8 | * Add logging (replace all print statements) - Use self.FSM.imapi.logger.debug() to access IM objects existing logger facility 9 | 10 | """ 11 | 12 | ##====================================================== 13 | # ScaleIO IM Installation process State Machine 14 | ##====================================================== 15 | 16 | 17 | ##=============================================== 18 | ## TRANSITIONS 19 | 20 | class Transition(object): 21 | # Code executed when transitioning from one state to another 22 | def __init__(self, toState): 23 | self.toState = toState 24 | 25 | def Execute(self): 26 | print ("Transitioning...") 27 | print "" 28 | 29 | 30 | ##=============================================== 31 | ## STATES 32 | 33 | class State(object): 34 | # The base template state which all others will inherit from 35 | def __init__(self, FSM, im_inst): 36 | self.FSM = FSM 37 | self.timer = 0 38 | self.startTime = 0 39 | im_instance = im_inst 40 | # Possible states: IDLE, PENDING, FAILED, COMPLETED 41 | #print self.FSM.getCurrentStateStatus() 42 | 43 | def Enter(self): 44 | self.FSM.setCurrentStateStatus('PENDING') 45 | pass 46 | 47 | def Execute (self): 48 | pass 49 | 50 | def status (self): 51 | pass 52 | 53 | def Next(self): 54 | pass 55 | 56 | def Exit(self): 57 | pass 58 | 59 | class Query(State): 60 | # Worker class - Takes care of managing IM QUERY phase and do status control of each phase 61 | def __init__(self, FSM, im_inst): 62 | super(Query, self).__init__(FSM, im_inst) 63 | self.calling_object = im_inst 64 | 65 | def Enter(self): 66 | print ("Entering QUERY phase") 67 | super(Query, self).Enter() 68 | 69 | def Execute (self): 70 | print ("Start ScaleIO IM Query phase") 71 | self.FSM.imapi.set_state('query') 72 | print self.FSM.imapi.get_state() 73 | self.pendingItemCount = self.FSM.install_process_status("query") 74 | while self.pendingItemCount > 0: 75 | self.pendingItemCount = self.FSM.install_process_status("query") 76 | time.sleep(0.5) 77 | if self.pendingItemCount < 0: 78 | print "*** FAILED ***" 79 | else: 80 | print "*** COMPLETED ***" 81 | self.Next() 82 | 83 | if self.FSM.autoTransition: 84 | self.FSM.Execute() 85 | 86 | def Next(self): 87 | #print ("Advance to next step") 88 | self.FSM.ToTransition("toUPLOAD") # Move to next step 89 | 90 | def Exit(self): 91 | pass 92 | #print ("Exiting QUERY phase.") 93 | 94 | class Upload(State): 95 | # Worker class - Takes care of managing IM QUERY phase and do status control of each phase 96 | def __init__(self, FSM, im_inst): 97 | super(Upload, self).__init__(FSM, im_inst) 98 | 99 | def Enter(self): 100 | print ("Entering UPLOAD phase") 101 | super(Upload, self).Enter() 102 | 103 | def Execute (self): 104 | print ("Upload of binaries to nodes") 105 | # Call set_state 106 | self.FSM.imapi.set_state('upload') # Set IM to UPLOAD state 107 | print self.FSM.imapi.get_state() 108 | self.pendingItemCount = self.FSM.install_process_status("upload") 109 | while self.pendingItemCount > 0: 110 | self.pendingItemCount = self.FSM.install_process_status("upload") 111 | time.sleep(2) 112 | if self.pendingItemCount < 0: 113 | print "*** FAILED ***" 114 | else: 115 | print "*** COMPLETED ***" 116 | self.Next() 117 | 118 | if self.FSM.autoTransition: 119 | self.FSM.Execute() 120 | 121 | def Next(self): 122 | #print ("Advance to next step") 123 | self.FSM.ToTransition("toINSTALL") # Move to next step 124 | 125 | def Exit(self): 126 | pass 127 | #print ("Exiting UPLOAD phase.") 128 | 129 | 130 | class Install(State): 131 | # Worker class - Takes care of managing IM Install phase and do status control of each phase 132 | def __init__(self, FSM, im_inst): 133 | super(Install, self).__init__(FSM, im_inst) 134 | 135 | def Enter(self): 136 | print ("Entering install phase") 137 | super(Install, self).Enter() 138 | 139 | def Execute (self): 140 | print ("Installing ScaleIO binaries") 141 | self.FSM.imapi.set_state('install') # Set IM to QUERY state 142 | print self.FSM.imapi.get_state() 143 | self.pendingItemCount = self.FSM.install_process_status("install") 144 | while self.pendingItemCount > 0: 145 | self.pendingItemCount = self.FSM.install_process_status("install") 146 | time.sleep(2) 147 | if self.pendingItemCount < 0: 148 | print "*** FAILED ***" 149 | else: 150 | print "*** COMPLETED ***" 151 | time.sleep(6) 152 | self.Next() 153 | 154 | if self.FSM.autoTransition: 155 | self.FSM.Execute() 156 | 157 | def Next(self): 158 | print ("Advance to next step") 159 | self.FSM.ToTransition("toCONFIGURE") # Move to next step 160 | 161 | def Exit(self): 162 | print ("Exiting INSTALL phase.") 163 | 164 | 165 | class Configure(State): 166 | # Worker class - Takes care of managing IM Configure phase and do status control of each phase 167 | def __init__(self, FSM, im_inst): 168 | super(Configure, self).__init__(FSM, im_inst) 169 | 170 | def Enter(self): 171 | print ("Entering configure phase") 172 | super(Configure, self).Enter() 173 | 174 | def Execute (self): 175 | print ("Configure ScaleIO cluster") 176 | # Call set_state 177 | self.FSM.imapi.set_state('configure') # Set IM to QUERY state 178 | print self.FSM.imapi.get_state() 179 | self.pendingItemCount = self.FSM.install_process_status("configure") 180 | while self.pendingItemCount > 0: 181 | self.pendingItemCount = self.FSM.install_process_status("configure") 182 | time.sleep(2) 183 | if self.pendingItemCount < 0: 184 | print "*** FAILED ***" 185 | else: 186 | print "*** COMPLETED ***" 187 | self.Next() 188 | 189 | if self.FSM.autoTransition: 190 | self.FSM.Execute() 191 | 192 | def Next(self): 193 | #print ("Advance to next step") 194 | self.FSM.ToTransition("toARCHIVE") # Move to next step 195 | 196 | def Exit(self): 197 | pass 198 | #print ("Exiting CONFIGURE phase.") 199 | 200 | class Archive(State): 201 | # Worker class - Takes care of managing IM Archive phase and do status control of each phase 202 | def __init__(self, FSM, im_inst): 203 | super(Archive, self).__init__(FSM, im_inst) 204 | 205 | def Enter(self): 206 | print ("Entering archive phase") 207 | super(Archive, self).Enter() 208 | 209 | def Execute (self): 210 | print ("Completing ScaleIO cluster install") 211 | #print ("Execute self.set_archive_all()") 212 | self.FSM.imapi.set_archive_all() 213 | print self.FSM.imapi.get_state() 214 | time.sleep(3) 215 | 216 | self.Next() 217 | if self.FSM.autoTransition: 218 | self.FSM.Execute() 219 | 220 | def Next(self): 221 | print ("Advance to next step") 222 | self.FSM.ToTransition("toCOMPLETE") # Move to next step 223 | 224 | def Exit(self): 225 | print ("Exiting ARCHIVE phase.") 226 | 227 | class Complete(State): 228 | # Worker class - Takes care of managing IM "Completing" phase and do status control of each phase 229 | def __init__(self, FSM, im_inst): 230 | super(Complete, self).__init__(FSM, im_inst) 231 | 232 | def Enter(self): 233 | print ("Entering COMPLETE phase") 234 | super(Complete, self).Enter() 235 | 236 | def Execute (self): 237 | print ("Installation complete!") 238 | self.FSM.setCurrentStateStatus('COMPLETED') 239 | print self.FSM.getCurrentStateStatus() 240 | print self.FSM.imapi.get_state() 241 | 242 | def Next(self): 243 | self.FSM.setCurrentStateStatus('COMPLETED') 244 | 245 | def Exit(self): 246 | print ("Exiting COMPLETE phase.") 247 | 248 | ##=============================================== 249 | ## FINITE STATE MACHINE 250 | 251 | class FSM(object): 252 | # Holds the states and transitions available, 253 | # executes current states main functions and transitions 254 | def __init__(self, imapi): 255 | self.states = {} 256 | self.transitions = {} 257 | self.curState = None 258 | self.prevState = None ## USE TO PREVENT LOOPING 2 STATES FOREVER 259 | self.trans = None 260 | self.autoTransition = False 261 | self.completed = False 262 | self.curStateStatus = 'IDLE' 263 | self.imapi = imapi 264 | 265 | def AddTransition(self, transName, transition): 266 | self.transitions[transName] = transition 267 | 268 | def enableAutoTransition(self): 269 | self.autoTransition = True 270 | 271 | def disableAutoTransition(self): 272 | self.autoTransition = False 273 | 274 | def AddState(self, stateName, state): 275 | self.states[stateName] = state 276 | 277 | def SetState(self, stateName): 278 | self.prevState = self.curState 279 | self.curState = self.states[stateName] 280 | 281 | def getState(self): 282 | return curState 283 | 284 | def setCurrentStateStatus(self, status): 285 | print "*** Setting currentStateStatus to " + status + " ***" 286 | self.curStateStatus = status 287 | if status == 'COMPLETED': 288 | self.trans = None 289 | # self.curState = None 290 | #print "setCurrentStateStatus() = " + self.curStateStatus 291 | 292 | def getCurrentStateStatus(self): 293 | return self.curStateStatus 294 | # Return IDLE, FAILED, PENDING, COMPLETE 295 | 296 | def ToTransition(self, toTrans): 297 | self.trans = self.transitions[toTrans] 298 | 299 | def Next(self): 300 | if self.curStateStatus == 'IDLE' or self.curStateStatus == 'PENDING': 301 | self.curState.Next() 302 | if self.autoTransition: 303 | self.Execute() 304 | else: 305 | self.trans = None 306 | print " Status is COMPLETE - Next() will not do anything" 307 | 308 | def Execute(self): 309 | print "FSM Execute() - curStateStatus = " + self.curStateStatus 310 | if self.curStateStatus == 'IDLE' or self.curStateStatus == 'PENDING': 311 | self.setCurrentStateStatus('PENDING') 312 | if (self.trans): 313 | self.curState.Exit() 314 | self.trans.Execute() 315 | self.SetState(self.trans.toState) 316 | self.curState.Enter() 317 | self.trans = None 318 | self.curState.Execute() 319 | else: 320 | print "Statemachine COMPLETE" 321 | self.trans = None 322 | 323 | 324 | def install_process_status(self, expectState): 325 | self.imapi.get_state() 326 | self.cStatusItems = json.loads(self.imapi.get_command_state()) 327 | self.allCompleted = False 328 | self.pendingItemCount = 0 329 | for kOuter,vOuter in self.cStatusItems.items(): 330 | for innerArray in vOuter: 331 | if innerArray['allowedState'] == str(expectState): 332 | if innerArray['commandState'] == 'failed': 333 | return -1 334 | if innerArray['commandState'] == 'pending': 335 | self.pendingItemCount += 1 336 | print "Pending Items = " + str(self.pendingItemCount) 337 | if self.pendingItemCount == 0: 338 | return 0 339 | else: 340 | return self.pendingItemCount 341 | 342 | # IM State: 343 | # idle, query, upload, install, configure 344 | # IM Command state: 345 | # pending, failed, completed 346 | 347 | ##=============================================== 348 | ## IMPLEMENTATION 349 | 350 | Char = type("Char", (object,), {}) 351 | 352 | class InstallerFSM: 353 | # Base character which will be holding the Finite State Machine, 354 | # which in turn will hold the states and transitions. 355 | def __init__(self, imapi, automatic=False): 356 | self.FSM = FSM(imapi) 357 | if automatic: 358 | self.FSM.enableAutoTransition() 359 | 360 | ## STATES 361 | self.FSM.AddState("QUERY", Query(self.FSM, imapi)) 362 | self.FSM.AddState("UPLOAD", Upload(self.FSM, imapi)) 363 | self.FSM.AddState("INSTALL", Install(self.FSM, imapi)) 364 | self.FSM.AddState("CONFIGURE", Configure(self.FSM, imapi)) 365 | self.FSM.AddState("ARCHIVE", Archive(self.FSM, imapi)) 366 | self.FSM.AddState("COMPLETE", Complete(self.FSM, imapi)) 367 | 368 | ## TRANSITIONS 369 | self.FSM.AddTransition("toQUERY", Transition("QUERY")) 370 | self.FSM.AddTransition("toUPLOAD", Transition("UPLOAD")) 371 | self.FSM.AddTransition("toINSTALL", Transition("INSTALL")) 372 | self.FSM.AddTransition("toCONFIGURE", Transition("CONFIGURE")) 373 | self.FSM.AddTransition("toARCHIVE", Transition("ARCHIVE")) 374 | self.FSM.AddTransition("toCOMPLETE", Transition("COMPLETE")) 375 | 376 | self.FSM.SetState("QUERY") # When executing FSM first time always start at QUERY phase 377 | 378 | def Next(self): 379 | self.FSM.Next() 380 | 381 | def getCurrentStateStatus(self): 382 | return self.FSM.getCurrentStateStatus() 383 | 384 | def Execute(self): 385 | self.FSM.Execute() 386 | 387 | -------------------------------------------------------------------------------- /scaleiopy/api/scaleio/provisioning/volume.py: -------------------------------------------------------------------------------- 1 | # Standard lib imports 2 | # None 3 | 4 | # Third party imports 5 | # None 6 | 7 | # Project level imports 8 | from scaleiopy.api.scaleio.mapping.volume import SIO_Volume 9 | 10 | 11 | 12 | class Volume(object): 13 | 14 | def __init__(self, connection): 15 | """ 16 | Initialize a new instance 17 | """ 18 | self.conn = connection 19 | 20 | @property 21 | def get(self): 22 | pass 23 | 24 | def create_volume(self, volName, volSizeInMb, pdObj, spObj, thinProvision=True, **kwargs): #v1.32 require storagePoolId when creating a volume 25 | # Check if object parameters are the correct ones, otherwise throw error 26 | self.conn.connection._check_login() 27 | if thinProvision: 28 | volType = 'ThinProvisioned' 29 | else: 30 | volType = 'ThickProvisioned' 31 | # ScaleIO v1.31 demand protectionDomainId in JSON but not storgePoolId. v1.32 is fine with storeagePoolId only 32 | volumeDict = {'protectionDomainId': pdObj.id, 'storagePoolId': spObj.id, 'volumeSizeInKb': str(int(volSizeInMb) * 1024), 'name': volName, 'volumeType': volType} 33 | response = self.conn.connection._do_post("{}/{}".format(self.conn.connection._api_url, "types/Volume/instances"), json=volumeDict) 34 | 35 | if kwargs: 36 | for key, value in kwargs.iteritems(): 37 | if key == 'enableMapAllSdcs' and value == True: 38 | self.map_volume_to_sdc(self.conn.connection.get_volume_by_name(volName), enableMapAllSdcs=True) 39 | if key == 'mapToSdc': 40 | if value: 41 | for innerKey, innerValue in kwargs.iteritems(): 42 | if innerKey == 'enableMapAllSdcs': 43 | if innerValue == True: 44 | self.map_volume_to_sdc(self.conn.connection.get_volume_by_name(volName), enableMapAllSdcs=True) 45 | else: 46 | self.map_volume_to_sdc(self.conn.connection.get_volume_by_name(volName), self.get_sdc_by_name(value)) 47 | return response 48 | 49 | def is_valid_volsize(self,volsize): 50 | """ 51 | Convenience method that round input to valid ScaleIO Volume size (8GB increments) 52 | :param volsize: Size in MB 53 | :rtype int: Valid ScaleIO Volume size rounded to nearest 8GB increment above or equal to volsize 54 | """ 55 | 56 | if type(volsize) is int: 57 | size_temp = divmod(volsize, 8192) 58 | if size_temp[1] > 0: # If not on 8GB boundary 59 | return int((1 + size_temp[0]) * 8192) # Always round to next 8GB increment 60 | else: 61 | return int(volsize) 62 | 63 | def delete_volume(self, volObj, removeMode='ONLY_ME', **kwargs): 64 | """ 65 | removeMode = 'ONLY_ME' | 'INCLUDING_DESCENDANTS' | 'DESCENDANTS_ONLY' | 'WHOLE_VTREE' 66 | Using kwargs it will be possible to tell delete_volume() to unmap all SDCs before delting. Not working yet 67 | """ 68 | if kwargs: 69 | for key, value in kwargs.iteritems(): 70 | if key =='autoUnmap' and value ==True: 71 | # Find all mapped SDS to this volObj 72 | # Call unmap for all of them 73 | if self.get_volume_all_sdcs_mapped(volObj): 74 | try: 75 | self.conn.cluster.unmap_volume_from_sdc(volObj, enableMapAllSdcs=False) 76 | except: 77 | raise RuntimeError("delete_volume() - enableMapAllSdcs error") 78 | else: # All SDS not enabled so loop through all mapped SDCs of volume and remove one by one 79 | for sdc in self.get_sdc_for_volume(volObj): 80 | try: 81 | self.unmap_volume_from_sdc(volObj, self.get_sdc_by_id(sdc['sdcId'])) 82 | except: 83 | raise RuntimeError("delete_volume() - unmap_volume_from_sdc() error") 84 | # TODO: 85 | # Check if object parameters are the correct ones, otherwise throw error 86 | self.conn.connection._check_login() 87 | deleteVolumeDict = {'removeMode': removeMode} 88 | try: 89 | response = self.conn.connection._do_post("{}/{}{}/{}".format(self.conn.connection._api_url, "instances/Volume::", volObj.id, 'action/removeVolume'), json=deleteVolumeDict) 90 | except: 91 | raise RuntimeError("delete_volume() - Communication error with ScaleIO Gateway") 92 | return response 93 | 94 | 95 | def map_volume_to_sdc(self, volumeObj, sdcObj=None, allowMultipleMappings=False, **kwargs): 96 | """ 97 | Map a Volume to SDC 98 | :param volumeObj: ScaleIO Volume object 99 | :param sdcObj: ScaleIO SDC object 100 | :param allowMultipleMappings: True to allow more than one SDC to be mapped to volume 101 | :return: POST request response 102 | :rtype: Requests POST response object 103 | """ 104 | self.conn.connection._check_login() 105 | if kwargs: 106 | for key, value in kwargs.iteritems(): 107 | if key == 'enableMapAllSdcs': 108 | if value == True: 109 | mapVolumeToSdcDict = {'allSdcs': 'True'} 110 | else: 111 | mapVolumeToSdcDict = {'sdcId': sdcObj.id, 'allowMultipleMappings': str(allowMultipleMappings).upper()} 112 | response = self.conn.connection._do_post("{}/{}{}/{}".format(self._api_url, "instances/Volume::", volumeObj.id, 'action/addMappedSdc'), json=mapVolumeToSdcDict) 113 | return response 114 | 115 | def get_volume_all_sdcs_mapped(self, volObj): 116 | if volObj.mappingToAllSdcsEnabled == True: 117 | return True 118 | return False 119 | 120 | def unmap_volume_from_sdc(self, volObj, sdcObj=None, **kwargs): 121 | """ 122 | Unmap a Volume from SDC or all SDCs 123 | :param volObj: ScaleIO Volume object 124 | :param sdcObj: ScaleIO SDC object 125 | :param \**kwargs: 126 | :Keyword Arguments: 127 | *disableMapAllSdcs* (``bool``) -- True to disable all SDCs mapping 128 | :return: POST request response 129 | :rtype: Requests POST response object 130 | :raise RuntimeError: If failure happen during communication with REST Gateway - Need to be cleaned up and made consistent to return understandable errors 131 | """ 132 | # TODO: 133 | # Check if object parameters are the correct ones, otherwise throw error 134 | # ADD logic for ALL SDC UNMAP 135 | # For all SDC unmapVolumeFromDict = {'allSdc':'True'} False can be used 136 | self.conn.connection._check_login() 137 | if kwargs: 138 | for key, value in kwargs.iteritems(): 139 | if key == 'enableMapAllSdcs' and value == False: 140 | if self.get_volume_all_sdcs_mapped(volObj): # Check if allSdc?s is True before continuing 141 | unmapVolumeFromSdcDict = {'allSdcs': 'False'} 142 | else: 143 | unmapVolumeFromSdcDict = {'sdcId': sdcObj.id} 144 | try: 145 | response = self.conn.connection._do_post("{}/{}{}/{}".format(self.conn.connection._api_url, "instances/Volume::", volObj.id, 'action/removeMappedSdc'), json=unmapVolumeFromSdcDict) 146 | except: 147 | raise RuntimeError("unmap_volume_from_sdc() - Cannot unmap volume") 148 | return response 149 | 150 | 151 | def get_volumes_for_sdc(self, sdcObj): 152 | """ 153 | :param sdcObj: SDC object 154 | :return: list of Volumes attached to SDC 155 | :rtyoe: ScaleIO Volume object 156 | """ 157 | self.conn.connection._check_login() 158 | all_volumes = [] 159 | response = self.conn.connection._do_get("{}/{}{}/{}".format(self.conn.connection._api_url, 'instances/Sdc::', sdcObj.id, 'relationships/Volume')).json() 160 | for sdc_volume in response: 161 | all_volumes.append( 162 | Volume.from_dict(sdc_volume) 163 | ) 164 | return all_volumes 165 | 166 | def get_volume_by_id(self, id): 167 | """ 168 | Get ScaleIO Volume object by its ID 169 | :param name: ID of volume 170 | :return: ScaleIO Volume object 171 | :raise KeyError: No Volume with specified ID found 172 | :rtype: ScaleIO Volume object 173 | """ 174 | for vol in self.conn.volumes: 175 | if vol.id == id: 176 | return vol 177 | raise KeyError("Volume with ID " + id + " not found") 178 | 179 | def get_volumes_for_vtree(self, vtreeObj): 180 | """ 181 | :param vtreeObj: VTree object 182 | Protection Domain Object 183 | :return: list of Volumes attached to VTree 184 | :rtype: ScaleIO Volume object 185 | """ 186 | self.conn.connection._check_login() 187 | all_volumes = [] 188 | response = self._do_get("{}/{}{}/{}".format(self.conn.connection._api_url, 'instances/VTree::', vtreeObj.id, 'relationships/Volume')).json() 189 | for vtree_volume in response: 190 | all_volumes.append( 191 | Volume.from_dict(vtree_volume) 192 | ) 193 | return all_volumes 194 | 195 | def get_volume_by_name(self, name): 196 | """ 197 | Get ScaleIO Volume object by its Name 198 | :param name: Name of volume 199 | :return: ScaleIO Volume object 200 | :raise KeyError: No Volume with specified name found 201 | :rtype: ScaleIO Volume object 202 | """ 203 | for vol in self.conn.volumes: 204 | if vol.name == name: 205 | return vol 206 | raise KeyError("Volume with NAME " + name + " not found") 207 | 208 | 209 | def resize_volume(self, volumeObj, sizeInGb, bsize=1000): 210 | """ 211 | Resize a volume to new GB size, must be larger than original. 212 | :param volumeObj: ScaleIO Volume Object 213 | :param sizeInGb: New size in GB (have to be larger than original) 214 | :param bsize: 1000 215 | :return: POST request response 216 | :rtype: Requests POST response object 217 | """ 218 | current_vol = self.get_volume_by_id(volumeObj.id) 219 | if current_vol.size_kb > (sizeInGb * bsize * bsize): 220 | raise RuntimeError( 221 | "resize_volume() - New size needs to be bigger than: %d KBs" % current_vol.size_kb) 222 | 223 | resizeDict = { 'sizeInGB' : str(sizeInGb) } 224 | response = self.conn.connection._do_post("{}/{}{}/{}".format( 225 | self.conn.connection._api_url, "instances/Volume::", volumeObj.id, 'action/setVolumeSize'), json=resizeDict) 226 | return response 227 | 228 | def create_snapshot(self, systemId, snapshotSpecificationObject): 229 | """ 230 | Create snapshot for list of volumes 231 | :param systemID: Cluster ID 232 | :param snapshotSpecificationObject: Of class SnapshotSpecification 233 | :rtype: SnapshotGroupId 234 | """ 235 | self.conn.connection._check_login() 236 | #try: 237 | response = self.conn.connection._do_post("{}/{}{}/{}".format(self.conn.connection._api_url, "instances/System::", systemId, 'action/snapshotVolumes'), json=snapshotSpecificationObject.__to_dict__()) 238 | #except: 239 | # raise RuntimeError("create_snapshot_by_system_id() - Error communicating with ScaleIO gateway") 240 | return response 241 | 242 | def delete_snapshot(self, systemId, snapshotGroupId): 243 | """ 244 | :param systemId: ID of Cluster 245 | :param snapshotGroupId: ID of snapshot group ID to be removed 246 | 247 | 248 | /api/instances/System::{id}/action/removeConsistencyGroupSnapshots 249 | type: POST 250 | Required: 251 | snapGroupId 252 | Return: 253 | numberOfVolumes - number of volumes that were removed because of this operation 254 | 255 | """ 256 | #try: 257 | consistencyGroupIdDict = {'snapGroupId':snapshotGroupId} 258 | self.conn.connection._check_login() 259 | response = self.conn.connection._do_post("{}/{}{}/{}".format(self.conn.connection._api_url, "instances/System::", systemId, 'action/removeConsistencyGroupSnapshots'), json=consistencyGroupIdDict) 260 | #except: 261 | # raise RuntimeError("delete_snapshot() - Error communicating wit ScaleIO gateway") 262 | return response 263 | 264 | def get_snapshots_by_vol(self, volObj): 265 | all_snapshots_for_vol = [] 266 | for volume in self.get_volumes_for_vtree(self.get_vtree_by_id(volObj.vtree_id)): 267 | if volume.ancestor_volume is not None: 268 | all_snapshots_for_vol.append(volume) 269 | return all_snapshots_for_vol 270 | 271 | def get_vtree_by_id(self,id): 272 | for vtree in self.vtrees: 273 | if vtree.id == id: 274 | return vtree 275 | raise KeyError("VTree with ID " + id + " not found") 276 | 277 | def get_vtree_by_name(self,name): 278 | for vtree in self.vtrees: 279 | if vtree.name == name: 280 | return vtree 281 | raise KeyError("VTree with NAME " + name + " not found") 282 | 283 | """ 284 | def get_snapshot_group_id_by_vol_name(self, volname): 285 | pass 286 | 287 | def get_snapshot_group_id_by_vol_id(self, volid): 288 | pass 289 | """ 290 | 291 | def get_sdc_for_volume(self, volObj): 292 | """ 293 | Get list of SDC mapped to a specific volume 294 | :param volObj: ScaleIO volume object 295 | :return: List of ScaleIO SDC objects (empty list if no mapping exist) 296 | :rtype: SDC object 297 | """ 298 | sdcList = [] 299 | if volObj.mapped_sdcs is not None: 300 | for sdc in volObj.mapped_sdcs: 301 | sdcList.append(sdc) 302 | if len(sdcList) == 0: 303 | self.conn.logger.debug("No SDCs mapped to volume: %s-(%s)" % (volObj.name, volObj.id)) 304 | return [] 305 | # returning an empty list is 306 | # valid for snapshots or volumes. 307 | return sdcList 308 | -------------------------------------------------------------------------------- /scaleiopy/scaleio.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | from requests.auth import HTTPBasicAuth 4 | from requests_toolbelt import SSLAdapter 5 | from requests.adapters import HTTPAdapter 6 | from requests.packages.urllib3.poolmanager import PoolManager 7 | import ssl 8 | import logging 9 | import time 10 | import sys 11 | 12 | 13 | from pprint import pprint 14 | 15 | # API specific imports 16 | from scaleiopy.api.scaleio.mapping.sio_generic_object import SIO_Generic_Object 17 | from scaleiopy.api.scaleio.system import SIO_System 18 | from scaleiopy.api.scaleio.mapping.sdc import SIO_SDC 19 | from scaleiopy.api.scaleio.mapping.sds import SIO_SDS 20 | from scaleiopy.api.scaleio.mapping.volume import SIO_Volume 21 | from scaleiopy.api.scaleio.mapping.storage_pool import SIO_Storage_Pool 22 | from scaleiopy.api.scaleio.mapping.protection_domain import SIO_Protection_Domain 23 | from scaleiopy.api.scaleio.mapping.faultset import SIO_Fault_Set 24 | from scaleiopy.api.scaleio.mapping.ip_list import SIO_IP_List 25 | from scaleiopy.api.scaleio.mapping.link import SIO_Link 26 | from scaleiopy.api.scaleio.mapping.snapshotspecification import SIO_SnapshotSpecification 27 | from scaleiopy.api.scaleio.mapping.vtree import SIO_Vtree 28 | from scaleiopy.api.scaleio.mapping.statistics import SIO_Statistics 29 | 30 | from scaleiopy.api.scaleio.metering.statistics import Statistics 31 | from scaleiopy.api.scaleio.common.connection import Connection 32 | from scaleiopy.api.scaleio.provisioning.volume import Volume 33 | from scaleiopy.api.scaleio.cluster.cluster import Cluster 34 | from scaleiopy.api.scaleio.cluster.storagepool import StoragePool 35 | from scaleiopy.api.scaleio.cluster.protectiondomain import ProtectionDomain 36 | from scaleiopy.api.scaleio.cluster.faultset import FaultSet 37 | 38 | 39 | # How to remove this one. Let Requests inherit from this class??? 40 | class TLS1Adapter(HTTPAdapter): 41 | """ 42 | A custom HTTP adapter we mount to the session to force the use of TLSv1, which is the only thing supported by 43 | the gateway. Python 2.x tries to establish SSLv2/3 first which failed. 44 | """ 45 | def init_poolmanager(self, connections, maxsize, block=False): 46 | self.poolmanager = PoolManager(num_pools=connections, 47 | maxsize=maxsize, 48 | block=block, 49 | ssl_version=ssl.PROTOCOL_TLSv1) 50 | 51 | 52 | class ScaleIO(SIO_Generic_Object): 53 | """ 54 | The ScaleIO class provides a pythonic way to interact with a ScaleIO cluster 55 | Depends: Working ScaleIO cluster and configured API gateway 56 | 57 | Provides: 58 | * API Login 59 | * Create/Delete Volume 60 | * Map/Unmap Volume to host 61 | * GET storagepools, systemobject, protectiondomains, sdc, sdc, volumes 62 | 63 | """ 64 | def __init__(self, api_url, username, password, verify_ssl=False, debugLevel=None): 65 | """ 66 | Initializes the class 67 | 68 | :param api_url: Base URL for the API. Often the MDM host. 69 | :type api_url: str 70 | :param username: Username to login with 71 | :type username: str 72 | :param password: Password 73 | :type password: str 74 | :return: A ScaleIO object 75 | :rtype: ScaleIO Object 76 | """ 77 | 78 | logging.basicConfig(format='%(asctime)s: %(levelname)s %(module)s:%(funcName)s | %(message)s', 79 | level=self._get_log_level(debugLevel)) 80 | self.logger = logging.getLogger(__name__) 81 | self.logger.debug("Logger initialized!") 82 | 83 | 84 | # Feature group init 85 | 86 | # API -> Connection 87 | self.connection = Connection(self, api_url, username, password, verify_ssl=False, debugLevel=None) 88 | self.connection._check_login() # Login. Otherwise login is called upon first API operation 89 | # API -> Statistics 90 | self.statistics = Statistics(self) 91 | self.provisioning = Volume(self) 92 | # API -> cluster level 93 | self.cluster = Cluster(self) 94 | self.cluster_sp = StoragePool(self) 95 | self.cluster_pd = ProtectionDomain(self) 96 | self.cluster_sds = Sds(self) 97 | self.cluster_sdc = Sdc(self) 98 | 99 | self.faultset = FaultSet(self) 100 | 101 | 102 | @staticmethod 103 | def _get_log_level(level): 104 | """ 105 | small static method to get logging level 106 | :param str level: string of the level e.g. "INFO" 107 | :returns logging.: appropriate debug level 108 | """ 109 | # default to DEBUG 110 | if level is None or level == "DEBUG": 111 | return logging.DEBUG 112 | 113 | level = level.upper() 114 | # Make debugging configurable 115 | if level == "INFO": 116 | return logging.INFO 117 | elif level == "WARNING": 118 | return logging.WARNING 119 | elif level == "CRITICAL": 120 | return logging.CRITICAL 121 | elif level == "ERROR": 122 | return logging.ERROR 123 | elif level == "FATAL": 124 | return logging.FATAL 125 | else: 126 | raise Exception("UnknownLogLevelException: enter a valid log level") 127 | 128 | # Common properties that interact with API 129 | @property 130 | def system(self): 131 | """ 132 | Returns a `list` of all the `System` objects to the cluster. Updates every time - no caching. 133 | :return: a `list` of all the `System` objects known to the cluster. 134 | :rtype: list 135 | """ 136 | self.connection._check_login() 137 | response = self.connection._do_get("{}/{}".format(self.connection._api_url, "types/System/instances")).json() 138 | all_system_objects = [] 139 | for system_object in response: 140 | all_system_objects.append(SIO_System.from_dict(system_object)) 141 | return all_system_objects 142 | 143 | @property 144 | def storage_pools(self): 145 | """ 146 | Returns a `list` of all the `System` objects to the cluster. Updates every time - no caching. 147 | :return: a `list` of all the `System` objects known to the cluster. 148 | :rtype: list 149 | """ 150 | self.connection._check_login() 151 | response = self.connection._do_get("{}/{}".format(self.connection._api_url, "types/StoragePool/instances")).json() 152 | all_storage_pools = [] 153 | for storage_pool_object in response: 154 | all_storage_pools.append(SIO_Storage_Pool.from_dict(storage_pool_object)) 155 | return all_storage_pools 156 | 157 | @property 158 | def sdc(self): 159 | """ 160 | Returns a `list` of all the `ScaleIO_SDC` known to the cluster. Updates every time - no caching. 161 | :return: a `list` of all the `ScaleIO_SDC` known to the cluster. 162 | :rtype: list 163 | """ 164 | self.connection._check_login() 165 | response = self.connection._do_get("{}/{}".format(self.connection._api_url, "types/Sdc/instances")).json() 166 | all_sdc = [] 167 | for sdc in response: 168 | all_sdc.append( 169 | SIO_SDC.from_dict(sdc) 170 | ) 171 | return all_sdc 172 | 173 | @property 174 | def sds(self): 175 | """ 176 | Returns a `list` of all the `ScaleIO_SDS` known to the cluster. Updates every time - no caching. 177 | :return: a `list` of all the `ScaleIO_SDS` known to the cluster. 178 | :rtype: list 179 | """ 180 | self.connection._check_login() 181 | response = self.connection._do_get("{}/{}".format(self.connection._api_url,"types/Sds/instances")).json() 182 | all_sds = [] 183 | for sds in response: 184 | all_sds.append( 185 | SIO_SDS.from_dict(sds) 186 | ) 187 | return all_sds 188 | 189 | @property 190 | def volumes(self): 191 | """ 192 | Returns a `list` of all the `Volume` known to the cluster. Updates every time - no caching. 193 | :return: a `list` of all the `Volume` known to the cluster. 194 | :rtype: list 195 | """ 196 | self.connection._check_login() 197 | response = self.connection._do_get("{}/{}".format(self.connection._api_url, "types/Volume/instances")).json() 198 | all_volumes = [] 199 | for volume in response: 200 | all_volumes.append( 201 | SIO_Volume.from_dict(volume) 202 | ) 203 | return all_volumes 204 | 205 | @property 206 | def snapshots(self): 207 | """ 208 | Get all Volumes of type Snapshot. Updates every time - no caching. 209 | :return: a `list` of all the `ScaleIO_Volume` that have a are of type Snapshot. 210 | :rtype: list 211 | """ 212 | self.connection._check_login() 213 | response = self.connection._do_get("{}/{}".format(self.connection._api_url, "types/Volume/instances")).json() 214 | all_volumes_snapshot = [] 215 | for volume in response: 216 | if volume['volumeType'] == 'Snapshot': 217 | all_volumes_snapshot.append( 218 | Volume.from_dict(volume) 219 | ) 220 | return all_volumes_snapshot 221 | 222 | @property 223 | def protection_domains(self): 224 | """ 225 | :rtype: list of Protection Domains 226 | """ 227 | self.connection._check_login() 228 | response = self.connection._do_get("{}/{}".format(self.connection._api_url, "types/ProtectionDomain/instances")).json() 229 | all_pds = [] 230 | for pd in response: 231 | all_pds.append( 232 | SIO_Protection_Domain.from_dict(pd) 233 | ) 234 | return all_pds 235 | 236 | @property 237 | def fault_sets(self): 238 | """ 239 | You can only create and configure Fault Sets before adding SDSs to the system, and configuring them incorrectly 240 | may prevent the creation of volumes. An SDS can only be added to a Fault Set during the creation of the SDS. 241 | :rtype: list of Faultset objects 242 | """ 243 | self.connection._check_login() 244 | response = self.connection._do_get("{}/{}".format(self.connection._api_url, "types/FaultSet/instances")).json() 245 | all_faultsets = [] 246 | for fs in response: 247 | all_faultsets.append( 248 | SIO_Fault_Set.from_dict(fs) 249 | ) 250 | return all_faultsets 251 | 252 | @property 253 | def vtrees(self): 254 | """ 255 | Get list of VTrees from ScaleIO cluster 256 | :return: List of VTree objects - Can be empty of no VTrees exist 257 | :rtype: VTree object 258 | """ 259 | self.connection._check_login() 260 | response = self.connection._do_get("{}/{}".format(self.connection._api_url, "types/VTree/instances")).json() 261 | all_vtrees = [] 262 | for vtree in response: 263 | all_vtrees.append( 264 | SIO_Vtree.from_dict(vtree) 265 | ) 266 | return all_vtrees 267 | 268 | 269 | def get_system_objects(self): 270 | return self.system 271 | 272 | def get_system_id(self): 273 | return self.system[0].id 274 | 275 | 276 | def is_ip_addr(self, ipstr): 277 | """ 278 | Convenience method to verify if string is an IP addr? 279 | :param ipstr: Stinrg containing IP address 280 | :rtype True if string is a valid IP address 281 | """ 282 | ipstr_chunks = ipstr.split('.') 283 | if len(ipstr_chunks) != 4: 284 | return False 285 | for ipstr_chunk in ipstr_chunks: 286 | if not ipstr_chunk.isdigit(): 287 | return False 288 | ipno_part = int(ipstr_chunk) 289 | if ipno_part < 0 or ipno_part > 255: 290 | return False 291 | return True 292 | ''' 293 | *************************** 294 | *** DEPRECEATED METHODS *** 295 | *************************** 296 | ''' 297 | 298 | # Connectoin related methods 299 | def login(self): 300 | return self.connection.login() 301 | 302 | def _check_login(self): 303 | return self.connection._check_login() 304 | 305 | def get_api_version(self): 306 | return self.connection.get_api_version() 307 | 308 | def _do_get(self, url, **kwargs): 309 | return self.connection._do_get(url, kwargs) 310 | 311 | def _do_post(self, url, **kwargs): 312 | return self.connection._do_post(url, kwargs) 313 | 314 | 315 | # Volume related methods 316 | def create_volume(self, volName, volSizeInMb, pdObj, spObj, thinProvision=True, **kwargs): 317 | return self.provisioning.create_volume(volName, volSizeInMb, pdObj, spObj, thinProvision, kwargs) 318 | 319 | def delete_volume(self, volObj, removeMode='ONLY_ME', **kwargs): 320 | return self.provisioning.delete_volume(volObj, removeMode, kwargs) 321 | 322 | def map_volume_to_sdc(self, volumeObj, sdcObj=None, allowMultipleMappings=False, **kwargs): 323 | return self.provisioning.map_volume_to_sdc(volumeObj, sdcObj, allowMultipleMappings, kwargs) 324 | 325 | def unmap_volume_from_sdc(self, volObj, sdcObj=None, **kwargs): 326 | return self.provisioning.unmap_volume_from_sdc(volObj, sdcObj, kwargs) 327 | 328 | def get_volumes_for_sdc(self, sdcObj): 329 | return self.provisioning.get_volumes_for_sdc(sdcObj) 330 | 331 | def get_volume_by_id(self, id): 332 | return self.provisioning.get_volume_by_id(id) 333 | 334 | def get_volumes_for_vtree(self, vtreeObj): 335 | return self.provisioning.get_volumes_for_vtree(vtreeObj) 336 | 337 | def get_volume_by_name(self, name): 338 | return self.provisioning.get_volume_by_name(name) 339 | 340 | def resize_volume(self, volumeObj, sizeInGb, bsize=1000): 341 | return self.provisioning.resize_volume(volumeObj, sizeInGb, bsize) 342 | 343 | def create_snapshot(self, systemId, snapshotSpecificationObject): 344 | return self.provisioning.create_snapshot(systemId, snapshotSpecificationObject) 345 | 346 | def delete_snapshot(self, systemId, snapshotGroupId): 347 | return self.provisioning.delete_snapshot(systemId, snapshotGroupId) 348 | 349 | def get_snapshots_by_vol(self, volObj): 350 | return self.provisioning.get_snapshots_by_vol(volObj) 351 | 352 | def get_vtree_by_id(self,id): 353 | return self.provisioning.get_vtree_by_id(id) 354 | 355 | def get_sdc_for_volume(self, volObj): 356 | return self.provisioning.get_sdc_for_volume(volObj) 357 | 358 | # StoragePool related methods 359 | def get_storage_pool_by_name(self, name): 360 | return self.cluster_sp.get_storage_pool_by_name(name) 361 | 362 | def get_storage_pool_by_id(self, id): 363 | return self.cluster_sp.get_storage_pool_by_id(id) 364 | 365 | # ProtectionDomain related methods 366 | def get_pd_by_name(self, name): 367 | return self.cluster_pd.get_pd_by_name(name) 368 | 369 | def get_pd_by_id(self, id): 370 | return self.cluster_pd.get_pd_by_id(id) 371 | 372 | def create_protection_domain(self, pdObj, **kwargs): 373 | return self.cluster_pd.create_protection_domain(pdObj, kwargs) 374 | 375 | def delete_potection_domain(self, pdObj): 376 | return self.cluster_pd.delete_potection_domain(pdObj) 377 | 378 | # FaultSet related methods 379 | def get_faultset_by_id(self, id): 380 | return self.faultset.get_faultset_by_id(id) 381 | 382 | def get_faultset_by_name(self,name): 383 | return self.faultset.get_faultset_by_name(name) 384 | 385 | def set_faultset_name(self, name, fsObj): 386 | return self.faultsetdef.set_faultset_name(name, fsObj) 387 | 388 | # SDS related methods 389 | def set_sds_name(self, name, sdsObj): 390 | return self.cluster_sds.set_sds_name(name, sdsObj) 391 | 392 | def unregisterSds(self, sdsObj): 393 | return self.cluster_sds.unregisterSds(sdsObj) 394 | 395 | def registerSds(self, sdsObj, **kwargs): 396 | return self.cluster_sds.registerSds(sdsObj, kwargs) 397 | 398 | def get_sds_in_faultset(self, faultSetObj): 399 | return self.cluster_sds.get_sds_in_faultset(faultSetObj) 400 | 401 | def get_sds_by_name(self,name): 402 | return self.cluster_sds.get_sds_by_name(name) 403 | 404 | def get_sds_by_ip(self,ip): 405 | return self.cluster_sds.get_sds_by_ip(ip) 406 | 407 | def get_sds_by_id(self,id): 408 | return self.cluster_sds.get_sds_by_id(id) 409 | 410 | # SDC related methods 411 | def set_sdc_name(self, name, sdcObj): 412 | return self.cluster_sdc.set_sdc_name(name, sdcObj) 413 | 414 | def get_sdc_by_name(self, name): 415 | return self.cluster_sdc.get_sdc_by_name(name) 416 | 417 | def get_sdc_by_id(self, id): 418 | return self.cluster_sdc.get_sdc_by_id(id) 419 | 420 | def get_sdc_by_guid(self, guid): 421 | return self.cluster_sdc.get_sdc_by_guid(guid) 422 | 423 | def get_sdc_by_ip(self, ip): 424 | return self.cluster_sdc.get_sdc_by_ip(ip) 425 | 426 | def unregisterSdc(self, sdcObj): 427 | return self.cluster_sdc.unregisterSdc(sdcObj) 428 | 429 | def registerSdc(self, sdcObj, **kwargs): 430 | return self.cluster_sdc.registerSdc(sdcObj, kwargs) 431 | 432 | 433 | if __name__ == "__main__": 434 | if len(sys.argv) == 1: 435 | print "Usage: scaleio.py mdm_ip user pass" 436 | else: 437 | sio = ScaleIO("https://" + sys.argv[1] + "/api",sys.argv[2],sys.argv[3],False,"DEBUG") # HTTPS must be used as there seem to be an issue with 302 responses in Requests when using POST 438 | pprint(sio.system) 439 | pprint(sio.sdc) 440 | pprint(sio.sds) 441 | pprint(sio.volumes) 442 | pprint(sio.protection_domains) 443 | pprint(sio.storage_pools) 444 | --------------------------------------------------------------------------------