diff --git a/src/oca/python/pyone/__init__.py b/src/oca/python/pyone/__init__.py index 75ac5215d5..2fe9dcdb09 100644 --- a/src/oca/python/pyone/__init__.py +++ b/src/oca/python/pyone/__init__.py @@ -12,31 +12,48 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +''' +PyONE is an implementation of Open Nebula XML-RPC bindings. +''' -from pyone import bindings -from six import string_types import xmlrpc.client import socket +import requests +import requests.utils +from six import string_types +from aenum import IntEnum +from pyone import bindings from .util import cast2one -from .acl import OneAcl + # # Exceptions as defined in the XML-API reference # - class OneException(Exception): pass + + class OneAuthenticationException(OneException): pass + + class OneAuthorizationException(OneException): pass + + class OneNoExistsException(OneException): pass + + class OneActionException(OneException): pass + + class OneApiException(OneException): pass + + class OneInternalException(OneException): pass @@ -44,22 +61,34 @@ class OneInternalException(OneException): # Constants, naming follows those in Open Nebula Ruby API # -from aenum import IntEnum -DATASTORE_TYPES = IntEnum('DATASTORE_TYPES','IMAGE SYSTEM FILE',start=0) -DATASTORE_STATES = IntEnum('DATASTORE_STATES','READY DISABLED',start=0) +DATASTORE_TYPES = IntEnum('DATASTORE_TYPES', 'IMAGE SYSTEM FILE', start=0) +DATASTORE_STATES = IntEnum('DATASTORE_STATES', 'READY DISABLED', start=0) -DISK_TYPES = IntEnum('DISK_TYPES','FILE CD_ROM BLOCK RBD',start=0) +DISK_TYPES = IntEnum('DISK_TYPES', 'FILE CD_ROM BLOCK RBD', start=0) -HISTORY_ACTION = IntEnum('HISTORY_ACTION','none migrate live-migrate shutdown shutdown-hard undeploy undeploy-hard hold release stop suspend resume boot delete delete-recreate reboot reboot-hard resched unresched poweroff poweroff-hard disk-attach disk-detach nic-attach nic-detach disk-snapshot-create disk-snapshot-delete terminate terminate-hard disk-resize deploy chown chmod updateconf rename resize update snapshot-resize snapshot-delete snapshot-revert disk-saveas disk-snapshot-revert recover retry monitor',start=0) +HISTORY_ACTION = IntEnum('HISTORY_ACTION', '''none migrate live-migrate + shutdown shutdown-hard undeploy undeploy-hard hold release stop + suspend resume boot delete delete-recreate reboot reboot-hard resched + unresched poweroff poweroff-hard disk-attach disk-detach nic-attach + nic-detach disk-snapshot-create disk-snapshot-delete terminate + terminate-hard disk-resize deploy chown chmod updateconf rename resize + update snapshot-resize snapshot-delete snapshot-revert disk-saveas + disk-snapshot-revert recover retry monitor''', start=0) -HOST_STATES = IntEnum('HOST_STATES','INIT MONITORING_MONITORED MONITORED ERROR DISABLED MONITORING_ERROR MONITORING_INIT MONITORING_DISABLED OFFLINE', start=0) -HOST_STATUS = IntEnum('HOST_STATUS','ENABLED DISABLED OFFLINE',start=0) +HOST_STATES = IntEnum('HOST_STATES', '''INIT MONITORING_MONITORED MONITORED + ERROR DISABLED MONITORING_ERROR MONITORING_INIT MONITORING_DISABLED + OFFLINE''', start=0) -IMAGE_STATES = IntEnum('IMAGE_STATES','INIT READY USED DISABLED LOCKED ERROR CLONE DELETE USED_PERS LOCKED_USED LOCKED_USED_PERS', start=0) -IMAGE_TYPES = IntEnum('IMAGE_TYPES','OS CDROM DATABLOCK KERNEL RAMDISK CONTEXT', start=0) +HOST_STATUS = IntEnum('HOST_STATUS', 'ENABLED DISABLED OFFLINE', start=0) -LCM_STATE = IntEnum('LCM_STATE',''' +IMAGE_STATES = IntEnum('IMAGE_STATES', '''INIT READY USED DISABLED LOCKED ERROR + CLONE DELETE USED_PERS LOCKED_USED LOCKED_USED_PERS''', start=0) + +IMAGE_TYPES = IntEnum('IMAGE_TYPES', '''OS CDROM DATABLOCK KERNEL RAMDISK + CONTEXT''', start=0) + +LCM_STATE = IntEnum('LCM_STATE', ''' LCM_INIT PROLOG BOOT @@ -124,24 +153,30 @@ LCM_STATE = IntEnum('LCM_STATE',''' PROLOG_MIGRATE_UNKNOWN_FAILURE DISK_RESIZE DISK_RESIZE_POWEROFF - DISK_RESIZE_UNDEPLOYED''',start=0) + DISK_RESIZE_UNDEPLOYED''', start=0) -MARKETPLACEAPP_STATES = IntEnum('MARKETPLACEAPP_STATES', 'INIT READY LOCKED ERROR DISABLED', start=0) -MARKETPLACEAPP_TYPES = IntEnum('MARKETPLACEAPP_TYPES','UNKNOWN IMAGE VMTEMPLATE SERVICE_TEMPLATE', start=0) +MARKETPLACEAPP_STATES = IntEnum('MARKETPLACEAPP_STATES', '''INIT READY LOCKED + ERROR DISABLED''', start=0) +MARKETPLACEAPP_TYPES = IntEnum('MARKETPLACEAPP_TYPES', '''UNKNOWN IMAGE + VMTEMPLATE SERVICE_TEMPLATE''', start=0) -PAGINATED_POOLS = IntEnum('PAGINATED_POOLS','VM_POOL IMAGE_POOL TEMPLATE_POOL VN_POOL DOCUMENT_POOL SECGROUP_POOL',start=0) +PAGINATED_POOLS = IntEnum('PAGINATED_POOLS', '''VM_POOL IMAGE_POOL + TEMPLATE_POOL VN_POOL DOCUMENT_POOL SECGROUP_POOL''', start=0) -REMOVE_VNET_ATTRS = IntEnum('REMOVE_VNET_ATTRS','{AR_ID BRIDGE CLUSTER_ID IP MAC TARGET NIC_ID NETWORK_ID VN_MAD SECURITY_GROUPS VLAN_ID',start=0) +REMOVE_VNET_ATTRS = IntEnum('REMOVE_VNET_ATTRS', '''AR_ID BRIDGE CLUSTER_ID + IP MAC TARGET NIC_ID NETWORK_ID VN_MAD SECURITY_GROUPS VLAN_ID + ''', start=0) -VM_STATE = IntEnum('VM_STATE','INIT PENDING HOLD ACTIVE STOPPED SUSPENDED DONE FAILED POWEROFF UNDEPLOYED CLONING CLONING_FAILURE',start=0) +VM_STATE = IntEnum('VM_STATE', '''INIT PENDING HOLD ACTIVE STOPPED SUSPENDED + DONE FAILED POWEROFF UNDEPLOYED CLONING CLONING_FAILURE''', start=0) # # Import helper methods after definitions they are likely to refer to. # - from .helpers import marketapp_export + class OneServer(xmlrpc.client.ServerProxy): """ XML-RPC OpenNebula Server @@ -162,11 +197,20 @@ class OneServer(xmlrpc.client.ServerProxy): if timeout: # note that this will affect other classes using sockets too. socket.setdefaulttimeout(timeout) + # register helpers: self.__helpers = { "marketapp.export": marketapp_export } - xmlrpc.client.ServerProxy.__init__(self, uri, **options) + + transport = RequestsTransport() + transport.set_https(uri.startswith('https')) + + xmlrpc.client.ServerProxy.__init__( + self, + uri, + transport=transport, + **options) # def _ServerProxy__request(self, methodname, params): @@ -186,17 +230,18 @@ class OneServer(xmlrpc.client.ServerProxy): if methodname in self.__helpers: return self.__helpers[methodname](self, *params) - else: - ret = self._do_request("one."+methodname,self._cast_parms(params)) - return self.__response(ret) + + ret = self._do_request("one."+methodname, self._cast_parms(params)) + return self.__response(ret) def _do_request(self, method, params): try: - return xmlrpc.client.ServerProxy._ServerProxy__request(self, method, params) + return xmlrpc.client.ServerProxy._ServerProxy__request( + self, method, params) except xmlrpc.client.Fault as e: raise OneException(str(e)) - def _cast_parms(self,params): + def _cast_parms(self, params): """ cast parameters, make them one-friendly :param params: @@ -205,52 +250,101 @@ class OneServer(xmlrpc.client.ServerProxy): lparams = list(params) for i, param in enumerate(lparams): lparams[i] = cast2one(param) - params= tuple(lparams) + params = tuple(lparams) # and session a prefix params = (self.__session,) + params return params - - # # Process the response from one XML-RPC server # will throw exceptions for each error condition # will bind returned xml to objects generated from xsd schemas - def __response(self, rawResponse): - sucess = rawResponse[0] - code = rawResponse[2] + def __response(self, raw_response): + sucess = raw_response[0] + code = raw_response[2] if sucess: - ret = rawResponse[1] + ret = raw_response[1] if isinstance(ret, string_types): # detect xml if ret[0] == '<': return bindings.parseString(ret.encode("utf-8")) return ret - else: - message = rawResponse[1] - if code == 0x0100: - raise OneAuthenticationException(message) - elif code == 0x0200: - raise OneAuthorizationException(message) - elif code == 0x0400: - raise OneNoExistsException(message) - elif code == 0x0800: - raise OneActionException(message) - elif code == 0x1000: - raise OneApiException(message) - elif code == 0x2000: - raise OneInternalException(message) - else: - raise OneException(message) + message = raw_response[1] + if code == 0x0100: + raise OneAuthenticationException(message) + if code == 0x0200: + raise OneAuthorizationException(message) + if code == 0x0400: + raise OneNoExistsException(message) + if code == 0x0800: + raise OneActionException(message) + if code == 0x1000: + raise OneApiException(message) + if code == 0x2000: + raise OneInternalException(message) + raise OneException(message) def server_retry_interval(self): - '''returns the recommended wait time between attempts to check if the opennebula platform has - reached a desired state, in seconds''' + '''returns the recommended wait time between attempts to check if + the opennebula platform has reached a desired state, in seconds''' return 1 def server_close(self): pass +class RequestsTransport(xmlrpc.client.Transport): + """ + Drop in Transport for xmlrpclib that uses Requests instead of httplib + """ + user_agent = "Python XMLRPC with Requests (python-requests.org)" + use_https = False + + def set_https(self, https=False): + self.use_https = https + + def request(self, host, handler, request_body, verbose=False): + """ + Make an xmlrpc request. + """ + headers = {'User-Agent': self.user_agent, + 'Content-Type': 'text/xml', + 'Accept': '*/*' + } + + url = self._build_url(host, handler) + + kwargs = {'verify': True} + + resp = requests.post(url, data=request_body, headers=headers, + **kwargs) + try: + resp.raise_for_status() + except requests.RequestException as e: + raise xmlrpc.client.ProtocolError(url, resp.status_code, + str(e), resp.headers) + else: + return self.parse_response(resp) + + def parse_response(self, response): + """ + Parse the xmlrpc response. + """ + p, u = self.getparser() + + p.feed(response.content) + p.close() + + return u.close() + + def _build_url(self, host, handler): + """ + Build a url for our request based on the host, handler and use_http + property + """ + scheme = 'https' if self.use_https else 'http' + handler = handler.lstrip('/') + + return '%s://%s/%s' % (scheme, host, handler) diff --git a/src/oca/python/setup.py b/src/oca/python/setup.py index 1b28be99af..4c69d0c7b0 100644 --- a/src/oca/python/setup.py +++ b/src/oca/python/setup.py @@ -32,7 +32,8 @@ install_requires=[ 'xmltodict', 'six', 'aenum', - 'tblib' + 'tblib', + 'requests' ] # include future in python2