From b202172a30e944705d6e3591707a2928bd90b08e Mon Sep 17 00:00:00 2001 From: kjg Date: Thu, 16 Nov 2023 22:29:06 +0900 Subject: [PATCH] [python-oca] create new repo --- .travis.yml | 8 + AUTHORS | 9 + MANIFEST.in | 5 + README | 1 + README.rst | 42 +++ TODO | 1 + docs/Makefile | 130 +++++++ docs/api.rst | 17 + docs/api/client.rst | 7 + docs/api/group.rst | 10 + docs/api/host.rst | 10 + docs/api/image.rst | 10 + docs/api/template.rst | 10 + docs/api/user.rst | 10 + docs/api/vm.rst | 10 + docs/api/vn.rst | 11 + docs/conf.py | 218 ++++++++++++ docs/index.rst | 23 ++ docs/make.bat | 170 +++++++++ docs/user_guide.rst | 17 + oca/__init__.py | 156 +++++++++ oca/acl.py | 1 + oca/cluster.py | 56 +++ oca/datastore.py | 71 ++++ oca/exceptions.py | 5 + oca/group.py | 62 ++++ oca/host.py | 140 ++++++++ oca/image.py | 241 +++++++++++++ oca/pool.py | 177 ++++++++++ oca/template.py | 112 ++++++ oca/tests/__init__.py | 0 oca/tests/fixtures/cluster.xml | 20 ++ oca/tests/fixtures/clusterpool.xml | 24 ++ oca/tests/fixtures/datastore.xml | 42 +++ oca/tests/fixtures/datastorepool.xml | 79 +++++ oca/tests/fixtures/group.xml | 11 + oca/tests/fixtures/grouppool.xml | 21 ++ oca/tests/fixtures/host.xml | 48 +++ oca/tests/fixtures/hostpool.xml | 62 ++++ oca/tests/fixtures/image.xml | 38 ++ oca/tests/fixtures/imagepool.xml | 116 +++++++ oca/tests/fixtures/one_auth | 1 + oca/tests/fixtures/template.xml | 26 ++ oca/tests/fixtures/templatepool.xml | 52 +++ oca/tests/fixtures/user.xml | 16 + oca/tests/fixtures/userpool.xml | 54 +++ oca/tests/fixtures/vm.xml | 73 ++++ oca/tests/fixtures/vmpool.xml | 215 ++++++++++++ oca/tests/fixtures/vnet.xml | 46 +++ oca/tests/fixtures/vnetpool.xml | 60 ++++ oca/tests/integration_test_client.py | 25 ++ oca/tests/integration_test_template.py | 66 ++++ oca/tests/test_client.py | 107 ++++++ oca/tests/test_cluster.py | 33 ++ oca/tests/test_clusterpool.py | 21 ++ oca/tests/test_datastore.py | 45 +++ oca/tests/test_datastore_pool.py | 20 ++ oca/tests/test_group.py | 37 ++ oca/tests/test_group_pool.py | 20 ++ oca/tests/test_host.py | 80 +++++ oca/tests/test_host_pool.py | 20 ++ oca/tests/test_image.py | 109 ++++++ oca/tests/test_image_pool.py | 22 ++ oca/tests/test_integration.py | 12 + oca/tests/test_template.py | 85 +++++ oca/tests/test_template_pool.py | 20 ++ oca/tests/test_user.py | 40 +++ oca/tests/test_user_pool.py | 20 ++ oca/tests/test_virtual_network.py | 92 +++++ oca/tests/test_virtual_network_pool.py | 21 ++ oca/tests/test_virtualmachine.py | 147 ++++++++ oca/tests/test_virtualmachine_pool.py | 52 +++ oca/user.py | 150 ++++++++ oca/vm.py | 458 +++++++++++++++++++++++++ oca/vn.py | 173 ++++++++++ setup.py | 42 +++ tox-integrationtests.ini | 30 ++ tox.ini | 12 + 78 files changed, 4703 insertions(+) create mode 100644 .travis.yml create mode 100644 AUTHORS create mode 100644 MANIFEST.in create mode 120000 README create mode 100644 README.rst create mode 100644 TODO create mode 100644 docs/Makefile create mode 100644 docs/api.rst create mode 100644 docs/api/client.rst create mode 100644 docs/api/group.rst create mode 100644 docs/api/host.rst create mode 100644 docs/api/image.rst create mode 100644 docs/api/template.rst create mode 100644 docs/api/user.rst create mode 100644 docs/api/vm.rst create mode 100644 docs/api/vn.rst create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/user_guide.rst create mode 100644 oca/__init__.py create mode 100644 oca/acl.py create mode 100644 oca/cluster.py create mode 100644 oca/datastore.py create mode 100644 oca/exceptions.py create mode 100644 oca/group.py create mode 100644 oca/host.py create mode 100644 oca/image.py create mode 100644 oca/pool.py create mode 100644 oca/template.py create mode 100644 oca/tests/__init__.py create mode 100644 oca/tests/fixtures/cluster.xml create mode 100644 oca/tests/fixtures/clusterpool.xml create mode 100644 oca/tests/fixtures/datastore.xml create mode 100644 oca/tests/fixtures/datastorepool.xml create mode 100644 oca/tests/fixtures/group.xml create mode 100644 oca/tests/fixtures/grouppool.xml create mode 100644 oca/tests/fixtures/host.xml create mode 100644 oca/tests/fixtures/hostpool.xml create mode 100644 oca/tests/fixtures/image.xml create mode 100644 oca/tests/fixtures/imagepool.xml create mode 100644 oca/tests/fixtures/one_auth create mode 100644 oca/tests/fixtures/template.xml create mode 100644 oca/tests/fixtures/templatepool.xml create mode 100644 oca/tests/fixtures/user.xml create mode 100644 oca/tests/fixtures/userpool.xml create mode 100644 oca/tests/fixtures/vm.xml create mode 100644 oca/tests/fixtures/vmpool.xml create mode 100644 oca/tests/fixtures/vnet.xml create mode 100644 oca/tests/fixtures/vnetpool.xml create mode 100644 oca/tests/integration_test_client.py create mode 100644 oca/tests/integration_test_template.py create mode 100644 oca/tests/test_client.py create mode 100644 oca/tests/test_cluster.py create mode 100644 oca/tests/test_clusterpool.py create mode 100644 oca/tests/test_datastore.py create mode 100644 oca/tests/test_datastore_pool.py create mode 100644 oca/tests/test_group.py create mode 100644 oca/tests/test_group_pool.py create mode 100644 oca/tests/test_host.py create mode 100644 oca/tests/test_host_pool.py create mode 100644 oca/tests/test_image.py create mode 100644 oca/tests/test_image_pool.py create mode 100644 oca/tests/test_integration.py create mode 100644 oca/tests/test_template.py create mode 100644 oca/tests/test_template_pool.py create mode 100644 oca/tests/test_user.py create mode 100644 oca/tests/test_user_pool.py create mode 100644 oca/tests/test_virtual_network.py create mode 100644 oca/tests/test_virtual_network_pool.py create mode 100644 oca/tests/test_virtualmachine.py create mode 100644 oca/tests/test_virtualmachine_pool.py create mode 100644 oca/user.py create mode 100644 oca/vm.py create mode 100644 oca/vn.py create mode 100644 setup.py create mode 100644 tox-integrationtests.ini create mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..45fe394 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: python +python: + - "2.7" + - "3.5" + +install: + - pip install tox tox-travis +script: tox diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..cf0aea3 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,9 @@ +========= + AUTHORS +========= + +Łukasz Oleś +Mattias de Hollander +Matthias Schmitz +Michael Schmidt +Sysradium diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..811adc7 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include README.rst +recursive-include docs * +recursive-include oca * + + diff --git a/README b/README new file mode 120000 index 0000000..92cacd2 --- /dev/null +++ b/README @@ -0,0 +1 @@ +README.rst \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..7625c0a --- /dev/null +++ b/README.rst @@ -0,0 +1,42 @@ +############################################## +OCA - OpenNebula Cloud Api +############################################## + +:Version: 4.10.0 +:TravisCI Status: + .. image:: https://travis-ci.org/python-oca/python-oca.svg + :target: https://travis-ci.org/python-oca/python-oca + +About +----- + +Bindings for XMLRPC OpenNebula Cloud API + +Documentation +------------- +See http://python-oca.github.io/python-oca/index.html and http://docs.opennebula.org/4.10/integration/system_interfaces/api.html + +All `allocate` functions are implemented as static methods. + +Examples +-------- + +Show all running virtual machines:: + + client = oca.Client('user:password', 'http://12.12.12.12:2633/RPC2') + vm_pool = oca.VirtualMachinePool(client) + vm_pool.info() + + for vm in vm_pool: + ip_list = ', '.join(v.ip for v in vm.template.nics) + print("{} {} {} (memory: {} MB)".format(vm.name, ip_list, vm.str_state, vm.template.memory)) + +License +------- + +OCA is under Apache Software License + +Authors +------- + +See AUTHORS file diff --git a/TODO b/TODO new file mode 100644 index 0000000..def42d3 --- /dev/null +++ b/TODO @@ -0,0 +1 @@ +-add ACL module diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..1af62a7 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,130 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/oca.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/oca.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/oca" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/oca" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..ef7ac9f --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,17 @@ +API Documentation +================= + +See http://docs.opennebula.org/4.10/integration/system_interfaces/api.html + +.. toctree:: + :maxdepth: 2 + + api/client + api/host + api/image + api/vn + api/user + api/group + api/template + api/vm + diff --git a/docs/api/client.rst b/docs/api/client.rst new file mode 100644 index 0000000..fcc7986 --- /dev/null +++ b/docs/api/client.rst @@ -0,0 +1,7 @@ +.. _oca module: + +:mod:`oca.Client` +-------------------------------- + +.. autoclass:: oca.Client + diff --git a/docs/api/group.rst b/docs/api/group.rst new file mode 100644 index 0000000..7307b05 --- /dev/null +++ b/docs/api/group.rst @@ -0,0 +1,10 @@ +.. _group module: + +:mod:`oca.group` +-------------------------------- + +.. autoclass:: oca.Group +.. autoclass:: oca.GroupPool + :members: info + :no-inherited-members: + diff --git a/docs/api/host.rst b/docs/api/host.rst new file mode 100644 index 0000000..f7f0593 --- /dev/null +++ b/docs/api/host.rst @@ -0,0 +1,10 @@ +.. _host module: + +:mod:`oca.host` +-------------------------------- + +.. autoclass:: oca.Host +.. autoclass:: oca.HostPool + :members: info + :no-inherited-members: + diff --git a/docs/api/image.rst b/docs/api/image.rst new file mode 100644 index 0000000..c87fec4 --- /dev/null +++ b/docs/api/image.rst @@ -0,0 +1,10 @@ +.. _image module: + +:mod:`oca.image` +-------------------------------- + +.. autoclass:: oca.Image +.. autoclass:: oca.ImagePool + :members: info + :no-inherited-members: + diff --git a/docs/api/template.rst b/docs/api/template.rst new file mode 100644 index 0000000..92ffce2 --- /dev/null +++ b/docs/api/template.rst @@ -0,0 +1,10 @@ +.. _template module: + +:mod:`oca.template` +-------------------------------- + +.. autoclass:: oca.VmTemplate +.. autoclass:: oca.VmTemplatePool + :members: info + :no-inherited-members: + diff --git a/docs/api/user.rst b/docs/api/user.rst new file mode 100644 index 0000000..5382851 --- /dev/null +++ b/docs/api/user.rst @@ -0,0 +1,10 @@ +.. _user module: + +:mod:`oca.user` +-------------------------------- + +.. autoclass:: oca.User +.. autoclass:: oca.UserPool + :members: info + :no-inherited-members: + diff --git a/docs/api/vm.rst b/docs/api/vm.rst new file mode 100644 index 0000000..9502ea9 --- /dev/null +++ b/docs/api/vm.rst @@ -0,0 +1,10 @@ +.. _virtualmachine module: + +:mod:`oca.virtualmachine` +-------------------------------- + +.. autoclass:: oca.VirtualMachine + :members: +.. autoclass:: oca.VirtualMachinePool + :members: info + :no-inherited-members: diff --git a/docs/api/vn.rst b/docs/api/vn.rst new file mode 100644 index 0000000..537ffe0 --- /dev/null +++ b/docs/api/vn.rst @@ -0,0 +1,11 @@ +.. _virtualnetwork module: + +:mod:`oca.virtualnetwork` +-------------------------------- + +.. autoclass:: oca.VirtualNetwork + :members: + +.. autoclass:: oca.VirtualNetworkPool + :members: info + :no-inherited-members: diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..fe29967 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- +# +# oca documentation build configuration file, created by +# sphinx-quickstart on Sat Dec 11 00:53:18 2010. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'oca' +copyright = u'2010-2015, Python OCA authors' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '4.10.0' +# The full version, including alpha/beta/rc tags. +release = '4.10.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ocadoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'oca.tex', u'oca Documentation', + u'Łukasz Oleś', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'oca', u'oca Documentation', + [u'Łukasz Oleś'], 1) +] + +autodoc_default_flags = ['members', 'undoc-members', 'inherited-members', 'show-inheritance'] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..2458e71 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,23 @@ +.. oca documentation master file, created by + sphinx-quickstart on Sat Dec 11 00:53:18 2010. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to oca's documentation! +=============================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + user_guide + api + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..8814a7e --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,170 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\oca.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\oca.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/docs/user_guide.rst b/docs/user_guide.rst new file mode 100644 index 0000000..b9c075f --- /dev/null +++ b/docs/user_guide.rst @@ -0,0 +1,17 @@ +User Guide +========== + +Examples +-------- + +Allocating new host:: + + import oca + client = oca.Client('user:password', 'http:12.12.12.12:2633/RPC2') + new_host_id = oca.Host.allocate(client, 'host_name', 'im_xen', 'vmm_xen', 'tm_nfs') + hostpool = oca.HostPool(client) + hostpool.info() + vm = hostpool.get_by_id(new_host_id) + print vm.name, vm.str_state + + diff --git a/oca/__init__.py b/oca/__init__.py new file mode 100644 index 0000000..844b3d7 --- /dev/null +++ b/oca/__init__.py @@ -0,0 +1,156 @@ +# -*- coding: UTF-8 -*- +import http.client +import logging +import os +import re +import socket +import xmlrpc.client + +from .cluster import Cluster, ClusterPool +from .datastore import Datastore, DatastorePool +from .exceptions import OpenNebulaException +from .group import Group, GroupPool +from .host import Host, HostPool +from .image import Image, ImagePool +from .template import VmTemplate, VmTemplatePool +from .user import User, UserPool +from .vm import VirtualMachine, VirtualMachinePool +from .vn import VirtualNetwork, VirtualNetworkPool + +CONNECTED = -3 +ALL = -2 +CONNECTED_AND_GROUP = -1 + +logger = logging.getLogger(__name__) + + +class TimeoutHTTPConnection(http.client.HTTPConnection): + def connect(self): + http.client.HTTPConnection.connect(self) + self.sock.settimeout(self.timeout) + + +class TimeoutHTTP(http.client.HTTPConnection): + _connection_class = TimeoutHTTPConnection + + def set_timeout(self, timeout): + self._conn.timeout = timeout + + +class ProxiedTransport(xmlrpc.client.Transport): + def __init__(self, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, *args, **kwargs): + xmlrpc.client.Transport.__init__(self, *args, **kwargs) + self.timeout = timeout + + def set_proxy(self, proxy): + self.proxy = proxy + + def make_connection(self, host): + self.realhost = host + h = http.client.HTTPConnection(self.proxy) + return h + + def send_request(self, connection, handler, request_body): + connection.putrequest("POST", 'http://%s%s' % (self.realhost, handler)) + + def send_host(self, connection, host): + connection.putheader('Host', self.realhost) + + +class Client(object): + """ + The client class, represents the connection with the core and handles the + xml-rpc calls(see http://www.opennebula.org/documentation:rel3.2:api) + """ + DEFAULT_ONE_AUTH = "~/.one/one_auth" + ONE_AUTH_RE = re.compile('^(.+?):(.+)$') + DEFAULT_ONE_ADDRESS = "http://localhost:2633/RPC2" + + def __init__(self, secret=None, address=None, proxy=None): + self.one_version = None + + if secret: + one_secret = secret + elif "ONE_AUTH" in os.environ and os.environ["ONE_AUTH"]: + one_auth = os.path.expanduser(os.environ["ONE_AUTH"]) + with open(one_auth) as f: + one_secret = f.read().strip() + elif os.path.exists(os.path.expanduser(self.DEFAULT_ONE_AUTH)): + with open(os.path.expanduser(self.DEFAULT_ONE_AUTH)) as f: + one_secret = f.read().strip() + else: + raise OpenNebulaException('ONE_AUTH file not present') + + one_secret = self.ONE_AUTH_RE.match(one_secret) + if one_secret: + user = one_secret.groups()[0] + password = one_secret.groups()[1] + else: + raise OpenNebulaException("Authorization file malformed") + + self.one_auth = '{0}:{1}'.format(user, password) + + if address: + self.one_address = address + elif "ONE_XMLRPC" in os.environ and os.environ["ONE_XMLRPC"]: + self.one_address = os.environ["ONE_XMLRPC"] + else: + self.one_address = self.DEFAULT_ONE_ADDRESS + + if proxy: + p = ProxiedTransport(timeout=100) + p.set_proxy(proxy) + self.server = xmlrpc.client.ServerProxy(self.one_address, transport=p) + else: + self.server = xmlrpc.client.ServerProxy(self.one_address) + + def call(self, function, *args): + """ + Calls rpc function. + + Arguments + + ``function`` + OpenNebula xmlrpc funtcion name (without preceding 'one.') + + ``args`` + function arguments + + """ + try: + func = getattr(self.server.one, function) + ret = func(self.one_auth, *args) + try: + is_success, data, return_code = ret + except ValueError: + logger.error( + """Called function: {function} +arguments: {args} +Return value = {ret}""" + .format( + function=function, args=str(args), + ret=str(ret) + ) + ) + data = '' + is_success = False + except socket.error as e: + # connection error + raise e + if not is_success: + raise OpenNebulaException(data) + return data + + def version(self): + """ + Get the version of the connected OpenNebula server. + """ + self.one_version = self.call('system.version') + return self.one_version + + +__all__ = [Client, OpenNebulaException, Host, HostPool, VirtualMachine, + VirtualMachinePool, User, UserPool, + Image, ImagePool, VirtualNetwork, VirtualNetworkPool, + Group, GroupPool, VmTemplate, VmTemplatePool, ALL, CONNECTED, + Cluster, ClusterPool, Datastore, DatastorePool] diff --git a/oca/acl.py b/oca/acl.py new file mode 100644 index 0000000..8d98fed --- /dev/null +++ b/oca/acl.py @@ -0,0 +1 @@ +# -*- coding: UTF-8 -*- diff --git a/oca/cluster.py b/oca/cluster.py new file mode 100644 index 0000000..477f7b0 --- /dev/null +++ b/oca/cluster.py @@ -0,0 +1,56 @@ +# -*- coding: UTF-8 -*- +from .pool import Pool, PoolElement, Template, extractString + + +class Cluster(PoolElement): + METHODS = { + # 'info' : 'cluster.info', + 'allocate': 'cluster.allocate', + 'delete': 'cluster.delete', + # 'enable' : 'cluster.enable', + # 'update' : 'cluster.update' + } + + XML_TYPES = { + 'id': int, + 'name': extractString, + 'host_ids': ['HOSTS', lambda hosts: [int(host_id.text) for host_id in hosts]], + 'datastore_ids': ['DATASTORES', lambda datastores: [int(datastore_id.text) for datastore_id in datastores]], + 'vnet_ids': ['VNETS', lambda vnets: [int(vnet_id.text) for vnet_id in vnets]], + 'template': ['TEMPLATE', Template], + } + + ELEMENT_NAME = 'CLUSTER' + + @staticmethod + def allocate(client, cluster_name): + """ + Adds a cluster to the cluster list + + Arguments + + ``cluster_name`` + Clustername to add + """ + cluster_id = client.call(Cluster.METHODS['allocate'], cluster_name) + return cluster_id + + def __init__(self, xml, client): + super(Cluster, self).__init__(xml, client) + self._convert_types() + + def __repr__(self): + return '' % self.name + + +class ClusterPool(Pool): + METHODS = { + 'info': 'clusterpool.info', + } + + def __init__(self, client): + super(ClusterPool, self).__init__('CLUSTER_POOL', 'CLUSTER', client) + + def _factory(self, xml): + c = Cluster(xml, self.client) + return c diff --git a/oca/datastore.py b/oca/datastore.py new file mode 100644 index 0000000..4f40bdf --- /dev/null +++ b/oca/datastore.py @@ -0,0 +1,71 @@ +# -*- coding: UTF-8 -*- +from .pool import Pool, PoolElement, Template, extractString + + +class Datastore(PoolElement): + METHODS = { + # 'info' : 'datastore.info', + 'allocate': 'datastore.allocate', + 'delete': 'datastore.delete', + # 'enable' : 'datastore.enable', + # 'update' : 'datastore.update' + } + + XML_TYPES = { + 'id': int, + 'name': extractString, + 'uid': int, + 'gid': int, + 'uname': extractString, + 'gname': extractString, + # 'permissions' : Permissions, + 'ds_mad': extractString, + 'tm_mad': extractString, + 'base_path': extractString, + 'type': int, + 'disk_type': int, + # 'state' : ???, + # 'cluster_id': int, + 'cluster_ids': ['CLUSTERS', lambda clusters: [int(cluster_id.text) for cluster_id in clusters]], + # 'cluster': extractString, + 'total_mb': int, + 'free_mb': int, + 'used_mb': int, + 'image_ids': ['IMAGES', lambda images: [int(image_id.text) for image_id in images]], + 'template': ['TEMPLATE', Template], + } + + ELEMENT_NAME = 'DATASTORE' + + @staticmethod + def allocate(client, datastore_template): + """ + Adds a datastore to the datastore list + + Arguments + + ``datastore_template`` + Template for the datastore to add + """ + datastore_id = client.call(Datastore.METHODS['allocate'], datastore_template) + return datastore_id + + def __init__(self, xml, client): + super(Datastore, self).__init__(xml, client) + self._convert_types() + + def __repr__(self): + return '' % self.name + + +class DatastorePool(Pool): + METHODS = { + 'info': 'datastorepool.info', + } + + def __init__(self, client): + super(DatastorePool, self).__init__('DATASTORE_POOL', 'DATASTORE', client) + + def _factory(self, xml): + c = Datastore(xml, self.client) + return c diff --git a/oca/exceptions.py b/oca/exceptions.py new file mode 100644 index 0000000..44373b1 --- /dev/null +++ b/oca/exceptions.py @@ -0,0 +1,5 @@ +# -*- coding: UTF-8 -*- + + +class OpenNebulaException(Exception): + pass diff --git a/oca/group.py b/oca/group.py new file mode 100644 index 0000000..bfdca6f --- /dev/null +++ b/oca/group.py @@ -0,0 +1,62 @@ +# -*- coding: UTF-8 -*- +from .pool import Pool, PoolElement, Template, extractString + + +class Group(PoolElement): + METHODS = { + 'info': 'group.info', + 'allocate': 'group.allocate', + 'delete': 'group.delete', + } + + XML_TYPES = { + 'id': int, + 'name': extractString, + 'template': ['TEMPLATE', Template], + 'users': ['USERS', lambda users: [int(i.text) for i in users]], + # 'resource_providers': handled separately + # 'datastore_quota': handled separately + # 'network_quota': handled separately + # 'vm_quota': handled separately + # 'image_quota' + # 'default_group_quotas' + } + + ELEMENT_NAME = 'GROUP' + + @staticmethod + def allocate(client, group_name): + """ + Allocates a new group in OpenNebula + + Arguments + + ``client`` + oca.Client object + + ``group`` + a string containing the group name + """ + group_id = client.call(Group.METHODS['allocate'], group_name) + return group_id + + def __init__(self, xml, client): + super(Group, self).__init__(xml, client) + self.id = self['ID'] if self['ID'] else None + + def __repr__(self): + return '' % self.name + + +class GroupPool(Pool): + METHODS = { + 'info': 'grouppool.info', + } + + def __init__(self, client): + super(GroupPool, self).__init__('GROUP_POOL', 'GROUP', client) + + def _factory(self, xml): + i = Group(xml, self.client) + i._convert_types() + return i diff --git a/oca/host.py b/oca/host.py new file mode 100644 index 0000000..631bd35 --- /dev/null +++ b/oca/host.py @@ -0,0 +1,140 @@ +# -*- coding: UTF-8 -*- +from .pool import Pool, PoolElement, Template, extractString + + +class HostShare(Template): + def __repr__(self): + return '' + + +class Host(PoolElement): + METHODS = { + 'info': 'host.info', + 'allocate': 'host.allocate', + 'delete': 'host.delete', + 'enable': 'host.enable', + 'update': 'host.update' + } + + INIT = 0 + MONITORING_MONITORED = 1 # Currently monitoring, previously MONITORED + MONITORED = 2 + ERROR = 3 + DISABLED = 4 + MONITORING_ERROR = 5 # Currently monitoring, previously ERROR + MONITORING_INIT = 6 # Currently monitoring, previously initialized + MONITORING_DISABLED = 7 # Currently monitoring, previously DISABLED + HOST_STATES = ['INIT', 'MONITORING_MONITORED', 'MONITORED', 'ERROR', 'DISABLED', + 'MONITORING_ERROR', 'MONITORING_INIT', 'MONITORING_DISABLED'] + + SHORT_HOST_STATES = { + 'INIT': 'on', + 'MONITORING_MONITORED': 'on', + 'MONITORED': 'on', + 'ERROR': 'err', + 'DISABLED': 'off', + 'MONITORING_ERROR': 'on', + 'MONITORING_INIT': 'on', + 'MONITORING_DISABLED': 'on', + } + + XML_TYPES = { + 'id': int, + 'name': extractString, + 'state': int, + 'im_mad': extractString, + 'vm_mad': extractString, + 'vn_mad': extractString, + 'last_mon_time': int, + 'cluster': extractString, + 'cluster_id': int, + 'vm_ids': ['VMS', lambda vms: [int(vmid.text) for vmid in vms]], + 'template': ['TEMPLATE', Template], + 'host_share': ['HOST_SHARE', HostShare], + } + + ELEMENT_NAME = 'HOST' + + @staticmethod + def allocate(client, hostname, im, vmm, tm, cluster_id=-1): + """ + Adds a host to the host list + + Arguments + + ``hostname`` + Hostname machine to add + + ``im`` + Information manager' + + ``vmm`` + Virtual machine manager. + + ``tm`` + Transfer manager + """ + host_id = client.call(Host.METHODS['allocate'], hostname, im, vmm, tm, cluster_id) + return host_id + + def __init__(self, xml, client): + # remove 'vn_mad' attribute in OpenNebula >= 5 + xml_types = Host.XML_TYPES + if client.one_version is None: + client.version() + if client.one_version >= '5' and 'vn_mad' in xml_types: + del xml_types['vn_mad'] + + super(Host, self).__init__(xml, client) + self.id = self['ID'] if self['ID'] else None + + def enable(self): + """ + Enable this host + """ + self.client.call(self.METHODS['enable'], self.id, True) + + def disable(self): + """ + Disable this host. + """ + self.client.call(self.METHODS['enable'], self.id, False) + + def update(self, template, merge=False): + """ + Update the template of this host. If merge is false (default), + the existing template is replaced. + """ + self.client.call(self.METHODS['update'], self.id, template, 1 if merge else 0) + + @property + def str_state(self): + """ + String representation of host state. + One of 'INIT', 'MONITORING', 'MONITORED', 'ERROR', 'DISABLED' + """ + return self.HOST_STATES[int(self.state)] + + @property + def short_state(self): + """ + Short string representation of host state. One of 'on', 'off', 'err' + """ + return self.SHORT_HOST_STATES[self.str_state] + + def __repr__(self): + return '' % self.name + + +class HostPool(Pool): + METHODS = { + 'info': 'hostpool.info', + } + + def __init__(self, client): + super(HostPool, self).__init__('HOST_POOL', 'HOST', client) + + def _factory(self, xml): + h = Host(xml, self.client) + h._convert_types() + return h diff --git a/oca/image.py b/oca/image.py new file mode 100644 index 0000000..3797798 --- /dev/null +++ b/oca/image.py @@ -0,0 +1,241 @@ +# -*- coding: UTF-8 -*- +from .pool import Pool, PoolElement, Template, extractString + + +class Image(PoolElement): + METHODS = { + 'info': 'image.info', + 'allocate': 'image.allocate', + 'delete': 'image.delete', + 'update': 'image.update', + 'enable': 'image.enable', + 'publish': 'image.publish', + 'chmod': 'image.chmod', + 'chown': 'image.chown', + 'persistent': 'image.persistent', + 'clone': 'image.clone', + } + + XML_TYPES = { + 'id': int, + 'uid': int, + 'gid': int, + 'uname': extractString, + 'gname': extractString, + 'name': extractString, + # 'permissions' : ???, + 'type': int, + 'disk_type': int, + 'persistent': int, + 'regtime': int, + 'source': extractString, + 'path': extractString, + 'fstype': extractString, + 'size': int, + 'state': int, + 'running_vms': int, + 'cloning_ops': int, + 'cloning_id': int, + 'datastore_id': int, + 'datastore': extractString, + 'vm_ids': ["VMS", lambda vms: [int(vm_id.text) for vm_id in vms]], + 'clone_ids': ["CLONES", lambda clones: [int(clone_id.text) for clone_id in clones]], + 'template': ['TEMPLATE', Template], + } + + INIT = 0 + READY = 1 + USED = 2 + DISABLED = 3 + IMAGE_STATES = ['INIT', 'READY', 'USED', 'DISABLED', 'LOCKED', 'ERROR', 'CLONE', 'DELETE', 'USED_PERS'] + + SHORT_IMAGE_STATES = { + "INIT": "init", + "READY": "rdy", + "USED": "used", + "DISABLED": "disa", + "LOCKED": "lock", + "ERROR": "err", + "CLONE": "clon", + "DELETE": "dele", + "USED_PERS": "used" + } + + IMAGE_TYPES = ['OS', 'CDROM', 'DATABLOCK'] + + SHORT_IMAGE_TYPES = { + "OS": "OS", + "CDROM": "CD", + "DATABLOCK": "DB" + } + + ELEMENT_NAME = 'IMAGE' + + @staticmethod + def allocate(client, template, datastore): + """ + Allocates a new image in OpenNebula + + Arguments + + ``client`` + oca.Client object + + ``template`` + a string containing the template of the image + ``datastore`` + the datastore id where the image is to be allocated + """ + image_id = client.call(Image.METHODS['allocate'], template, datastore) + return image_id + + def __init__(self, xml, client): + super(Image, self).__init__(xml, client) + self.id = self['ID'] if self['ID'] else None + + def update(self, template): + """ + Replaces the template contents + + Arguments + + ``template`` + New template contents + """ + self.client.call(self.METHODS['update'], self.id, template) + + def enable(self): + """ + Enables an image + """ + self.client.call(self.METHODS['enable'], self.id, True) + + def disable(self): + """ + Disables an image + """ + self.client.call(self.METHODS['enable'], self.id, False) + + def publish(self): + """ + Publishes an image + """ + self.client.call(self.METHODS['publish'], self.id, True) + + def unpublish(self): + """ + Unpublishes an image + """ + self.client.call(self.METHODS['publish'], self.id, False) + + def set_persistent(self): + """ + Set Image as persistent + """ + self.client.call(self.METHODS['persistent'], self.id, True) + + def set_nonpersistent(self): + """ + Set Image as non persistent + """ + self.client.call(self.METHODS['persistent'], self.id, False) + + def chown(self, uid, gid): + """ + Changes the owner/group + + Arguments + + ``uid`` + New owner id. Set to -1 to leave current value + ``gid`` + New group id. Set to -1 to leave current value + """ + self.client.call(self.METHODS['chown'], self.id, uid, gid) + + def chmod(self, owner_u, owner_m, owner_a, group_u, group_m, group_a, other_u, other_m, other_a): + """ + Changes the permission bits + + Arguments + + ``owner_u`` + User USE bit. Set to -1 to leave current value + ``owner_m`` + User MANAGE bit. Set to -1 to leave current value + ``owner_a`` + User ADMIN bit. Set to -1 to leave current value + ``group_u`` + Group USE bit. Set to -1 to leave current value + ``group_m`` + Group MANAGE bit. Set to -1 to leave current value + ``group_a`` + Group ADMIN bit. Set to -1 to leave current value + ``other_u`` + Other USE bit. Set to -1 to leave current value + ``other_m`` + Other MANAGE bit. Set to -1 to leave current value + ``other_a`` + Other ADMIN bit. Set to -1 to leave current value + """ + self.client.call(self.METHODS['chmod'], self.id, owner_u, owner_m, owner_a, group_u, group_m, group_a, other_u, + other_m, other_a) + + def clone(self, name='', datastore_id=-1): + """ + Creates a clone of an image + ``name`` + name of a target element + ``datastore_id`` + The ID of the target datastore. Optional, can be set to -1 to use the current one. + """ + self.client.call(self.METHODS['clone'], self.id, name, datastore_id) + + @property + def str_state(self): + """ + String representation of image state. + One of 'INIT', 'READY', 'USED', 'DISABLED', 'LOCKED', 'ERROR', 'CLONE', 'DELETE', 'USED_PERS' + """ + return self.IMAGE_STATES[int(self.state)] + + @property + def short_state(self): + """ + Short string representation of image state. + One of 'init', 'rdy', 'used', 'disa', 'lock', 'err', 'clon', 'dele', 'used' + """ + return self.SHORT_IMAGE_STATES[self.str_state] + + @property + def str_type(self): + """ + String representation of image type. + One of 'OS', 'CDROM', 'DATABLOCK' + """ + return self.IMAGE_TYPES[int(self.type)] + + @property + def short_type(self): + """ + Short string representation of image type. + One of 'OS', 'CD', 'DB' + """ + return self.SHORT_IMAGE_TYPES[self.str_type] + + def __repr__(self): + return '' % self.name + + +class ImagePool(Pool): + METHODS = { + 'info': 'imagepool.info', + } + + def __init__(self, client): + super(ImagePool, self).__init__('IMAGE_POOL', 'IMAGE', client) + + def _factory(self, xml): + i = Image(xml, self.client) + i._convert_types() + return i diff --git a/oca/pool.py b/oca/pool.py new file mode 100644 index 0000000..cbf5e38 --- /dev/null +++ b/oca/pool.py @@ -0,0 +1,177 @@ +# -*- coding: UTF-8 -*- +import xml.etree.ElementTree as ET + +from .exceptions import OpenNebulaException + + +class WrongNameError(OpenNebulaException): + pass + + +class WrongIdError(OpenNebulaException): + pass + + +def extractString(xml_or_string): + if isinstance(xml_or_string, str): + return xml_or_string + + # Py2 compatibility + try: + if isinstance(xml_or_string, unicode): + return xml_or_string + except NameError: + pass + + return xml_or_string.text or '' + + +class Template(object): + def __init__(self, xml_element, multiple=[]): + self.xml = ET.tostring(xml_element) + self.xml_element = xml_element + self.multiple = multiple + self.parse() + + def parse(self): + for element in self.xml_element: + tag = element.tag + if tag in self.multiple: + self.parse_multiple(tag, element) + else: + setattr(self, tag.lower(), element.text) + + def parse_multiple(self, tag, element): + attr = tag.lower() + 's' + attr_list = getattr(self, attr, []) + + class_obj = type(tag.capitalize(), (Template,), {}) + + attr_list.append(class_obj(element)) + setattr(self, attr, attr_list) + + +class XMLElement(object): + XML_TYPES = {} + + def __init__(self, xml=None): + if not (xml is None or ET.iselement(xml)): + xml = ET.fromstring(xml) + self.xml = xml + + def _initialize_xml(self, xml, root_element): + self.xml = ET.fromstring(xml.encode('utf-8')) + if self.xml.tag != root_element.upper(): + self.xml = None + self._convert_types() + + def __getitem__(self, key): + value = self.xml.find(key.upper()) + if value is not None: + if value.text: + return value.text + else: + return value + else: + raise IndexError("Key {0} not found!".format(key)) + + def __getattr__(self, name): + try: + return self[name] + except (IndexError, TypeError): + raise AttributeError(name) + + def _convert_types(self): + for name, fun in self.XML_TYPES.items(): + if isinstance(fun, list): + tag, cls = fun[0], fun[1] + xml = self.xml.find(tag) + setattr(self, name, cls(xml, *fun[2:])) + else: + setattr(self, name, fun(self[name])) + + +class Pool(list, XMLElement): + def __init__(self, pool, element, client): + super(Pool, self).__init__() + + self.pool_name = pool + self.element_name = element + self.client = client + + def info(self, filter=-3, range_start=-1, range_end=-1, *args): + """ + Retrives/Refreshes resource pool information + + ``filter`` + Filter flag. By defaults retrives only connected user reources. + + ``range_start`` + Range start ID. -1 for all + + ``range_end`` + Range end ID. -1 for all + """ + self[:] = [] + data = self.client.call(self.METHODS['info'], filter, + range_start, range_end, *args) + self._initialize_xml(data, self.pool_name) + for element in self.xml.findall(self.element_name): + self.append(self._factory(element)) + + def _factory(self): + pass + + def get_by_id(self, id): + for i in self: + if i.id == id: + return i + raise WrongIdError() + + def get_by_name(self, name): + for i in self: + if i.name == name: + return i + raise WrongNameError() + + +class PoolElement(XMLElement): + def __init__(self, xml, client): + super(PoolElement, self).__init__(xml) + self.client = client + + @classmethod + def new_with_id(cls, client, element_id): + """ + Retrives object which id equals ```id```. + + Arguments + + ```client``` + oca.Client object. + ```element_id`` + object id. + """ + element = cls.ELEMENT_NAME + xml = '<{0}>{1}'.format(element, element_id) + obj = cls(xml, client) + obj.id = int(obj.id) + return obj + + def info(self, *args): + data = self.client.call(self.METHODS['info'], self.id) + self._initialize_xml(data, self.ELEMENT_NAME) + + def delete(self): + """ + Deletes current object from the pool + """ + self.client.call(self.METHODS['delete'], self.id) + + def clone(self, name=''): + """ + Creates a clone of an elemet + ``name`` + name of a target element + """ + self.client.call(self.METHODS['clone'], self.id, name) diff --git a/oca/template.py b/oca/template.py new file mode 100644 index 0000000..344b5ed --- /dev/null +++ b/oca/template.py @@ -0,0 +1,112 @@ +# -*- coding: UTF-8 -*- +from .pool import Pool, PoolElement, Template + + +class VmTemplate(PoolElement): + METHODS = { + 'info': 'template.info', + 'allocate': 'template.allocate', + 'delete': 'template.delete', + 'update': 'template.update', + 'publish': 'template.publish', + 'chown': 'template.chown', + 'instantiate': 'template.instantiate', + 'clone': 'template.clone', + } + + XML_TYPES = { + 'id': int, + 'uid': int, + 'gid': int, + 'name': str, + 'uname': str, + 'gname': str, + 'regtime': int, + 'template': ['TEMPLATE', Template, ['DISK']] + } + + ELEMENT_NAME = 'VMTEMPLATE' + + @staticmethod + def allocate(client, template): + """ + Allocates a new template in OpenNebula + + ``client`` + oca.Client object + + ``template`` + a string containing the template contents + """ + template_id = client.call(VmTemplate.METHODS['allocate'], template) + return template_id + + def __init__(self, xml, client): + super(VmTemplate, self).__init__(xml, client) + self.id = self['ID'] if self['ID'] else None + + def update(self, template, update_type=0): + """ + Replaces the template contents. + + ``template`` + The new template contents. + ``update_type`` + Update type: 0: replace the whole template. 1: Merge new template with the existing one. + """ + self.client.call(VmTemplate.METHODS['update'], self.id, template, update_type) + + def publish(self): + """ + Publishes a template. + """ + self.client.call(VmTemplate.METHODS['publish'], self.id, True) + + def unpublish(self): + """ + Unpublishes a template. + """ + self.client.call(VmTemplate.METHODS['publish'], self.id, False) + + def chown(self, uid=-1, gid=-1): + """ + Changes the ownership of a template. + + ``uid`` + The User ID of the new owner. If set to -1, the owner is not changed. + ``gid`` + The Group ID of the new group. If set to -1, the group is not changed. + """ + self.client.call(VmTemplate.METHODS['chown'], self.id, uid, gid) + + def instantiate(self, name='', pending=False, extra_template=''): + """ + Creates a VM instance from a VmTemplate + + ``name`` + name of the VM instance + ``pending`` + False to create the VM on pending (default), True to create it on hold. + ``extra_template`` + A string containing an extra template to be merged with the one being instantiated + """ + return self.client.call(VmTemplate.METHODS['instantiate'], self.id, name, pending, extra_template) + + def __repr__(self): + return '' % self.name + + +class VmTemplatePool(Pool): + METHODS = { + 'info': 'templatepool.info', + } + + def __init__(self, client): + super(VmTemplatePool, self).__init__('VMTEMPLATE_POOL', 'VMTEMPLATE', client) + + # def info(self, + + def _factory(self, xml): + i = VmTemplate(xml, self.client) + i._convert_types() + return i diff --git a/oca/tests/__init__.py b/oca/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oca/tests/fixtures/cluster.xml b/oca/tests/fixtures/cluster.xml new file mode 100644 index 0000000..33853b6 --- /dev/null +++ b/oca/tests/fixtures/cluster.xml @@ -0,0 +1,20 @@ + + 101 + oneCluster + + 2 + 3 + + + 4 + 5 + + + 6 + 7 + + + diff --git a/oca/tests/fixtures/clusterpool.xml b/oca/tests/fixtures/clusterpool.xml new file mode 100644 index 0000000..7967dfa --- /dev/null +++ b/oca/tests/fixtures/clusterpool.xml @@ -0,0 +1,24 @@ + + + 101 + oneCluster + + + + + + + 102 + anotherCluster + + + + + + diff --git a/oca/tests/fixtures/datastore.xml b/oca/tests/fixtures/datastore.xml new file mode 100644 index 0000000..863fc61 --- /dev/null +++ b/oca/tests/fixtures/datastore.xml @@ -0,0 +1,42 @@ + + 100 + 0 + 0 + oneadmin + oneadmin + Custom-DS + + 1 + 1 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + + + + + 0 + 0 + 0 + -1 + + 9952 + 8999 + 425 + + 2 + 3 + + + diff --git a/oca/tests/fixtures/datastorepool.xml b/oca/tests/fixtures/datastorepool.xml new file mode 100644 index 0000000..88fa4b2 --- /dev/null +++ b/oca/tests/fixtures/datastorepool.xml @@ -0,0 +1,79 @@ + + + 0 + 0 + 0 + oneadmin + oneadmin + system + + 1 + 1 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + + + + + 1 + 0 + 0 + -1 + test + 0 + 0 + 0 + + + + + 1 + 0 + 0 + oneadmin + oneadmin + default + + 1 + 1 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + + + + + 0 + 0 + 0 + -1 + + 9952 + 8999 + 425 + + + + diff --git a/oca/tests/fixtures/group.xml b/oca/tests/fixtures/group.xml new file mode 100644 index 0000000..9f9e3ac --- /dev/null +++ b/oca/tests/fixtures/group.xml @@ -0,0 +1,11 @@ + + 1 + users + + + 1 + 2 + + diff --git a/oca/tests/fixtures/grouppool.xml b/oca/tests/fixtures/grouppool.xml new file mode 100644 index 0000000..401e042 --- /dev/null +++ b/oca/tests/fixtures/grouppool.xml @@ -0,0 +1,21 @@ + + + 1 + users +