1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-22 22:03:39 +03:00

F #2258: Introduce python bindings - pyone

Ported from standalone python addon
https://github.com/OpenNebula/addon-pyone
This commit is contained in:
Jan Orel 2018-12-12 13:03:17 +01:00 committed by Ruben S. Montero
parent 79f17288b9
commit 8c568708e8
12 changed files with 1081 additions and 0 deletions

5
.gitignore vendored
View File

@ -22,6 +22,11 @@ src/oca/java/share/doc
src/oca/java/java-oca*.tar.gz
src/vmm_mad/remotes/lxd/tests/
src/oca/python/pyone/bindings
src/oca/python/build/
src/oca/python/dist/
src/oca/python/opennebula.egg-info/
src/oca/python/doc/
src/docker_machine/pkg
src/docker_machine/src/docker_machine/bin/docker-machine-driver-opennebula

37
share/doc/xsd/index.xsd Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
targetNamespace="http://opennebula.org/XMLSchema" xmlns="http://opennebula.org/XMLSchema">
<xs:include schemaLocation="acct.xsd"/>
<xs:include schemaLocation="cluster_pool.xsd"/>
<xs:include schemaLocation="cluster.xsd"/>
<xs:include schemaLocation="datastore_pool.xsd"/>
<xs:include schemaLocation="datastore.xsd"/>
<xs:include schemaLocation="group_pool.xsd"/>
<xs:include schemaLocation="group.xsd"/>
<xs:include schemaLocation="host_pool.xsd"/>
<xs:include schemaLocation="host.xsd"/>
<xs:include schemaLocation="image_pool.xsd"/>
<xs:include schemaLocation="image.xsd"/>
<xs:include schemaLocation="marketplaceapp_pool.xsd"/>
<xs:include schemaLocation="marketplaceapp.xsd"/>
<xs:include schemaLocation="marketplace_pool.xsd"/>
<xs:include schemaLocation="marketplace.xsd"/>
<xs:include schemaLocation="user_pool.xsd"/>
<xs:include schemaLocation="user.xsd"/>
<xs:include schemaLocation="vdc_pool.xsd"/>
<xs:include schemaLocation="vdc.xsd"/>
<xs:include schemaLocation="vm_group_pool.xsd"/>
<xs:include schemaLocation="vm_group.xsd"/>
<xs:include schemaLocation="vm_pool.xsd"/>
<xs:include schemaLocation="vmtemplate_pool.xsd"/>
<xs:include schemaLocation="vmtemplate.xsd"/>
<xs:include schemaLocation="vm.xsd"/>
<xs:include schemaLocation="vnet_pool.xsd"/>
<xs:include schemaLocation="vnet.xsd"/>
<xs:include schemaLocation="vntemplate_pool.xsd"/>
<xs:include schemaLocation="vntemplate.xsd"/>
<xs:include schemaLocation="vrouter_pool.xsd"/>
<xs:include schemaLocation="vrouter.xsd"/>
<xs:include schemaLocation="zone_pool.xsd"/>
<xs:include schemaLocation="zone.xsd"/>
</xs:schema>

201
src/oca/python/LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2018 www.privaz.io Valletech AB
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

50
src/oca/python/Makefile Normal file
View File

@ -0,0 +1,50 @@
# Copyright 2018 www.privaz.io Valletech AB
# Copyright 2002-2018, OpenNebula Project, OpenNebula Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Use full path to ensure virtualenv compatibility
PYTHON = $(shell which python)
GDS = $(shell which generateDS)
PWD = $(shell pwd)
schemas = index.xsd
VPATH = src: ../../../share/doc/xsd
ifneq ("${ROOT}","")
root="--root=${ROOT}"
endif
all: pyone/bindings/__init__.py pyone/bindings/supbind.py
pyone/bindings/__init__.py pyone/bindings/supbind.py: $(schemas)
mkdir -p pyone/bindings
${PYTHON} ${GDS} -q -f -o pyone/bindings/supbind.py -s pyone/bindings/__init__.py --super=supbind --external-encoding=utf-8 --silence $^
sed -i "s/import supbind/from . import supbind/" pyone/bindings/__init__.py
sed -i "s/import sys/import sys\nfrom pyone.util import TemplatedType/" pyone/bindings/__init__.py
sed -i "s/(supermod\./(TemplatedType, supermod\./g" pyone/bindings/__init__.py
.PHONY: clean
clean:
rm -rf build dist pyone/bindings *.egg-info doc
dist:
${PYTHON} setup.py sdist bdist_wheel
install:
${PYTHON} setup.py install ${root}
doc:
mkdir -p doc
PYTHONPATH=$(PWD) pdoc --overwrite --html --html-no-source --html-dir doc pyone

View File

@ -0,0 +1,7 @@
OpenNebula Python Bindings
==================================
Description
-----------
PyOne is an implementation of Open Nebula XML-RPC bindings in Python. It has been integrated into upstream OpenNebula release cycles from `here <https://github.com/OpenNebula/addon-pyone>`_.

View File

@ -0,0 +1,255 @@
# Copyright 2018 www.privaz.io Valletech AB
# Copyright 2002-2018, OpenNebula Project, OpenNebula Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyone import bindings
from six import string_types
import xmlrpc.client
import socket
from .util import cast2one
#
# 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
#
# 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)
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)
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)
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
RUNNING
MIGRATE
SAVE_STOP
SAVE_SUSPEND
SAVE_MIGRATE
PROLOG_MIGRATE
PROLOG_RESUME
EPILOG_STOP
EPILOG
SHUTDOWN
CANCEL
FAILURE
CLEANUP_RESUBMIT
UNKNOWN
HOTPLUG
SHUTDOWN_POWEROFF
BOOT_UNKNOWN
BOOT_POWEROFF
BOOT_SUSPENDED
BOOT_STOPPED
CLEANUP_DELETE
HOTPLUG_SNAPSHOT
HOTPLUG_NIC
HOTPLUG_SAVEAS
HOTPLUG_SAVEAS_POWEROFF
HOTPLUG_SAVEAS_SUSPENDED
SHUTDOWN_UNDEPLOY
EPILOG_UNDEPLOY
PROLOG_UNDEPLOY
BOOT_UNDEPLOY
HOTPLUG_PROLOG_POWEROFF
HOTPLUG_EPILOG_POWEROFF
BOOT_MIGRATE
BOOT_FAILURE
BOOT_MIGRATE_FAILURE
PROLOG_MIGRATE_FAILURE
PROLOG_FAILURE
EPILOG_FAILURE
EPILOG_STOP_FAILURE
EPILOG_UNDEPLOY_FAILURE
PROLOG_MIGRATE_POWEROFF
PROLOG_MIGRATE_POWEROFF_FAILURE
PROLOG_MIGRATE_SUSPEND
PROLOG_MIGRATE_SUSPEND_FAILURE
BOOT_UNDEPLOY_FAILURE
BOOT_STOPPED_FAILURE
PROLOG_RESUME_FAILURE
PROLOG_UNDEPLOY_FAILURE
DISK_SNAPSHOT_POWEROFF
DISK_SNAPSHOT_REVERT_POWEROFF
DISK_SNAPSHOT_DELETE_POWEROFF
DISK_SNAPSHOT_SUSPENDED
DISK_SNAPSHOT_REVERT_SUSPENDED
DISK_SNAPSHOT_DELETE_SUSPENDED
DISK_SNAPSHOT
DISK_SNAPSHOT_REVERT
DISK_SNAPSHOT_DELETE
PROLOG_MIGRATE_UNKNOWN
PROLOG_MIGRATE_UNKNOWN_FAILURE
DISK_RESIZE
DISK_RESIZE_POWEROFF
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)
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)
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
Slightly tuned ServerProxy
"""
def __init__(self, uri, session, timeout=None, **options):
"""
Override the constructor to take the authentication or session
Will also configure the socket timeout
:param uri: OpenNebula endpoint
:param session: OpenNebula authentication session
:param timeout: Socket timetout
:param options: additional options for ServerProxy
"""
self.__session = session
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)
#
def _ServerProxy__request(self, methodname, params):
"""
Override/patch the (private) request method to:
- structured parameters will be casted to attribute=value or XML
- automatically prefix all methodnames with "one."
- automatically add the authentication info as first parameter
- process the response
:param methodname: XMLRPC method name
:param params: XMLRPC parameters
:return: opennebula object or XMLRPC returned value
"""
# check if this is a helper or a XMLPRC method call
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)
def _do_request(self, method, params):
try:
return xmlrpc.client.ServerProxy._ServerProxy__request(self, method, params)
except xmlrpc.client.Fault as e:
raise OneException(str(e))
def _cast_parms(self,params):
"""
cast parameters, make them one-friendly
:param params:
:return:
"""
lparams = list(params)
for i, param in enumerate(lparams):
lparams[i] = cast2one(param)
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]
if sucess:
ret = rawResponse[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)
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'''
return 1
def server_close(self):
pass

View File

@ -0,0 +1,89 @@
# Copyright 2018 www.privaz.io Valletech AB
# Copyright 2002-2018, OpenNebula Project, OpenNebula Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import OneException
from . import MARKETPLACEAPP_STATES, MARKETPLACEAPP_TYPES
from base64 import b64decode
class OneHelperException(OneException):
pass
# Helpers implementation
# This methods are non primitive XMLRPC methods, they can be implemented as a
# series of calls to primitive XMLRPC methods, however they are called in the
# exact same way as XMLRPC method
# They are implemented as standalone functions that take the XMLRPC server as first parameter
# they need to be registered in the XMLRPC server constructor
def marketapp_export(one, appid, dsid=None, name=None, vmtemplate_name=None):
'''
Exports a market app to a suitable OpenNebula object
:param one: the XMLRPC server
:param appid: id of the marketplace app
:param dsid: id of the datastore to save images, if not provided the datastore named "default" will be used.
:param name: name of the new object, if not provided the same name as the App will be used
:param vmtemplate_name: name for the VM Template, if the app has one.
:return: a dictionary with the ID of the new Image as image and the ID of the new associated template as vmtemplate. If no template has been defined, it will return -1.
'''
ret= {
"image": -1,
"vmtemplate": -1
}
# find out the datastore
if not dsid:
datastores = one.datastorepool.info()
for ds in datastores.DATASTORE:
if ds.NAME == "default":
dsid = ds.ID
break
if not dsid:
raise OneHelperException("Datastore was not provided and could not find the defaultone")
# get the application
app = one.marketapp.info(appid)
if app.STATE != MARKETPLACEAPP_STATES.READY:
raise OneHelperException("Application is not in READY state")
if app.TYPE == MARKETPLACEAPP_TYPES.IMAGE:
if app.APPTEMPLATE64:
templ=b64decode(app.APPTEMPLATE64).decode()
else:
templ=""
if not name:
name = app.NAME
templ+='''\nNAME="%s"\nFROM_APP="%d"''' % (name,app.ID)
ret['image'] = one.image.allocate(templ,dsid)
if 'VMTEMPLATE64' in app.TEMPLATE:
vmtempl = b64decode(app.TEMPLATE['VMTEMPLATE64']).decode()
if not vmtemplate_name:
vmtemplate_name = app.NAME
vmtempl += '''\nNAME="%s"\nDISK=[ IMAGE_ID = %d ]''' % (vmtemplate_name, ret['image'])
ret['vmtemplate'] = one.template.allocate(vmtempl)
else:
raise OneHelperException('App type %s not supported' % MARKETPLACEAPP_TYPES(app.TYPE).name)
return ret

View File

@ -0,0 +1,25 @@
# Copyright 2018 www.privaz.io Valletech AB
# Copyright 2002-2018, OpenNebula Project, OpenNebula Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from os import environ
# check if test stub featuers are enabled
_test_fixture = (environ.get("PYONE_TEST_FIXTURE", "False").lower() in ["1", "yes", "true"])
if _test_fixture is False:
from . import OneServer
else:
from pyone.tester import OneServerTester as OneServer

View File

@ -0,0 +1,201 @@
# Copyright 2018 www.privaz.io Valletech AB
# Copyright 2002-2018, OpenNebula Project, OpenNebula Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from hashlib import md5
from json import load, dumps as json_dumps
from base64 import b64decode, b64encode
from pickle import dumps, loads
from os import path
from . import OneServer
from tblib import pickling_support
from sys import exc_info
from six import reraise
from collections import OrderedDict
from gzip import open
from os import environ
pickling_support.install()
def read_fixture_file(fixture_file):
f = open(fixture_file, "rt")
ret = load(f)
f.close()
return ret
def write_fixture_file(fixture_file, obj):
f = open(fixture_file, "wb")
f.write(json_dumps(obj).encode())
f.close()
class OneServerTester(OneServer):
'''
This class extends the OneServer to facilitate unit testing
The idea is to be able to "record" fixtures while testing with a live OpenNebula platform.
Those recordings can later be use in "replay" mode to simulate an OpenNebula platform.
XMLAPI method calls are recorded as test_base/unit_test/method_signature_instance
The signature is generated as the md5 of the parameters
if several calls with the same signature are doing during the same unit test, instance is incremented.
The order in which calls happen within the same unit_test must be deterministic.
'''
def __init__(self, uri, session, fixture_file=None, fixture_unit=None, timeout=None, fixture_replay=None, **options):
# Environment driven initialization required for capturing fixtures during ansible integration tests
if fixture_file is None:
fixture_file = environ.get("PYONE_TEST_FIXTURE_FILE", None)
if fixture_replay is None:
fixture_replay = (environ.get("PYONE_TEST_FIXTURE_REPLAY", "True").lower() in ["1", "yes", "true"])
if fixture_unit is None:
fixture_unit = environ.get("PYONE_TEST_FIXTURE_UNIT", "init")
if path.isfile(fixture_file):
self._fixtures = read_fixture_file(fixture_file)
else:
self._fixtures = dict()
# all members involved in the fixtures must be predefined or
# the magic getter method will trigger resulting in stack overflows
self._fixture_replay = fixture_replay
self._fixture_file = fixture_file
self.set_fixture_unit_test(fixture_unit)
OneServer.__init__(self, uri, session, timeout, **options)
def set_fixture_unit_test(self,name):
"""
Set the name of the unit test. creates an entry in the fixture tree if it does not exist.
:param name:
:return:
"""
if not name in self._fixtures:
self._fixtures[name] = dict()
self._fixture_unit_test = self._fixtures[name]
def _fixture_signature(self, methodname, params):
signature_md5 = md5()
sparms=json_dumps(params,sort_keys=True).encode()
signature_md5.update(sparms)
return signature_md5.hexdigest()
def _get_fixture(self, methodname, params):
'''
returns the next fixture for a given call.
:param methodname: XMlRPC method
:param params: the paramters passed
:return: file name were to store to or read from the fixture data
'''
ret = self._fixture_unit_test[methodname][self._fixture_signature(methodname,params)].pop(0)
if not ret:
raise Exception("Could not read fixture, if the tests changed you must re-record fixtures")
return ret
def _set_fixture(self,methodname,params,object):
'''
Will create the fixture for a given call
:param methodname:
:param params:
:return:
'''
signature = self._fixture_signature(methodname,params)
if not methodname in self._fixture_unit_test:
self._fixture_unit_test[methodname]=dict()
if not signature in self._fixture_unit_test[methodname]:
self._fixture_unit_test[methodname][signature]=[]
self._fixture_unit_test[methodname][signature].append(object)
def _do_request(self,method,params):
'''
Intercepts requests.
In record mode they are recorded before being returned.
In replay mode they are read from fixtures instead
Exceptions are also captured and reraised
'''
ret = None
if self._fixture_replay:
ret = self._get_fixture(method,params)
if 'exception' in ret:
reraise(*loads(b64decode(ret['exception'])))
else:
try:
ret = OneServer._do_request(self, method, params)
except Exception as exception:
ret = {
"exception": b64encode(dumps(exc_info(), 2)).decode(),
}
raise exception
finally:
self._set_fixture(method,params,ret)
return ret
def _cast_parms(self,params):
"""
Parameters will be used to generate the signature of the method, an md5.
So we need signatures to be deterministic. There are two sources of randomness
- Python version, in particular differences in dealing with encodings
- Unsorted sets.
This method will add casting transformations to fix those, only required for testing.
:param params:
:return: a list of parameters that should generate a deterministic md5 signature.
"""
lparams = list(params)
for i, param in enumerate(lparams):
if isinstance(param, dict):
lparams[i] = self._to_ordered_dict(param)
return OneServer._cast_parms(self, lparams)
def _to_ordered_dict(self, param):
"""
deep orders a dictionary
:param param:
:return:
"""
if isinstance(param,dict):
# ensure the dictionary is ordered
param = OrderedDict(sorted(param.items()))
# recurse
for key, value in param.items():
if isinstance(value, dict):
param[key] = self._to_ordered_dict(value)
return param
def server_retry_interval(self):
return 0.01
def server_close(self):
"""
Unpdates the fixture data if we are recording.
:return:
"""
if not self._fixture_replay:
write_fixture_file(self._fixture_file, self._fixtures)

View File

@ -0,0 +1,125 @@
# Copyright 2018 www.privaz.io Valletech AB
# Copyright 2002-2018, OpenNebula Project, OpenNebula Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import dicttoxml
import xmltodict
from lxml.etree import tostring
from collections import OrderedDict
from aenum import IntEnum
def cast2one(param):
'''
This function will cast parameters to make them nebula friendly
flat dictionaries will be turned into attribute=value vectors
dictionaries with root dictionary will be serialized as XML
Structures will be turned into strings before being submitted.
:param param: the parameter to make nebula friendly
:return: casted parameter
'''
if isinstance(param, IntEnum):
# if the param is a constant we return its value
return param.value
if isinstance(param, dict):
# if this is a structured type
# in case we passed a dictionary that is part of another
if hasattr(param, '_root'):
param = param._root
# if the dictionary is not empty
if bool(param):
root = list(param.values())[0]
if isinstance(root, dict):
# We return this dictionary as XML
return dicttoxml.dicttoxml(param, root=False, attr_type=False).decode('utf8')
else:
# We return this dictionary as attribute=value vector
ret = u""
for (k, v) in param.items():
ret = u'''%s%s="%s"\n''' % (ret, k, v)
return ret
else:
raise Exception("Cannot cast empty dictionary")
else:
return param
def one2dict(element):
'''
This function returns a dictionary from an anyType binding element
The dictionary can then be used later as imput for an udpate
This is now deprecated, included for backward compatibility.
:param element: anyType element to be converted such as TEMLATE or USER_TEMPLATE
:return: a dictionary representing the element
'''
return element._root
def child2dict(element):
'''
Creates a dictionary from the documentTree obtained from a binding Element.
:param element:
:return:
'''
xml = tostring(element)
ret = xmltodict.parse(xml)
# get the tag name and remove the ns attribute if present
if "}" in element.tag:
tagName = element.tag.split('}')[1]
del ret[tagName]['@xmlns']
else:
tagName = element.tag
# Reemplace no-dictionary with empty dictionary
if ret[tagName] == None:
ret[tagName] = OrderedDict()
# return the contents dictionary, but save a reference
ret[tagName]._root = ret
return ret[tagName]
def build_template_node(obj,nodeName,child):
'''
Utility function to build an anyType element that can be accessed as a dictionary
:param obj:
:param nodeName:
:param child:
:return:
'''
if nodeName == "TEMPLATE":
obj.TEMPLATE = child2dict(child)
return True
elif nodeName == "USER_TEMPLATE":
obj.USER_TEMPLATE = child2dict(child)
return True
else:
return False
class TemplatedType(object):
'''
Mixin class for Templated bindings
'''
def buildChildren(self, child_, node, nodeName_, fromsubclass_=False):
if not build_template_node(self, nodeName_, child_):
super(TemplatedType, self).buildChildren(child_,node,nodeName_,fromsubclass_)

76
src/oca/python/setup.py Normal file
View File

@ -0,0 +1,76 @@
# Copyright 2018 www.privaz.io Valletech AB
# Copyright 2002-2018, OpenNebula Project, OpenNebula Systems
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Always prefer setuptools over distutils
from setuptools import setup, find_packages
# To use a consistent encoding
from codecs import open
from os import path
import sys
here = path.abspath(path.dirname(__file__))
# Get the long description from the README file
with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
long_description = f.read()
install_requires=[
'lxml',
'dicttoxml',
'xmltodict',
'six',
'aenum',
'tblib'
]
# include future in python2
if sys.version_info[0] < 3:
install_requires.append('future')
setup(
name='opennebula',
version='1.1.9',
description='Python Bindings for OpenNebula XML-RPC API',
long_description=long_description,
# The project's main homepage.
url='http://opennebula.org',
# Author details
author='Rafael del Valle',
author_email='rvalle@privaz.io',
# Choose your license
license='http://www.apache.org/licenses/LICENSE-2.0',
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Topic :: Software Development :: Build Tools',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6'
],
keywords='cloud opennebula xmlrpc bindings',
packages=find_packages(),
install_requires=install_requires,
extras_require={
'dev': ['check-manifest'],
'test': ['coverage'],
},
test_suite="tests"
)

10
src/oca/python/upload.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
pip install twine
# Read https://github.com/pypa/twine#keyring-support for authentication setup for $TWINE_USERNAME
export TWINE_USERNAME=opennebula
export TWINE_REPOSITORY_URL="https://test.pypi.org/legacy/" # Test repo until the process is fully automated
twine upload dist/*