Retire Sahara: remove repo content

Sahara project is retiring
- https://review.opendev.org/c/openstack/governance/+/919374

this commit remove the content of this project repo

Depends-On: https://review.opendev.org/c/openstack/project-config/+/919376
Change-Id: I45547184ec98d53346b98f3b3dcee84cf035e39f
This commit is contained in:
Ghanshyam Mann 2024-05-10 17:27:50 -07:00
parent 38b63d870f
commit fcc67b0374
178 changed files with 8 additions and 19523 deletions

View File

@ -1,12 +0,0 @@
[run]
branch = True
source = saharaclient
omit =
.tox/*
saharaclient/tests/*
[paths]
source = saharaclient
[report]
ignore_errors = True

40
.gitignore vendored
View File

@ -1,40 +0,0 @@
*.py[co]
*.egg
*.egg-info
dist
build
eggs
parts
var
sdist
develop-eggs
.installed.cfg
pip-log.txt
.tox
*.mo
.mr.developer.cfg
.DS_Store
Thumbs.db
.venv
.idea
.stestr
out
target
*.iml
*.ipr
*.iws
*.db
.coverage
nosetests.xml
pylint-report.txt
ChangeLog
cscope.out
.testrepository
AUTHORS
cover
doc/html
doc/source/apidoc
doc/build
*.log
# Files created by releasenotes build
releasenotes/build

View File

@ -1,3 +0,0 @@
[DEFAULT]
test_path=./saharaclient/tests
top_dir=./

View File

@ -1,65 +0,0 @@
- project:
queue: sahara
templates:
- openstack-cover-jobs
- openstack-python3-zed-jobs
- publish-openstack-docs-pti
- check-requirements
- release-notes-jobs-python3
- openstackclient-plugin-jobs
check:
jobs:
- python-saharaclient-scenario:
voting: false
- python-saharaclient-scenario-v2:
voting: false
- python-saharaclient-tempest:
voting: false
- python-saharaclient-tempest-v2:
voting: false
gate:
jobs:
- python-saharaclient-scenario:
voting: false
- python-saharaclient-scenario-v2:
voting: false
- python-saharaclient-tempest:
voting: false
- python-saharaclient-tempest-v2:
voting: false
- job:
name: python-saharaclient-scenario
description: |
Run scenario tests for Sahara against python-saharaclient
changes.
parent: sahara-tests-scenario
required-projects:
- openstack/python-saharaclient
- job:
name: python-saharaclient-scenario-v2
description: |
Run scenario tests for Sahara on API v2 against python-saharaclient
changes.
parent: sahara-tests-scenario-v2
required-projects:
- openstack/python-saharaclient
- job:
name: python-saharaclient-tempest
description: |
Run Tempest tests from the Sahara plugin against python-saharaclient
changes.
parent: sahara-tests-tempest
required-projects:
- openstack/python-saharaclient
- job:
name: python-saharaclient-tempest-v2
description: |
Run Tempest tests from the Sahara plugin on API v2 against
python-saharaclient changes.
parent: sahara-tests-tempest-v2
required-projects:
- openstack/python-saharaclient

View File

@ -1,19 +0,0 @@
The source repository for this project can be found at:
https://opendev.org/openstack/python-saharaclient
Pull requests submitted through GitHub are not monitored.
To start contributing to OpenStack, follow the steps in the contribution guide
to set up and use Gerrit:
https://docs.openstack.org/contributors/code-and-documentation/quick-start.html
Bugs should be filed on Storyboard:
https://storyboard.openstack.org/#!/project/openstack/python-saharaclient
For more specific information about contributing to this repository, see the
python-saharaclient contributor guide:
https://docs.openstack.org/python-saharaclient/latest/contributor/contributing.html

View File

@ -1,45 +0,0 @@
Sahara Style Commandments
=========================
- Step 1: Read the OpenStack Style Commandments
https://docs.openstack.org/hacking/latest/
- Step 2: Read on
Sahara Specific Commandments
----------------------------
Commit Messages
---------------
Using a common format for commit messages will help keep our git history
readable. Follow these guidelines:
- [S365] First, provide a brief summary of 50 characters or less. Summaries
of greater than 72 characters will be rejected by the gate.
- [S364] The first line of the commit message should provide an accurate
description of the change, not just a reference to a bug or blueprint.
Imports
-------
- [S366, S367] Organize your imports according to the ``Import order``
Dictionaries/Lists
------------------
- [S360] Ensure default arguments are not mutable.
- [S368] Must use a dict comprehension instead of a dict constructor with a
sequence of key-value pairs. For more information, please refer to
http://legacy.python.org/dev/peps/pep-0274/
Logs
----
- [S373] Don't translate logs
- [S374] You used a deprecated log level
Importing json
--------------
- [S375] It's more preferable to use ``jsonutils`` from ``oslo_serialization``
instead of ``json`` for operating with ``json`` objects.

176
LICENSE
View File

@ -1,176 +0,0 @@
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.

View File

@ -1,45 +1,10 @@
========================
Team and repository tags
========================
This project is no longer maintained.
.. image:: https://governance.openstack.org/tc/badges/python-saharaclient.svg
:target: https://governance.openstack.org/tc/reference/tags/index.html
.. Change things from this point on
Python bindings to the OpenStack Sahara API
===========================================
.. image:: https://img.shields.io/pypi/v/python-saharaclient.svg
:target: https://pypi.org/project/python-saharaclient/
:alt: Latest Version
This is a client for the OpenStack Sahara API. There's a Python API (the
``saharaclient`` module), and a command-line script (``sahara``). Each
implements the OpenStack Sahara API. You can find documentation for both
Python bindings and CLI in `Docs`_.
Development takes place via the usual OpenStack processes as outlined
in the `developer guide
<https://docs.openstack.org/infra/manual/developers.html>`_.
.. _Docs: https://docs.openstack.org/python-saharaclient/latest/
* License: Apache License, Version 2.0
* `PyPi`_ - package installation
* `Online Documentation`_
* `Blueprints`_ - feature specifications
* `Bugs`_ - stories and issue tracking
* `Source`_
* `Specs`_
* `How to Contribute`_
.. _PyPi: https://pypi.org/project/python-saharaclient
.. _Online Documentation: https://docs.openstack.org/python-saharaclient/latest/
.. _Blueprints: https://specs.openstack.org/openstack/sahara-specs/
.. _Bugs: https://storyboard.openstack.org/#!/project/934
.. _Source: https://opendev.org/openstack/python-saharaclient
.. _How to Contribute: https://docs.openstack.org/infra/manual/developers.html
.. _Specs: https://specs.openstack.org/openstack/sahara-specs/
.. _Release Notes: https://docs.openstack.org/releasenotes/python-saharaclient
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
For any further questions, please email
openstack-discuss@lists.openstack.org or join #openstack-dev on
OFTC.

View File

View File

@ -1,90 +0,0 @@
# Copyright (c) 2015 Mirantis Inc.
#
# 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 inspect
import os
import sys
from docutils import nodes
from . import ext
def _get_command(classes):
"""Associates each command class with command depending on setup.cfg
"""
commands = {}
setup_file = os.path.join(
os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')),
'setup.cfg')
for line in open(setup_file, 'r'):
for cl in classes:
if cl in line:
commands[cl] = line.split(' = ')[0].strip().replace('_', ' ')
return commands
class ArgParseDirectiveOSC(ext.ArgParseDirective):
"""Sphinx extension that automatically documents commands and options
of the module that contains OpenstackClient/cliff command objects
Usage example:
.. cli::
:module: saharaclient.osc.v1.clusters
"""
def run(self):
module_name = self.options['module']
mod = __import__(module_name, globals(), locals())
classes = inspect.getmembers(sys.modules[module_name], inspect.isclass)
classes_names = [cl[0] for cl in classes]
commands = _get_command(classes_names)
items = []
for cl in classes:
parser = cl[1](None, None).get_parser(None)
parser.prog = commands[cl[0]]
items.append(nodes.subtitle(text=commands[cl[0]]))
result = ext.parse_parser(
parser, skip_default_values='nodefault' in self.options)
result = ext.parser_navigate(result, '')
nested_content = ext.nodes.paragraph()
self.state.nested_parse(
self.content, self.content_offset, nested_content)
nested_content = nested_content.children
for item in nested_content:
if not isinstance(item, ext.nodes.definition_list):
items.append(item)
if 'description' in result:
items.append(self._nested_parse_paragraph(result['description']))
items.append(ext.nodes.literal_block(text=result['usage']))
items.append(ext.print_command_args_and_opts(
ext.print_arg_list(result, nested_content),
ext.print_opt_list(result, nested_content),
ext.print_subcommand_list(result, nested_content)
))
if 'epilog' in result:
items.append(self._nested_parse_paragraph(result['epilog']))
return items
def setup(app):
app.add_directive('cli', ArgParseDirectiveOSC)

View File

@ -1,386 +0,0 @@
# Copyright (c) 2013 Alex Rudakov
#
# 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 argparse import ArgumentParser
import os
from docutils import nodes
from docutils.statemachine import StringList
from docutils.parsers.rst.directives import flag, unchanged
from docutils.parsers.rst import Directive
from sphinx.util.nodes import nested_parse_with_titles
from .parser import parse_parser, parser_navigate
def map_nested_definitions(nested_content):
if nested_content is None:
raise Exception('Nested content should be iterable, not null')
# build definition dictionary
definitions = {}
for item in nested_content:
if not isinstance(item, nodes.definition_list):
continue
for subitem in item:
if not isinstance(subitem, nodes.definition_list_item):
continue
if not len(subitem.children) > 0:
continue
classifier = '@after'
idx = subitem.first_child_matching_class(nodes.classifier)
if idx is not None:
ci = subitem[idx]
if len(ci.children) > 0:
classifier = ci.children[0].astext()
if classifier is not None and classifier not in (
'@replace', '@before', '@after'):
raise Exception('Unknown classifier: %s' % classifier)
idx = subitem.first_child_matching_class(nodes.term)
if idx is not None:
ch = subitem[idx]
if len(ch.children) > 0:
term = ch.children[0].astext()
idx = subitem.first_child_matching_class(nodes.definition)
if idx is not None:
def_node = subitem[idx]
def_node.attributes['classifier'] = classifier
definitions[term] = def_node
return definitions
def print_arg_list(data, nested_content):
definitions = map_nested_definitions(nested_content)
items = []
if 'args' in data:
for arg in data['args']:
my_def = [nodes.paragraph(text=arg['help'])] if arg['help'] else []
name = arg['name']
my_def = apply_definition(definitions, my_def, name)
if len(my_def) == 0:
my_def.append(nodes.paragraph(text='Undocumented'))
if 'choices' in arg:
my_def.append(nodes.paragraph(
text=('Possible choices: %s' % ', '.join([str(c) for c in arg['choices']]))))
items.append(
nodes.option_list_item(
'', nodes.option_group('', nodes.option_string(text=name)),
nodes.description('', *my_def)))
return nodes.option_list('', *items) if items else None
def print_opt_list(data, nested_content):
definitions = map_nested_definitions(nested_content)
items = []
if 'options' in data:
for opt in data['options']:
names = []
my_def = [nodes.paragraph(text=opt['help'])] if opt['help'] else []
for name in opt['name']:
option_declaration = [nodes.option_string(text=name)]
if opt['default'] is not None \
and opt['default'] != '==SUPPRESS==':
option_declaration += nodes.option_argument(
'', text='=' + str(opt['default']))
names.append(nodes.option('', *option_declaration))
my_def = apply_definition(definitions, my_def, name)
if len(my_def) == 0:
my_def.append(nodes.paragraph(text='Undocumented'))
if 'choices' in opt:
my_def.append(nodes.paragraph(
text=('Possible choices: %s' % ', '.join([str(c) for c in opt['choices']]))))
items.append(
nodes.option_list_item(
'', nodes.option_group('', *names),
nodes.description('', *my_def)))
return nodes.option_list('', *items) if items else None
def print_command_args_and_opts(arg_list, opt_list, sub_list=None):
items = []
if arg_list:
items.append(nodes.definition_list_item(
'', nodes.term(text='Positional arguments:'),
nodes.definition('', arg_list)))
if opt_list:
items.append(nodes.definition_list_item(
'', nodes.term(text='Options:'),
nodes.definition('', opt_list)))
if sub_list and len(sub_list):
items.append(nodes.definition_list_item(
'', nodes.term(text='Sub-commands:'),
nodes.definition('', sub_list)))
return nodes.definition_list('', *items)
def apply_definition(definitions, my_def, name):
if name in definitions:
definition = definitions[name]
classifier = definition['classifier']
if classifier == '@replace':
return definition.children
if classifier == '@after':
return my_def + definition.children
if classifier == '@before':
return definition.children + my_def
raise Exception('Unknown classifier: %s' % classifier)
return my_def
def print_subcommand_list(data, nested_content):
definitions = map_nested_definitions(nested_content)
items = []
if 'children' in data:
for child in data['children']:
my_def = [nodes.paragraph(
text=child['help'])] if child['help'] else []
name = child['name']
my_def = apply_definition(definitions, my_def, name)
if len(my_def) == 0:
my_def.append(nodes.paragraph(text='Undocumented'))
if 'description' in child:
my_def.append(nodes.paragraph(text=child['description']))
my_def.append(nodes.literal_block(text=child['usage']))
my_def.append(print_command_args_and_opts(
print_arg_list(child, nested_content),
print_opt_list(child, nested_content),
print_subcommand_list(child, nested_content)
))
items.append(
nodes.definition_list_item(
'',
nodes.term('', '', nodes.strong(text=name)),
nodes.definition('', *my_def)
)
)
return nodes.definition_list('', *items)
class ArgParseDirective(Directive):
has_content = True
option_spec = dict(module=unchanged, func=unchanged, ref=unchanged,
prog=unchanged, path=unchanged, nodefault=flag,
manpage=unchanged, nosubcommands=unchanged, passparser=flag)
def _construct_manpage_specific_structure(self, parser_info):
"""
Construct a typical man page consisting of the following elements:
NAME (automatically generated, out of our control)
SYNOPSIS
DESCRIPTION
OPTIONS
FILES
SEE ALSO
BUGS
"""
# SYNOPSIS section
synopsis_section = nodes.section(
'',
nodes.title(text='Synopsis'),
nodes.literal_block(text=parser_info["bare_usage"]),
ids=['synopsis-section'])
# DESCRIPTION section
description_section = nodes.section(
'',
nodes.title(text='Description'),
nodes.paragraph(text=parser_info.get(
'description', parser_info.get(
'help', "undocumented").capitalize())),
ids=['description-section'])
nested_parse_with_titles(
self.state, self.content, description_section)
if parser_info.get('epilog'):
# TODO: do whatever sphinx does to understand ReST inside
# docstrings magically imported from other places. The nested
# parse method invoked above seem to be able to do this but
# I haven't found a way to do it for arbitrary text
description_section += nodes.paragraph(
text=parser_info['epilog'])
# OPTIONS section
options_section = nodes.section(
'',
nodes.title(text='Options'),
ids=['options-section'])
if 'args' in parser_info:
options_section += nodes.paragraph()
options_section += nodes.subtitle(text='Positional arguments:')
options_section += self._format_positional_arguments(parser_info)
if 'options' in parser_info:
options_section += nodes.paragraph()
options_section += nodes.subtitle(text='Optional arguments:')
options_section += self._format_optional_arguments(parser_info)
items = [
# NOTE: we cannot generate NAME ourselves. It is generated by
# docutils.writers.manpage
synopsis_section,
description_section,
# TODO: files
# TODO: see also
# TODO: bugs
]
if len(options_section.children) > 1:
items.append(options_section)
if 'nosubcommands' not in self.options:
# SUBCOMMANDS section (non-standard)
subcommands_section = nodes.section(
'',
nodes.title(text='Sub-Commands'),
ids=['subcommands-section'])
if 'children' in parser_info:
subcommands_section += self._format_subcommands(parser_info)
if len(subcommands_section) > 1:
items.append(subcommands_section)
if os.getenv("INCLUDE_DEBUG_SECTION"):
import json
# DEBUG section (non-standard)
debug_section = nodes.section(
'',
nodes.title(text="Argparse + Sphinx Debugging"),
nodes.literal_block(text=json.dumps(parser_info, indent=' ')),
ids=['debug-section'])
items.append(debug_section)
return items
def _format_positional_arguments(self, parser_info):
assert 'args' in parser_info
items = []
for arg in parser_info['args']:
arg_items = []
if arg['help']:
arg_items.append(nodes.paragraph(text=arg['help']))
else:
arg_items.append(nodes.paragraph(text='Undocumented'))
if 'choices' in arg:
arg_items.append(
nodes.paragraph(
text='Possible choices: ' + ', '.join(arg['choices'])))
items.append(
nodes.option_list_item(
'',
nodes.option_group(
'', nodes.option(
'', nodes.option_string(text=arg['metavar'])
)
),
nodes.description('', *arg_items)))
return nodes.option_list('', *items)
def _format_optional_arguments(self, parser_info):
assert 'options' in parser_info
items = []
for opt in parser_info['options']:
names = []
opt_items = []
for name in opt['name']:
option_declaration = [nodes.option_string(text=name)]
if opt['default'] is not None \
and opt['default'] != '==SUPPRESS==':
option_declaration += nodes.option_argument(
'', text='=' + str(opt['default']))
names.append(nodes.option('', *option_declaration))
if opt['help']:
opt_items.append(nodes.paragraph(text=opt['help']))
else:
opt_items.append(nodes.paragraph(text='Undocumented'))
if 'choices' in opt:
opt_items.append(
nodes.paragraph(
text='Possible choices: ' + ', '.join(opt['choices'])))
items.append(
nodes.option_list_item(
'', nodes.option_group('', *names),
nodes.description('', *opt_items)))
return nodes.option_list('', *items)
def _format_subcommands(self, parser_info):
assert 'children' in parser_info
items = []
for subcmd in parser_info['children']:
subcmd_items = []
if subcmd['help']:
subcmd_items.append(nodes.paragraph(text=subcmd['help']))
else:
subcmd_items.append(nodes.paragraph(text='Undocumented'))
items.append(
nodes.definition_list_item(
'',
nodes.term('', '', nodes.strong(
text=subcmd['bare_usage'])),
nodes.definition('', *subcmd_items)))
return nodes.definition_list('', *items)
def _nested_parse_paragraph(self, text):
content = nodes.paragraph()
self.state.nested_parse(StringList(text.split("\n")), 0, content)
return content
def run(self):
if 'module' in self.options and 'func' in self.options:
module_name = self.options['module']
attr_name = self.options['func']
elif 'ref' in self.options:
_parts = self.options['ref'].split('.')
module_name = '.'.join(_parts[0:-1])
attr_name = _parts[-1]
else:
raise self.error(
':module: and :func: should be specified, or :ref:')
mod = __import__(module_name, globals(), locals(), [attr_name])
if not hasattr(mod, attr_name):
raise self.error((
'Module "%s" has no attribute "%s"\n'
'Incorrect argparse :module: or :func: values?'
) % (module_name, attr_name))
func = getattr(mod, attr_name)
if isinstance(func, ArgumentParser):
parser = func
elif 'passparser' in self.options:
parser = ArgumentParser()
func(parser)
else:
parser = func()
if 'path' not in self.options:
self.options['path'] = ''
path = str(self.options['path'])
if 'prog' in self.options:
parser.prog = self.options['prog']
result = parse_parser(
parser, skip_default_values='nodefault' in self.options)
result = parser_navigate(result, path)
if 'manpage' in self.options:
return self._construct_manpage_specific_structure(result)
nested_content = nodes.paragraph()
self.state.nested_parse(
self.content, self.content_offset, nested_content)
nested_content = nested_content.children
items = []
# add common content between
for item in nested_content:
if not isinstance(item, nodes.definition_list):
items.append(item)
if 'description' in result:
items.append(self._nested_parse_paragraph(result['description']))
items.append(nodes.literal_block(text=result['usage']))
items.append(print_command_args_and_opts(
print_arg_list(result, nested_content),
print_opt_list(result, nested_content),
print_subcommand_list(result, nested_content)
))
if 'epilog' in result:
items.append(self._nested_parse_paragraph(result['epilog']))
return items
def setup(app):
app.add_directive('argparse', ArgParseDirective)

View File

@ -1,138 +0,0 @@
# Copyright (c) 2013 Alex Rudakov
#
# 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 argparse import _HelpAction, _SubParsersAction
import re
class NavigationException(Exception):
pass
def parser_navigate(parser_result, path, current_path=None):
if isinstance(path, str):
if path == '':
return parser_result
path = re.split('\s+', path)
current_path = current_path or []
if len(path) == 0:
return parser_result
if 'children' not in parser_result:
raise NavigationException(
'Current parser have no children elements. (path: %s)' %
' '.join(current_path))
next_hop = path.pop(0)
for child in parser_result['children']:
if child['name'] == next_hop:
current_path.append(next_hop)
return parser_navigate(child, path, current_path)
raise NavigationException(
'Current parser have no children element with name: %s (path: %s)' % (
next_hop, ' '.join(current_path)))
def _try_add_parser_attribute(data, parser, attribname):
attribval = getattr(parser, attribname, None)
if attribval is None:
return
if not isinstance(attribval, str):
return
if len(attribval) > 0:
data[attribname] = attribval
def _format_usage_without_prefix(parser):
"""
Use private argparse APIs to get the usage string without
the 'usage: ' prefix.
"""
fmt = parser._get_formatter()
fmt.add_usage(parser.usage, parser._actions,
parser._mutually_exclusive_groups, prefix='')
return fmt.format_help().strip()
def parse_parser(parser, data=None, **kwargs):
if data is None:
data = {
'name': '',
'usage': parser.format_usage().strip(),
'bare_usage': _format_usage_without_prefix(parser),
'prog': parser.prog,
}
_try_add_parser_attribute(data, parser, 'description')
_try_add_parser_attribute(data, parser, 'epilog')
for action in parser._get_positional_actions():
if isinstance(action, _HelpAction):
continue
if isinstance(action, _SubParsersAction):
helps = {}
for item in action._choices_actions:
helps[item.dest] = item.help
# commands which share an existing parser are an alias,
# don't duplicate docs
subsection_alias = {}
subsection_alias_names = set()
for name, subaction in action._name_parser_map.items():
if subaction not in subsection_alias:
subsection_alias[subaction] = []
else:
subsection_alias[subaction].append(name)
subsection_alias_names.add(name)
for name, subaction in action._name_parser_map.items():
if name in subsection_alias_names:
continue
subalias = subsection_alias[subaction]
subaction.prog = '%s %s' % (parser.prog, name)
subdata = {
'name': name if not subalias else
'%s (%s)' % (name, ', '.join(subalias)),
'help': helps.get(name, ''),
'usage': subaction.format_usage().strip(),
'bare_usage': _format_usage_without_prefix(subaction),
}
parse_parser(subaction, subdata, **kwargs)
data.setdefault('children', []).append(subdata)
continue
if 'args' not in data:
data['args'] = []
arg = {
'name': action.dest,
'help': action.help or '',
'metavar': action.metavar
}
if action.choices:
arg['choices'] = action.choices
data['args'].append(arg)
show_defaults = (
('skip_default_values' not in kwargs)
or (kwargs['skip_default_values'] is False))
for action in parser._get_optional_actions():
if isinstance(action, _HelpAction):
continue
if 'options' not in data:
data['options'] = []
option = {
'name': action.option_strings,
'default': action.default if show_defaults else '==SUPPRESS==',
'help': action.help or ''
}
if action.choices:
option['choices'] = action.choices
if "==SUPPRESS==" not in option['help']:
data['options'].append(option)
return data

View File

@ -1,7 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
openstackdocstheme>=2.2.1 # Apache-2.0
reno>=3.1.0 # Apache-2.0
sphinx>=2.0.0,!=2.1.0 # BSD
python-saharaclient>=1.1.0

View File

@ -1,11 +0,0 @@
<h3>Useful Links</h3>
<ul>
<li><a href="https://wiki.openstack.org/wiki/Sahara">Sahara @ OpenStack Wiki</a></li>
<li><a href="https://storyboard.openstack.org/#!/project_group/74">Sahara @ Storyboard</a></li>
</ul>
{% if READTHEDOCS %}
<script type='text/javascript'>
$('div.body').css('margin', 0)
</script>
{% endif %}

View File

@ -1,4 +0,0 @@
{% extends "basic/layout.html" %}
{% set css_files = css_files + ['_static/tweaks.css'] %}
{% block relbar1 %}{% endblock relbar1 %}

View File

@ -1,4 +0,0 @@
[theme]
inherit = nature
stylesheet = nature.css
pygments_style = tango

View File

@ -1,9 +0,0 @@
=================
Sahara CLI client
=================
.. toctree::
:maxdepth: 2
intro
reference

View File

@ -1,64 +0,0 @@
Introduction
============
The Sahara shell utility now is part of the OpenStackClient, so all
shell commands take the following form:
.. code-block:: bash
$ openstack dataprocessing <command> [arguments...]
To get a list of all possible commands you can run:
.. code-block:: bash
$ openstack help dataprocessing
To get detailed help for the command you can run:
.. code-block:: bash
$ openstack help dataprocessing <command>
For more information about commands and their parameters you can refer to
:doc:`the Sahara CLI commands <reference>`.
For more information about abilities and features of OpenStackClient CLI you
can refer to `OpenStackClient documentation <https://docs.openstack.org/python-openstackclient/latest/>`_
Configuration
-------------
The CLI is configured via environment variables and command-line options which
are described in https://docs.openstack.org/python-openstackclient/latest/cli/authentication.html.
Authentication using username/password is most commonly used and can be
provided with environment variables:
.. code-block:: bash
export OS_AUTH_URL=<url-to-openstack-identity>
export OS_PROJECT_NAME=<project-name>
export OS_USERNAME=<username>
export OS_PASSWORD=<password> # (optional)
or command-line options:
.. code-block:: bash
--os-auth-url <url>
--os-project-name <project-name>
--os-username <username>
[--os-password <password>]
Additionally :program:`sahara` API url can be configured with parameter:
.. code-block:: bash
--os-data-processing-url
or with environment variable:
.. code-block:: bash
export OS_DATA_PROCESSING_URL=<url-to-sahara-API>

View File

@ -1,64 +0,0 @@
CLI Reference
=============
The following commands are currently supported by the Sahara CLI:
Plugins
-------
.. cli::
:module: saharaclient.osc.v1.plugins
Images
------
.. cli::
:module: saharaclient.osc.v1.images
Node Group Templates
--------------------
.. cli::
:module: saharaclient.osc.v1.node_group_templates
Cluster Templates
-----------------
.. cli::
:module: saharaclient.osc.v1.cluster_templates
Clusters
--------
.. cli::
:module: saharaclient.osc.v1.clusters
Data Sources
------------
.. cli::
:module: saharaclient.osc.v1.data_sources
Job Binaries
------------
.. cli::
:module: saharaclient.osc.v1.job_binaries
Job Types
---------
.. cli::
:module: saharaclient.osc.v1.job_types
Job Templates
-------------
.. cli::
:module: saharaclient.osc.v1.job_templates
Jobs
----
.. cli::
:module: saharaclient.osc.v1.jobs

View File

@ -1,258 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2013 Mirantis Inc.
#
# 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 sys
import os
import warnings
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
# 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('.'))
sys.path.insert(0, os.path.abspath('../../saharaclient'))
sys.path.append(os.path.abspath('..'))
sys.path.append(os.path.abspath('../bin'))
# -- 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.doctest', 'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.viewcode', 'ext.cli',
'openstackdocstheme']
# openstackdocstheme options
openstackdocs_repo_name = 'openstack/python-saharaclient'
openstackdocs_use_storyboard = True
openstackdocs_auto_name = False
# 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 = 'Sahara Client'
copyright = '2013, OpenStack Foundation'
# 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 = []
# 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 = 'native'
# 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 = 'openstackdocs'
if on_rtd:
html_theme_path = ['.']
html_theme = '_theme_rtd'
# 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
# "<project> v<release> documentation".
html_title = 'Sahara Client'
# 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 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 = {
'index': ['sidebarlinks.html', 'localtoc.html', 'searchbox.html', 'sourcelink.html'],
'**': ['localtoc.html', 'relations.html',
'searchbox.html', 'sourcelink.html']
}
# 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 <link> 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 = 'SaharaClientDoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'saharaclientdoc.tex', 'Sahara Client',
'OpenStack Foundation', '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
# 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', 'saharaclient', 'Sahara Client',
['OpenStack Foundation'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'Sahara Client', 'Sahara Client',
'OpenStack Foundation', 'Sahara Client', 'Sahara Client',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'

View File

@ -1,14 +0,0 @@
============================
So You Want to Contribute...
============================
For general information on contributing to OpenStack, please check out the
`contributor guide <https://docs.openstack.org/contributors/>`_ to get started.
It covers all the basics that are common to all OpenStack projects: the
accounts you need, the basics of interacting with our Gerrit review system, how
we communicate as a community, etc.
python-saharaclient is maintained by the OpenStack Sahara project.
To understand our development process and how you can contribute to it, please
look at the Sahara project's general contributor's page:
https://docs.openstack.org/sahara/latest/contributor/contributing.html

View File

@ -1,8 +0,0 @@
=================
Contributor Guide
=================
.. toctree::
:maxdepth: 2
contributing

View File

@ -1,25 +0,0 @@
Python bindings to the OpenStack Sahara API
===========================================
This is a client for OpenStack Sahara API. There's :doc:`a Python API client
<reference/index>` (the :mod:`saharaclient` module), and a :doc:`command-line utility
<cli/index>` (installed as an OpenStackClient plugin). Each implements the entire
OpenStack Sahara API.
You'll need credentials for an OpenStack cloud that implements the
Data Processing API, in order to use the sahara client.
You may want to read the `OpenStack Sahara Docs`__ -- the overview, at
least -- to get an idea of the concepts. By understanding the concepts
this library should make more sense.
__ https://docs.openstack.org/sahara/latest/
Contents:
.. toctree::
:maxdepth: 2
reference/index
cli/index
contributor/index

View File

@ -1,9 +0,0 @@
===============
Reference guide
===============
.. toctree::
:maxdepth: 2
pythonclient
pythonclient_v2

View File

@ -1,177 +0,0 @@
Python Sahara client
====================
Overview
--------
Sahara Client provides a list of Python interfaces to communicate with the
Sahara REST API. Sahara Client enables users to perform most of the existing
operations like retrieving template lists, creating Clusters, submitting EDP
Jobs, etc.
Instantiating a Client
----------------------
To start using the Sahara Client users have to create an instance of the
`Client` class. The client constructor has a list of parameters to authenticate
and locate Sahara endpoint.
.. autoclass:: saharaclient.api.client.Client
:members:
**Important!**
It is not a mandatory rule to provide all of the parameters above. The minimum
number should be enough to determine Sahara endpoint, check user
authentication and tenant to operate in.
Authentication check
~~~~~~~~~~~~~~~~~~~~
Passing authentication parameters to Sahara Client is deprecated. Keystone
Session object should be used for this purpose. For example:
.. sourcecode:: python
from keystoneauth1.identity import v2
from keystoneauth1 import session
from saharaclient import client
auth = v2.Password(auth_url=AUTH_URL,
username=USERNAME,
password=PASSWORD,
tenant_name=PROJECT_ID)
ses = session.Session(auth=auth)
sahara = client.Client('1.1', session=ses)
..
For more information about Keystone Sessions, see `Using Sessions`_.
.. _Using Sessions: https://docs.openstack.org/python-keystoneclient/latest/using-sessions.html
Sahara endpoint discovery
~~~~~~~~~~~~~~~~~~~~~~~~~
If user has a direct URL pointing to Sahara REST API, it may be specified as
`sahara_url`. If this parameter is missing, Sahara client will use Keystone
Service Catalog to find the endpoint. There are two parameters: `service_type`
and `endpoint_type` to configure endpoint search. Both parameters have
default values.
.. sourcecode:: python
from keystoneauth1.identity import v2
from keystoneauth1 import session
from saharaclient import client
auth = v2.Password(auth_url=AUTH_URL,
username=USERNAME,
password=PASSWORD,
tenant_name=PROJECT_ID)
ses = session.Session(auth=auth)
sahara = client.Client('1.1', session=ses,
service_type="non-default-service-type",
endpoint_type="internalURL")
..
Object managers
---------------
Sahara Client has a list of fields to operate with:
* plugins
* clusters
* cluster_templates
* node_group_templates
* images
* data_sources
* job_binaries
* job_binary_internals
* job_executions
* job_types
Each of this fields is a reference to a Manager for a corresponding group of
REST calls.
Supported operations
--------------------
Plugin ops
~~~~~~~~~~
.. autoclass:: saharaclient.api.plugins.PluginManagerV1
:members:
:inherited-members:
Image Registry ops
~~~~~~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.images.ImageManagerV1
:members:
:inherited-members:
Node Group Template ops
~~~~~~~~~~~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.node_group_templates.NodeGroupTemplateManagerV1
:members:
:inherited-members:
Cluster Template ops
~~~~~~~~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.cluster_templates.ClusterTemplateManagerV1
:members:
:inherited-members:
Cluster ops
~~~~~~~~~~~
.. autoclass:: saharaclient.api.clusters.ClusterManagerV1
:members:
:inherited-members:
Data Source ops
~~~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.data_sources.DataSourceManagerV1
:members:
:inherited-members:
Job Binary Internal ops
~~~~~~~~~~~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.job_binary_internals.JobBinaryInternalsManager
:members: create, update
Job Binary ops
~~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.job_binaries.JobBinariesManagerV1
:members:
:inherited-members:
Job ops
~~~~~~~
.. autoclass:: saharaclient.api.jobs.JobsManagerV1
:members:
:inherited-members:
Job Execution ops
~~~~~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.job_executions.JobExecutionsManager
:members:
:inherited-members:
Job Types ops
~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.job_types.JobTypesManager
:members:
:inherited-members:

View File

@ -1,82 +0,0 @@
Python Sahara client for APIv2
==============================
Overview
--------
There is also support for Sahara's experimental APIv2.
Supported operations
--------------------
Plugin ops
~~~~~~~~~~
.. autoclass:: saharaclient.api.plugins.PluginManagerV2
:members:
:inherited-members:
Image Registry ops
~~~~~~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.images.ImageManagerV2
:members:
:inherited-members:
Node Group Template ops
~~~~~~~~~~~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.node_group_templates.NodeGroupTemplateManagerV2
:members:
:inherited-members:
Cluster Template ops
~~~~~~~~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.cluster_templates.ClusterTemplateManagerV2
:members:
:inherited-members:
Cluster ops
~~~~~~~~~~~
.. autoclass:: saharaclient.api.clusters.ClusterManagerV2
:members:
:inherited-members:
Data Source ops
~~~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.data_sources.DataSourceManagerV2
:members:
:inherited-members:
Job Binary ops
~~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.job_binaries.JobBinariesManagerV2
:members:
:inherited-members:
Job Template ops
~~~~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.v2.job_templates.JobTemplatesManagerV2
:members:
:inherited-members:
Job ops
~~~~~~~
.. autoclass:: saharaclient.api.v2.jobs.JobsManagerV2
:members:
:inherited-members:
Job Types ops
~~~~~~~~~~~~~
.. autoclass:: saharaclient.api.job_types.JobTypesManager
:members:
:inherited-members:
:noindex:

View File

@ -1,12 +0,0 @@
---
features:
- |
The basic saharaclient and the OSC plugin now include support for
the enhanced boot from volume mechanism introduced in the Stein
release of Sahara, and support for the keypair replacement
mechanism introduced in the Rocky release of Sahara. The OSC plugin
also now includes support for the force deletion of clusters
feature introduced in the Queens release of Sahara, and support
for the decommision of a specific instance feature (albeit only via
the --json flag) introduced in the Queens release of Sahara. (All
of these features are exclusive to Sahara's APIv2.)

View File

@ -1,4 +0,0 @@
---
features:
- >
Automatically generated documentation for saharaclient API was added.

View File

@ -1,4 +0,0 @@
---
features:
- >
Automatically generated documentation for saharaclient CLI was added.

View File

@ -1,4 +0,0 @@
---
deprecations:
- >
Old CLI is deprecated and will not be maintained.

View File

@ -1,4 +0,0 @@
---
features:
- Added integration of Designate for hostname resolution through dns
servers

View File

@ -1,6 +0,0 @@
---
upgrade:
- |
Python 2.7 support has been dropped. Last release of python-saharaclient
to support python 2.7 is OpenStack Train. The minimum version of Python now
supported by python-saharaclient is Python 3.6.

View File

@ -1,4 +0,0 @@
---
features:
- Providing ability to make dump of event logs for clusters.
Also displaying shorten version of event logs by option.

View File

@ -1,3 +0,0 @@
---
features:
- Initial support for Sahara's experimental APIv2 is present.

View File

@ -1,5 +0,0 @@
---
fixes:
- >
[`bug 1534050 <https://bugs.launchpad.net/python-saharaclient/+bug/1534050>`_]
Now object's fields can be unset with ``update`` calls.

View File

@ -1,4 +0,0 @@
---
fixes:
- |
Fix the "job binary download" command when Python 3 is used.

View File

@ -1,6 +0,0 @@
---
fixes:
- |
A change in python-openstackclient 5.2.0 broke the image register command.
The incompatibility is now solved but the python-openstackclient
requirement has been bumped to 5.2.0.

View File

@ -1,4 +0,0 @@
---
features:
- >
Pagination for list operations is implemented.

View File

@ -1,6 +0,0 @@
---
fixes:
- >
[`bug 1508406 <https://bugs.launchpad.net/python-saharaclient/+bug/1508406>`_]
Now ``description`` and ``extra`` parameters of jobs ``create`` method
are optional.

View File

@ -1,6 +0,0 @@
---
fixes:
- >
[`bug 1506448 <https://bugs.launchpad.net/python-saharaclient/+bug/1506448>`_]
Now ``mains``, ``libs`` and ``description`` parameters of jobs ``create``
method are optional.

View File

@ -1,6 +0,0 @@
---
fixes:
- >
[`bug 1507966 <https://bugs.launchpad.net/python-saharaclient/+bug/1507966>`_]
Now input_id, output_id, configs parameters of job executions create
method are optional.

View File

@ -1,4 +0,0 @@
other:
- When using APIv2, the viewing (GET) of specific job templates and jobs and
the creation (POST) of job templates and jobs now only supports the API
behavior of Sahara 9.0.0.0b3 or later.

View File

@ -1,4 +0,0 @@
---
other:
- When using APIv2, the creation of multiple clusters simultaneously
now only supports the API behavior of Sahara 9.0.0.0b2 or later.

View File

@ -1,4 +0,0 @@
---
features:
- >
New CLI as part of the openstackclient was implemented.

View File

@ -1,5 +0,0 @@
---
features:
- |
Adding the ability for the CLI to communicate with OpenStack
Sahara using the new APIv2.

View File

@ -1,4 +0,0 @@
---
features:
- Plugins updates are supported now in saharaclient. Also
information about plugin labels is available for users.

View File

@ -1,4 +0,0 @@
---
prelude: >
Functional tests were replaced to sahara-tests repository. Please refer to
README of sahara-tests about how to run these tests now.

View File

@ -1,7 +0,0 @@
---
prelude: >
Old CLI commands are removed. Please use OpenStackClient
instead.
deprecations:
- Old CLI commands are removed. Please use OpenStackClient
instead.

View File

@ -1,5 +0,0 @@
---
deprecations:
- >
[`bug 1519510 <https://bugs.launchpad.net/python-saharaclient/+bug/1519510>`_]
Support of python 2.6 was dropped.

View File

@ -1,5 +0,0 @@
---
deprecations:
- >
[`bug 1526170 <https://bugs.launchpad.net/python-saharaclient/+bug/1526170>`_]
Support of python 3.3 was dropped.

View File

@ -1,10 +0,0 @@
---
upgrade:
- Option 'version' is replaced by 'plugin-version' option.
fixes:
- Option 'version' is a global option, which is used for getting
the client version. So there were problems with the OpenStack client,
when we specified 'version' of the plugin, but OSC treated
that as a request for getting the current client version. Hence, to fix
this problem, 'version' is replaced by 'plugin-version'.
Related bug 1565775.

View File

@ -1,6 +0,0 @@
---
upgrade:
- |
The Sahara client library now only supports authentication with a Keystone
session object. Consequently the arguments which `saharaclient.api.Client`
accepts, and the order of those arguments, have changed.

View File

@ -1,4 +0,0 @@
---
features:
- >
Now shares can be edited on an existing cluster.

View File

@ -1,4 +0,0 @@
---
other:
- >
Start using reno to manage release notes.

View File

@ -1,5 +0,0 @@
---
fixes:
- >
[`bug 1500790 <https://bugs.launchpad.net/python-saharaclient/+bug/1500790>`_]
Now tags can be added and removed simultaneously in one call.

View File

@ -1,5 +0,0 @@
---
fixes:
- >
[`bug 1510470 <https://bugs.launchpad.net/python-saharaclient/+bug/1510470>`_]
Now ``desc`` parameter of ``update_image`` is optional.

View File

@ -1,6 +0,0 @@
---
fixes:
- >
[`bug 1499697 <https://bugs.launchpad.net/python-saharaclient/+bug/1499697>`_]
Now node group templates can be created and updated with
``volume_mount_prefix`` parameter.

View File

@ -1,6 +0,0 @@
===========================
2023.2 Series Release Notes
===========================
.. release-notes::
:branch: stable/2023.2

View File

@ -1,222 +0,0 @@
# -*- coding: utf-8 -*-
# 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.
# Sahara Client Release Notes documentation build configuration file
extensions = [
'reno.sphinxext',
'openstackdocstheme'
]
# openstackdocstheme options
openstackdocs_repo_name = 'openstack/python-saharaclient'
openstackdocs_use_storyboard = True
openstackdocs_auto_name = False
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Saharaclient Release Notes'
copyright = u'2015, Sahara Developers'
# Release notes are version independent.
# The full version, including alpha/beta/rc tags.
release = ''
# The short X.Y version.
version = ''
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'native'
# -- 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 = 'openstackdocs'
# 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
# "<project> v<release> 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']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
# html_extra_path = []
# 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 <link> 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 = 'SaharaClientReleaseNotesdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'SaharaClientReleaseNotes.tex',
u'Sahara Client Release Notes Documentation',
u'Sahara Client Developers', '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
# 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', 'saharaclientreleasenotes',
u'Sahara Client Release Notes Documentation',
[u'Sahara Developers'], 1)
]
# If true, show URL addresses after external links.
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'SaharaClientReleaseNotes',
u'Sahara Client Release Notes Documentation',
u'Sahara Developers', 'SaharaClientReleaseNotes',
'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
# texinfo_no_detailmenu = False
# -- Options for Internationalization output ------------------------------
locale_dirs = ['locale/']

View File

@ -1,22 +0,0 @@
===========================
Saharaclient Release Notes
===========================
.. toctree::
:maxdepth: 1
unreleased
2023.2
yoga
xena
wallaby
victoria
ussuri
train
stein
rocky
queens
pike
ocata
newton
mitaka

View File

@ -1,6 +0,0 @@
===================================
Mitaka Series Release Notes
===================================
.. release-notes::
:branch: origin/stable/mitaka

View File

@ -1,6 +0,0 @@
===================================
Newton Series Release Notes
===================================
.. release-notes::
:branch: origin/stable/newton

View File

@ -1,6 +0,0 @@
===================================
Ocata Series Release Notes
===================================
.. release-notes::
:branch: origin/stable/ocata

View File

@ -1,6 +0,0 @@
===================================
Pike Series Release Notes
===================================
.. release-notes::
:branch: stable/pike

View File

@ -1,6 +0,0 @@
===================================
Queens Series Release Notes
===================================
.. release-notes::
:branch: stable/queens

View File

@ -1,6 +0,0 @@
===================================
Rocky Series Release Notes
===================================
.. release-notes::
:branch: stable/rocky

View File

@ -1,6 +0,0 @@
===================================
Stein Series Release Notes
===================================
.. release-notes::
:branch: stable/stein

View File

@ -1,6 +0,0 @@
==========================
Train Series Release Notes
==========================
.. release-notes::
:branch: stable/train

View File

@ -1,5 +0,0 @@
==============================
Current Series Release Notes
==============================
.. release-notes::

View File

@ -1,6 +0,0 @@
===========================
Ussuri Series Release Notes
===========================
.. release-notes::
:branch: stable/ussuri

View File

@ -1,6 +0,0 @@
=============================
Victoria Series Release Notes
=============================
.. release-notes::
:branch: stable/victoria

View File

@ -1,6 +0,0 @@
============================
Wallaby Series Release Notes
============================
.. release-notes::
:branch: stable/wallaby

View File

@ -1,6 +0,0 @@
=========================
Xena Series Release Notes
=========================
.. release-notes::
:branch: stable/xena

View File

@ -1,6 +0,0 @@
=========================
Yoga Series Release Notes
=========================
.. release-notes::
:branch: stable/yoga

View File

@ -1,14 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr>=3.1.1 # Apache-2.0
keystoneauth1>=3.4.0 # Apache-2.0
osc-lib>=2.0.0 # Apache-2.0
oslo.log>=5.0.0 # Apache-2.0
oslo.serialization>=2.25.0 # Apache-2.0
oslo.i18n>=3.20.0 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0
python-openstackclient>=5.2.0 # Apache-2.0
requests>=2.14.2 # Apache-2.0

View File

@ -1,19 +0,0 @@
# Copyright 2017 Huawei, Inc. All rights reserved.
#
# 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 saharaclient import version
__version__ = version.version_info.version_string()

View File

@ -1,20 +0,0 @@
# 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.
"""oslo.i18n integration module.
See https://docs.openstack.org/oslo.i18n/latest/user/usage.html
"""
import oslo_i18n
_ = oslo_i18n.TranslatorFactory(domain='saharaclient').primary

View File

@ -1,284 +0,0 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 copy
from oslo_serialization import jsonutils
from urllib import parse
from saharaclient._i18n import _
class Resource(object):
resource_name = 'Something'
defaults = {}
def __init__(self, manager, info):
self.manager = manager
info = info.copy()
self._info = info
self._set_defaults(info)
self._add_details(info)
def _set_defaults(self, info):
for name, value in self.defaults.items():
if name not in info:
info[name] = value
def _add_details(self, info):
for (k, v) in info.items():
try:
setattr(self, k, v)
self._info[k] = v
except AttributeError:
# In this case we already defined the attribute on the class
pass
def to_dict(self):
return copy.deepcopy(self._info)
def __str__(self):
return '%s %s' % (self.resource_name, str(self._info))
def _check_items(obj, searches):
try:
return all(getattr(obj, attr) == value for (attr, value) in searches)
except AttributeError:
return False
class NotUpdated(object):
"""A sentinel class to signal that parameter should not be updated."""
def __repr__(self):
return 'NotUpdated'
class ResourceManager(object):
resource_class = None
def __init__(self, api):
self.api = api
def find(self, **kwargs):
return [i for i in self.list() if _check_items(i, kwargs.items())]
def find_unique(self, **kwargs):
found = self.find(**kwargs)
if not found:
raise APIException(error_code=404,
error_message=_("No matches found."))
if len(found) > 1:
raise APIException(error_code=409,
error_message=_("Multiple matches found."))
return found[0]
def _copy_if_defined(self, data, **kwargs):
for var_name, var_value in kwargs.items():
if var_value is not None:
data[var_name] = var_value
def _copy_if_updated(self, data, **kwargs):
for var_name, var_value in kwargs.items():
if not isinstance(var_value, NotUpdated):
data[var_name] = var_value
def _create(self, url, data, response_key=None, dump_json=True):
if dump_json:
kwargs = {'json': data}
else:
kwargs = {'data': data}
resp = self.api.post(url, **kwargs)
if resp.status_code != 202:
self._raise_api_exception(resp)
if response_key is not None:
data = get_json(resp)[response_key]
else:
data = get_json(resp)
return self.resource_class(self, data)
def _update(self, url, data, response_key=None, dump_json=True):
if dump_json:
kwargs = {'json': data}
else:
kwargs = {'data': data}
resp = self.api.put(url, **kwargs)
if resp.status_code not in [200, 202]:
self._raise_api_exception(resp)
if response_key is not None:
data = get_json(resp)[response_key]
else:
data = get_json(resp)
return self.resource_class(self, data)
def _patch(self, url, data, response_key=None, dump_json=True):
if dump_json:
kwargs = {'json': data}
else:
kwargs = {'data': data}
resp = self.api.patch(url, **kwargs)
if resp.status_code != 202:
self._raise_api_exception(resp)
if response_key is not None:
data = get_json(resp)[response_key]
else:
data = get_json(resp)
return self.resource_class(self, data)
def _post(self, url, data, response_key=None, dump_json=True):
if dump_json:
kwargs = {'json': data}
else:
kwargs = {'data': data}
resp = self.api.post(url, **kwargs)
if resp.status_code != 202:
self._raise_api_exception(resp)
if response_key is not None:
data = get_json(resp)[response_key]
else:
data = get_json(resp)
return self.resource_class(self, data)
def _list(self, url, response_key):
resp = self.api.get(url)
if resp.status_code == 200:
data = get_json(resp)[response_key]
return [self.resource_class(self, res)
for res in data]
else:
self._raise_api_exception(resp)
def _page(self, url, response_key, limit=None):
resp = self.api.get(url)
if resp.status_code == 200:
result = get_json(resp)
data = result[response_key]
meta = result.get('markers')
next, prev = None, None
if meta:
prev = meta.get('prev')
next = meta.get('next')
li = [self.resource_class(self, res)
for res in data]
return Page(li, prev, next, limit)
else:
self._raise_api_exception(resp)
def _get(self, url, response_key=None):
resp = self.api.get(url)
if resp.status_code == 200:
if response_key is not None:
data = get_json(resp)[response_key]
else:
data = get_json(resp)
return self.resource_class(self, data)
else:
self._raise_api_exception(resp)
def _delete(self, url, data=None):
if data is not None:
kwargs = {'json': data}
resp = self.api.delete(url, **kwargs)
else:
resp = self.api.delete(url)
if resp.status_code not in [200, 204]:
self._raise_api_exception(resp)
if resp.status_code == 200:
return get_json(resp)
def _plurify_resource_name(self):
return self.resource_class.resource_name + 's'
def _raise_api_exception(self, resp):
try:
error_data = get_json(resp)
except Exception:
msg = _("Failed to parse response from Sahara: %s") % resp.reason
raise APIException(
error_code=resp.status_code,
error_message=msg)
raise APIException(error_code=error_data.get("error_code"),
error_name=error_data.get("error_name"),
error_message=error_data.get("error_message"))
def get_json(response):
"""Provide backward compatibility with old versions of requests library."""
json_field_or_function = getattr(response, 'json', None)
if callable(json_field_or_function):
return response.json()
else:
return jsonutils.loads(response.content)
class APIException(Exception):
def __init__(self, error_code=None, error_name=None, error_message=None):
super(APIException, self).__init__(error_message)
self.error_code = error_code
self.error_name = error_name
self.error_message = error_message
def get_query_string(search_opts, limit=None, marker=None, sort_by=None,
reverse=None):
opts = {}
if marker is not None:
opts['marker'] = marker
if limit is not None:
opts['limit'] = limit
if sort_by is not None:
if reverse:
opts['sort_by'] = "-%s" % sort_by
else:
opts['sort_by'] = sort_by
if search_opts is not None:
opts.update(search_opts)
if opts:
qparams = sorted(opts.items(), key=lambda x: x[0])
query_string = "?%s" % parse.urlencode(qparams, doseq=True)
else:
query_string = ""
return query_string
class Page(list):
def __init__(self, l, prev, next, limit):
super(Page, self).__init__(l)
self.prev = prev
self.next = next
self.limit = limit

View File

@ -1,116 +0,0 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 keystoneauth1 import adapter
from saharaclient.api import cluster_templates
from saharaclient.api import clusters
from saharaclient.api import data_sources
from saharaclient.api import images
from saharaclient.api import job_binaries
from saharaclient.api import job_binary_internals
from saharaclient.api import job_executions
from saharaclient.api import job_types
from saharaclient.api import jobs
from saharaclient.api import node_group_templates
from saharaclient.api import plugins
from saharaclient.api.v2 import job_templates
from saharaclient.api.v2 import jobs as jobs_v2
USER_AGENT = 'python-saharaclient'
class HTTPClient(adapter.Adapter):
def request(self, *args, **kwargs):
kwargs.setdefault('raise_exc', False)
kwargs.setdefault('allow', {'allow_experimental': True})
return super(HTTPClient, self).request(*args, **kwargs)
class Client(object):
_api_version = '1.1'
"""Client for the OpenStack Data Processing API.
:param session: Keystone session object. Required.
:param string sahara_url: Endpoint override.
:param string endpoint_type: Desired Sahara endpoint type.
:param string service_type: Sahara service name in Keystone catalog.
:param string region_name: Name of a region to select when choosing an
endpoint from the service catalog.
"""
def __init__(self, session=None, sahara_url=None,
endpoint_type='publicURL', service_type='data-processing',
region_name=None, **kwargs):
if not session:
raise RuntimeError("Must provide session")
auth = session.auth
kwargs['user_agent'] = USER_AGENT
kwargs.setdefault('interface', endpoint_type)
kwargs.setdefault('endpoint_override', sahara_url)
client = HTTPClient(session=session,
auth=auth,
service_type=service_type,
region_name=region_name,
version=self._api_version,
**kwargs)
self._register_managers(client)
def _register_managers(self, client):
self.clusters = clusters.ClusterManagerV1(client)
self.cluster_templates = (
cluster_templates.ClusterTemplateManagerV1(client)
)
self.node_group_templates = (
node_group_templates.NodeGroupTemplateManagerV1(client)
)
self.plugins = plugins.PluginManagerV1(client)
self.images = images.ImageManagerV1(client)
self.data_sources = data_sources.DataSourceManagerV1(client)
self.jobs = jobs.JobsManagerV1(client)
self.job_executions = job_executions.JobExecutionsManager(client)
self.job_binaries = job_binaries.JobBinariesManagerV1(client)
self.job_binary_internals = (
job_binary_internals.JobBinaryInternalsManager(client)
)
self.job_types = job_types.JobTypesManager(client)
class ClientV2(Client):
_api_version = '2'
def _register_managers(self, client):
self.clusters = clusters.ClusterManagerV2(client)
self.cluster_templates = (
cluster_templates.ClusterTemplateManagerV2(client)
)
self.node_group_templates = (
node_group_templates.NodeGroupTemplateManagerV2(client)
)
self.plugins = plugins.PluginManagerV2(client)
self.images = images.ImageManagerV2(client)
self.data_sources = data_sources.DataSourceManagerV2(client)
self.job_templates = job_templates.JobTemplatesManagerV2(client)
self.jobs = jobs_v2.JobsManagerV2(client)
self.job_binaries = job_binaries.JobBinariesManagerV2(client)
self.job_types = job_types.JobTypesManager(client)

View File

@ -1,167 +0,0 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 saharaclient.api import base
class ClusterTemplate(base.Resource):
resource_name = 'Cluster Template'
class ClusterTemplateManagerV1(base.ResourceManager):
resource_class = ClusterTemplate
NotUpdated = base.NotUpdated()
def create(self, name, plugin_name, hadoop_version, description=None,
cluster_configs=None, node_groups=None, anti_affinity=None,
net_id=None, default_image_id=None, use_autoconfig=None,
shares=None, is_public=None, is_protected=None,
domain_name=None):
"""Create a Cluster Template."""
data = {
'name': name,
'plugin_name': plugin_name,
'hadoop_version': hadoop_version,
}
return self._do_create(data, description, cluster_configs,
node_groups, anti_affinity, net_id,
default_image_id, use_autoconfig, shares,
is_public, is_protected, domain_name)
def _do_create(self, data, description, cluster_configs, node_groups,
anti_affinity, net_id, default_image_id, use_autoconfig,
shares, is_public, is_protected, domain_name):
self._copy_if_defined(data,
description=description,
cluster_configs=cluster_configs,
node_groups=node_groups,
anti_affinity=anti_affinity,
neutron_management_network=net_id,
default_image_id=default_image_id,
use_autoconfig=use_autoconfig,
shares=shares,
is_public=is_public,
is_protected=is_protected,
domain_name=domain_name)
return self._create('/cluster-templates', data, 'cluster_template')
def update(self, cluster_template_id, name=NotUpdated,
plugin_name=NotUpdated, hadoop_version=NotUpdated,
description=NotUpdated, cluster_configs=NotUpdated,
node_groups=NotUpdated, anti_affinity=NotUpdated,
net_id=NotUpdated, default_image_id=NotUpdated,
use_autoconfig=NotUpdated, shares=NotUpdated,
is_public=NotUpdated, is_protected=NotUpdated,
domain_name=NotUpdated):
"""Update a Cluster Template."""
data = {}
self._copy_if_updated(data, name=name,
plugin_name=plugin_name,
hadoop_version=hadoop_version,
description=description,
cluster_configs=cluster_configs,
node_groups=node_groups,
anti_affinity=anti_affinity,
neutron_management_network=net_id,
default_image_id=default_image_id,
use_autoconfig=use_autoconfig,
shares=shares,
is_public=is_public,
is_protected=is_protected,
domain_name=domain_name)
return self._update('/cluster-templates/%s' % cluster_template_id,
data, 'cluster_template')
def list(self, search_opts=None, marker=None,
limit=None, sort_by=None, reverse=None):
"""Get list of Cluster Templates."""
query = base.get_query_string(search_opts, marker=marker, limit=limit,
sort_by=sort_by, reverse=reverse)
url = "/cluster-templates%s" % query
return self._page(url, 'cluster_templates', limit)
def get(self, cluster_template_id):
"""Get information about a Cluster Template."""
return self._get('/cluster-templates/%s' % cluster_template_id,
'cluster_template')
def delete(self, cluster_template_id):
"""Delete a Cluster Template."""
self._delete('/cluster-templates/%s' % cluster_template_id)
def export(self, cluster_template_id):
"""Export a Cluster Template."""
return self._get('/cluster-templates/%s/export' % cluster_template_id)
class ClusterTemplateManagerV2(ClusterTemplateManagerV1):
NotUpdated = base.NotUpdated()
def create(self, name, plugin_name, plugin_version, description=None,
cluster_configs=None, node_groups=None, anti_affinity=None,
net_id=None, default_image_id=None, use_autoconfig=None,
shares=None, is_public=None, is_protected=None,
domain_name=None):
"""Create a Cluster Template."""
data = {
'name': name,
'plugin_name': plugin_name,
'plugin_version': plugin_version
}
return self._do_create(data, description, cluster_configs,
node_groups, anti_affinity, net_id,
default_image_id, use_autoconfig, shares,
is_public, is_protected, domain_name)
def update(self, cluster_template_id, name=NotUpdated,
plugin_name=NotUpdated, plugin_version=NotUpdated,
description=NotUpdated, cluster_configs=NotUpdated,
node_groups=NotUpdated, anti_affinity=NotUpdated,
net_id=NotUpdated, default_image_id=NotUpdated,
use_autoconfig=NotUpdated, shares=NotUpdated,
is_public=NotUpdated, is_protected=NotUpdated,
domain_name=NotUpdated):
"""Update a Cluster Template."""
data = {}
self._copy_if_updated(data, name=name,
plugin_name=plugin_name,
plugin_version=plugin_version,
description=description,
cluster_configs=cluster_configs,
node_groups=node_groups,
anti_affinity=anti_affinity,
neutron_management_network=net_id,
default_image_id=default_image_id,
use_autoconfig=use_autoconfig,
shares=shares,
is_public=is_public,
is_protected=is_protected,
domain_name=domain_name)
return self._patch('/cluster-templates/%s' % cluster_template_id,
data, 'cluster_template')
# NOTE(jfreud): keep this around for backwards compatibility
ClusterTemplateManager = ClusterTemplateManagerV1

View File

@ -1,223 +0,0 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 urllib import parse
from saharaclient.api import base
class Cluster(base.Resource):
resource_name = 'Cluster'
class ClusterManagerV1(base.ResourceManager):
resource_class = Cluster
NotUpdated = base.NotUpdated()
def create(self, name, plugin_name, hadoop_version,
cluster_template_id=None, default_image_id=None,
is_transient=None, description=None, cluster_configs=None,
node_groups=None, user_keypair_id=None,
anti_affinity=None, net_id=None, count=None,
use_autoconfig=None, shares=None,
is_public=None, is_protected=None):
"""Launch a Cluster."""
data = {
'name': name,
'plugin_name': plugin_name,
'hadoop_version': hadoop_version,
}
return self._do_create(data, cluster_template_id, default_image_id,
is_transient, description, cluster_configs,
node_groups, user_keypair_id, anti_affinity,
net_id, count, use_autoconfig, shares,
is_public, is_protected, api_ver=1.1)
def _do_create(self, data, cluster_template_id, default_image_id,
is_transient, description, cluster_configs, node_groups,
user_keypair_id, anti_affinity, net_id, count,
use_autoconfig, shares, is_public, is_protected, api_ver):
# Checking if count is greater than 1, otherwise we set it to None
# so the created dict in the _copy_if_defined method does not contain
# the count parameter.
if count and count <= 1:
count = None
self._copy_if_defined(data,
cluster_template_id=cluster_template_id,
is_transient=is_transient,
default_image_id=default_image_id,
description=description,
cluster_configs=cluster_configs,
node_groups=node_groups,
user_keypair_id=user_keypair_id,
anti_affinity=anti_affinity,
neutron_management_network=net_id,
count=count,
use_autoconfig=use_autoconfig,
shares=shares,
is_public=is_public,
is_protected=is_protected)
if count:
if api_ver >= 2:
return self._create('/clusters', data)
else:
return self._create('/clusters/multiple', data)
return self._create('/clusters', data, 'cluster')
def scale(self, cluster_id, scale_object):
"""Scale an existing Cluster.
:param scale_object: dict that describes scaling operation
:Example:
The following `scale_object` can be used to change the number of
instances in the node group and add instances of new node group to
existing cluster:
.. sourcecode:: json
{
"add_node_groups": [
{
"count": 3,
"name": "new_ng",
"node_group_template_id": "ngt_id"
}
],
"resize_node_groups": [
{
"count": 2,
"name": "old_ng"
}
]
}
"""
return self._update('/clusters/%s' % cluster_id, scale_object)
def list(self, search_opts=None, limit=None, marker=None,
sort_by=None, reverse=None):
"""Get a list of Clusters."""
query = base.get_query_string(search_opts, limit=limit, marker=marker,
sort_by=sort_by, reverse=reverse)
url = "/clusters%s" % query
return self._page(url, 'clusters', limit)
def get(self, cluster_id, show_progress=False):
"""Get information about a Cluster."""
url = ('/clusters/%(cluster_id)s?%(params)s' %
{"cluster_id": cluster_id,
"params": parse.urlencode({"show_progress": show_progress})})
return self._get(url, 'cluster')
def delete(self, cluster_id):
"""Delete a Cluster."""
self._delete('/clusters/%s' % cluster_id)
def update(self, cluster_id, name=NotUpdated, description=NotUpdated,
is_public=NotUpdated, is_protected=NotUpdated,
shares=NotUpdated):
"""Update a Cluster."""
data = {}
self._copy_if_updated(data, name=name, description=description,
is_public=is_public, is_protected=is_protected,
shares=shares)
return self._patch('/clusters/%s' % cluster_id, data)
def verification_update(self, cluster_id, status):
"""Start a verification for a Cluster."""
data = {'verification': {'status': status}}
return self._patch("/clusters/%s" % cluster_id, data)
class ClusterManagerV2(ClusterManagerV1):
def create(self, name, plugin_name, plugin_version,
cluster_template_id=None, default_image_id=None,
is_transient=None, description=None, cluster_configs=None,
node_groups=None, user_keypair_id=None,
anti_affinity=None, net_id=None, count=None,
use_autoconfig=None, shares=None,
is_public=None, is_protected=None):
"""Launch a Cluster."""
data = {
'name': name,
'plugin_name': plugin_name,
'plugin_version': plugin_version,
}
return self._do_create(data, cluster_template_id, default_image_id,
is_transient, description, cluster_configs,
node_groups, user_keypair_id, anti_affinity,
net_id, count, use_autoconfig, shares,
is_public, is_protected, api_ver=2)
def scale(self, cluster_id, scale_object):
"""Scale an existing Cluster.
:param scale_object: dict that describes scaling operation
:Example:
The following `scale_object` can be used to change the number of
instances in the node group (optionally specifiying which instances to
delete) or add instances of a new node group to an existing cluster:
.. sourcecode:: json
{
"add_node_groups": [
{
"count": 3,
"name": "new_ng",
"node_group_template_id": "ngt_id"
}
],
"resize_node_groups": [
{
"count": 2,
"name": "old_ng",
"instances": ["instance_id1", "instance_id2"]
}
]
}
"""
return self._update('/clusters/%s' % cluster_id, scale_object)
def force_delete(self, cluster_id):
"""Force Delete a Cluster."""
data = {'force': True}
return self._delete('/clusters/%s' % cluster_id, data)
def update_keypair(self, cluster_id):
"""Reflect an updated keypair on the cluster."""
data = {'update_keypair': True}
return self._patch("/clusters/%s" % cluster_id, data)
# NOTE(jfreud): keep this around for backwards compatibility
ClusterManager = ClusterManagerV1

View File

@ -1,98 +0,0 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 saharaclient.api import base
class DataSources(base.Resource):
resource_name = 'Data Source'
class DataSourceManagerV1(base.ResourceManager):
resource_class = DataSources
version = 1.1
def create(self, name, description, data_source_type,
url, credential_user=None, credential_pass=None,
is_public=None, is_protected=None, s3_credentials=None):
"""Create a Data Source."""
data = {
'name': name,
'description': description,
'type': data_source_type,
'url': url,
}
credentials = {}
self._copy_if_defined(credentials,
user=credential_user,
password=credential_pass)
credentials = credentials or s3_credentials
self._copy_if_defined(data, is_public=is_public,
is_protected=is_protected,
credentials=credentials)
return self._create('/data-sources', data, 'data_source')
def list(self, search_opts=None, limit=None, marker=None,
sort_by=None, reverse=None):
"""Get a list of Data Sources."""
query = base.get_query_string(search_opts, limit=limit, marker=marker,
sort_by=sort_by, reverse=reverse)
url = "/data-sources%s" % query
return self._page(url, 'data_sources', limit)
def get(self, data_source_id):
"""Get information about a Data Source."""
return self._get('/data-sources/%s' % data_source_id, 'data_source')
def delete(self, data_source_id):
"""Delete a Data Source."""
self._delete('/data-sources/%s' % data_source_id)
def update(self, data_source_id, update_data):
"""Update a Data Source.
:param dict update_data: dict that contains fields that should be
updated with new values.
Fields that can be updated:
* name
* description
* type
* url
* is_public
* is_protected
* credentials - dict with the keys `user` and `password` for data
source in Swift, or with the keys `accesskey`, `secretkey`,
`endpoint`, `ssl`, and `bucket_in_path` for data source in S3
"""
if self.version >= 2:
UPDATE_FUNC = self._patch
else:
UPDATE_FUNC = self._update
return UPDATE_FUNC('/data-sources/%s' % data_source_id,
update_data)
class DataSourceManagerV2(DataSourceManagerV1):
version = 2
# NOTE(jfreud): keep this around for backwards compatibility
DataSourceManager = DataSourceManagerV1

View File

@ -1,76 +0,0 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 saharaclient.api import parameters as params
class Helpers(object):
def __init__(self, sahara_client):
self.sahara = sahara_client
self.plugins = self.sahara.plugins
def _get_node_processes(self, plugin):
processes = []
for proc_lst in plugin.node_processes.values():
processes += proc_lst
return [(proc_name, proc_name) for proc_name in processes]
def get_node_processes(self, plugin_name, hadoop_version):
plugin = self.plugins.get_version_details(plugin_name, hadoop_version)
return self._get_node_processes(plugin)
def _extract_parameters(self, configs, scope, applicable_target):
parameters = []
for config in configs:
if (config['scope'] == scope and
config['applicable_target'] == applicable_target):
parameters.append(params.Parameter(config))
return parameters
def get_cluster_general_configs(self, plugin_name, hadoop_version):
plugin = self.plugins.get_version_details(plugin_name, hadoop_version)
return self._extract_parameters(plugin.configs, 'cluster', "general")
def get_general_node_group_configs(self, plugin_name, hadoop_version):
plugin = self.plugins.get_version_details(plugin_name, hadoop_version)
return self._extract_parameters(plugin.configs, 'node', 'general')
def get_targeted_node_group_configs(self, plugin_name, hadoop_version):
plugin = self.plugins.get_version_details(plugin_name, hadoop_version)
parameters = dict()
for service in plugin.node_processes.keys():
parameters[service] = self._extract_parameters(plugin.configs,
'node', service)
return parameters
def get_targeted_cluster_configs(self, plugin_name, hadoop_version):
plugin = self.plugins.get_version_details(plugin_name, hadoop_version)
parameters = dict()
for service in plugin.node_processes.keys():
parameters[service] = self._extract_parameters(plugin.configs,
'cluster', service)
return parameters

View File

@ -1,92 +0,0 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 saharaclient.api import base
class Image(base.Resource):
resource_name = 'Image'
defaults = {'description': ''}
class _ImageManager(base.ResourceManager):
resource_class = Image
def list(self, search_opts=None):
"""Get a list of registered images."""
query = base.get_query_string(search_opts)
return self._list('/images%s' % query, 'images')
def get(self, id):
"""Get information about an image"""
return self._get('/images/%s' % id, 'image')
def unregister_image(self, image_id):
"""Remove an Image from Sahara Image Registry."""
self._delete('/images/%s' % image_id)
def update_image(self, image_id, user_name, desc=None):
"""Create or update an Image in Image Registry."""
desc = desc if desc else ''
data = {"username": user_name,
"description": desc}
return self._post('/images/%s' % image_id, data)
class ImageManagerV1(_ImageManager):
def update_tags(self, image_id, new_tags):
"""Update an Image tags.
:param new_tags: list of tags that will replace currently
assigned tags
"""
# Do not add :param list in the docstring above until this is solved:
# https://github.com/sphinx-doc/sphinx/issues/2549
old_image = self.get(image_id)
old_tags = frozenset(old_image.tags)
new_tags = frozenset(new_tags)
to_add = list(new_tags - old_tags)
to_remove = list(old_tags - new_tags)
add_response, remove_response = None, None
if to_add:
add_response = self._post('/images/%s/tag' % image_id,
{'tags': to_add}, 'image')
if to_remove:
remove_response = self._post('/images/%s/untag' % image_id,
{'tags': to_remove}, 'image')
return remove_response or add_response or self.get(image_id)
class ImageManagerV2(_ImageManager):
def get_tags(self, image_id):
return self._get('/images/%s/tags' % image_id)
def update_tags(self, image_id, new_tags):
return self._update('/images/%s/tags' % image_id,
{'tags': new_tags})
def delete_tags(self, image_id):
return self._delete('/images/%s/tags' % image_id)
# NOTE(jfreud): keep this around for backwards compatibility
ImageManager = ImageManagerV1

View File

@ -1,103 +0,0 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 saharaclient.api import base
class JobBinaries(base.Resource):
resource_name = 'Job Binary'
class JobBinariesManagerV1(base.ResourceManager):
resource_class = JobBinaries
version = 1.1
def create(self, name, url, description=None, extra=None, is_public=None,
is_protected=None):
"""Create a Job Binary.
:param dict extra: authentication info needed for some job binaries,
containing the keys `user` and `password` for job binary in Swift
or the keys `accesskey`, `secretkey`, and `endpoint` for job
binary in S3
"""
data = {
"name": name,
"url": url
}
self._copy_if_defined(data, description=description, extra=extra,
is_public=is_public, is_protected=is_protected)
return self._create('/job-binaries', data, 'job_binary')
def list(self, search_opts=None, limit=None, marker=None,
sort_by=None, reverse=None):
"""Get a list of Job Binaries."""
query = base.get_query_string(search_opts, limit=limit, marker=marker,
sort_by=sort_by, reverse=reverse)
url = "/job-binaries%s" % query
return self._page(url, 'binaries', limit)
def get(self, job_binary_id):
"""Get information about a Job Binary."""
return self._get('/job-binaries/%s' % job_binary_id, 'job_binary')
def delete(self, job_binary_id):
"""Delete a Job Binary."""
self._delete('/job-binaries/%s' % job_binary_id)
def get_file(self, job_binary_id):
"""Download a Job Binary."""
resp = self.api.get('/job-binaries/%s/data' % job_binary_id)
if resp.status_code != 200:
self._raise_api_exception(resp)
return resp.content
def update(self, job_binary_id, data):
"""Update Job Binary.
:param dict data: dict that contains fields that should be updated
with new values.
Fields that can be updated:
* name
* description
* url
* is_public
* is_protected
* extra - dict with the keys `user` and `password` for job binary
in Swift, or with the keys `accesskey`, `secretkey`, and `endpoint`
for job binary in S3
"""
if self.version >= 2:
UPDATE_FUNC = self._patch
else:
UPDATE_FUNC = self._update
return UPDATE_FUNC(
'/job-binaries/%s' % job_binary_id, data, 'job_binary')
class JobBinariesManagerV2(JobBinariesManagerV1):
version = 2
# NOTE(jfreud): keep this around for backwards compatibility
JobBinariesManager = JobBinariesManagerV1

View File

@ -1,63 +0,0 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 urllib import parse as urlparse
from saharaclient.api import base
class JobBinaryInternal(base.Resource):
resource_name = 'JobBinaryInternal'
class JobBinaryInternalsManager(base.ResourceManager):
resource_class = JobBinaryInternal
NotUpdated = base.NotUpdated()
def create(self, name, data):
"""Create a Job Binary Internal.
:param str data: raw data of script text
"""
return self._update('/job-binary-internals/%s' %
urlparse.quote(name.encode('utf-8')), data,
'job_binary_internal', dump_json=False)
def list(self, search_opts=None, limit=None, marker=None,
sort_by=None, reverse=None):
"""Get a list of Job Binary Internals."""
query = base.get_query_string(search_opts, limit=limit, marker=marker,
sort_by=sort_by, reverse=reverse)
url = "/job-binary-internals%s" % query
return self._page(url, 'binaries', limit)
def get(self, job_binary_id):
"""Get information about a Job Binary Internal."""
return self._get('/job-binary-internals/%s' % job_binary_id,
'job_binary_internal')
def delete(self, job_binary_id):
"""Delete a Job Binary Internal."""
self._delete('/job-binary-internals/%s' % job_binary_id)
def update(self, job_binary_id, name=NotUpdated, is_public=NotUpdated,
is_protected=NotUpdated):
"""Update a Job Binary Internal."""
data = {}
self._copy_if_updated(data, name=name, is_public=is_public,
is_protected=is_protected)
return self._patch('/job-binary-internals/%s' % job_binary_id, data)

View File

@ -1,65 +0,0 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 saharaclient.api import base
class JobExecution(base.Resource):
resource_name = 'JobExecution'
class JobExecutionsManager(base.ResourceManager):
resource_class = JobExecution
NotUpdated = base.NotUpdated()
def list(self, search_opts=None, marker=None, limit=None,
sort_by=None, reverse=None):
"""Get a list of Job Executions."""
query = base.get_query_string(search_opts, limit=limit, marker=marker,
sort_by=sort_by, reverse=reverse)
url = "/job-executions%s" % query
return self._page(url, 'job_executions', limit)
def get(self, obj_id):
"""Get information about a Job Execution."""
return self._get('/job-executions/%s' % obj_id, 'job_execution')
def delete(self, obj_id):
"""Delete a Job Execution."""
self._delete('/job-executions/%s' % obj_id)
def create(self, job_id, cluster_id, input_id=None,
output_id=None, configs=None, interface=None, is_public=None,
is_protected=None):
"""Launch a Job."""
url = "/jobs/%s/execute" % job_id
data = {
"cluster_id": cluster_id,
}
self._copy_if_defined(data, input_id=input_id, output_id=output_id,
job_configs=configs, interface=interface,
is_public=is_public, is_protected=is_protected)
return self._create(url, data, 'job_execution')
def update(self, obj_id, is_public=NotUpdated, is_protected=NotUpdated):
"""Update a Job Execution."""
data = {}
self._copy_if_updated(data, is_public=is_public,
is_protected=is_protected)
return self._patch('/job-executions/%s' % obj_id, data)

View File

@ -1,29 +0,0 @@
# Copyright (c) 2015 Red Hat Inc.
#
# 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 saharaclient.api import base
class JobType(base.Resource):
resource_name = 'JobType'
class JobTypesManager(base.ResourceManager):
resource_class = JobType
def list(self, search_opts=None):
"""Get a list of job types supported by plugins."""
query = base.get_query_string(search_opts)
return self._list('/job-types%s' % query, 'job_types')

View File

@ -1,73 +0,0 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 saharaclient.api import base
class Job(base.Resource):
resource_name = 'Job'
class JobsManagerV1(base.ResourceManager):
resource_class = Job
NotUpdated = base.NotUpdated()
def create(self, name, type, mains=None, libs=None, description=None,
interface=None, is_public=None, is_protected=None):
"""Create a Job."""
data = {
'name': name,
'type': type
}
self._copy_if_defined(data, description=description, mains=mains,
libs=libs, interface=interface,
is_public=is_public, is_protected=is_protected)
return self._create('/jobs', data, 'job')
def list(self, search_opts=None, limit=None,
marker=None, sort_by=None, reverse=None):
"""Get a list of Jobs."""
query = base.get_query_string(search_opts, limit=limit, marker=marker,
sort_by=sort_by, reverse=reverse)
url = "/jobs%s" % query
return self._page(url, 'jobs', limit)
def get(self, job_id):
"""Get information about a Job"""
return self._get('/jobs/%s' % job_id, 'job')
def get_configs(self, job_type):
"""Get config hints for a specified Job type."""
return self._get('/jobs/config-hints/%s' % job_type)
def delete(self, job_id):
"""Delete a Job"""
self._delete('/jobs/%s' % job_id)
def update(self, job_id, name=NotUpdated, description=NotUpdated,
is_public=NotUpdated, is_protected=NotUpdated):
"""Update a Job."""
data = {}
self._copy_if_updated(data, name=name, description=description,
is_public=is_public, is_protected=is_protected)
return self._patch('/jobs/%s' % job_id, data)
# NOTE(jfreud): keep this around for backwards compatibility
JobsManager = JobsManagerV1

View File

@ -1,240 +0,0 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 saharaclient.api import base
class NodeGroupTemplate(base.Resource):
resource_name = 'Node Group Template'
class NodeGroupTemplateManagerV1(base.ResourceManager):
resource_class = NodeGroupTemplate
NotUpdated = base.NotUpdated()
def create(self, name, plugin_name, hadoop_version, flavor_id,
description=None, volumes_per_node=None, volumes_size=None,
node_processes=None, node_configs=None, floating_ip_pool=None,
security_groups=None, auto_security_group=None,
availability_zone=None, volumes_availability_zone=None,
volume_type=None, image_id=None, is_proxy_gateway=None,
volume_local_to_instance=None, use_autoconfig=None,
shares=None, is_public=None, is_protected=None,
volume_mount_prefix=None):
"""Create a Node Group Template."""
data = {
'name': name,
'plugin_name': plugin_name,
'hadoop_version': hadoop_version,
'flavor_id': flavor_id,
'node_processes': node_processes
}
return self._do_create(data, description, volumes_per_node,
volumes_size, node_configs, floating_ip_pool,
security_groups, auto_security_group,
availability_zone, volumes_availability_zone,
volume_type, image_id, is_proxy_gateway,
volume_local_to_instance, use_autoconfig,
shares, is_public, is_protected,
volume_mount_prefix)
def _do_create(self, data, description, volumes_per_node, volumes_size,
node_configs, floating_ip_pool, security_groups,
auto_security_group, availability_zone,
volumes_availability_zone, volume_type, image_id,
is_proxy_gateway, volume_local_to_instance, use_autoconfig,
shares, is_public, is_protected, volume_mount_prefix,
boot_from_volume=None, boot_volume_type=None,
boot_volume_az=None, boot_volume_local=None):
self._copy_if_defined(data,
description=description,
node_configs=node_configs,
floating_ip_pool=floating_ip_pool,
security_groups=security_groups,
auto_security_group=auto_security_group,
availability_zone=availability_zone,
image_id=image_id,
is_proxy_gateway=is_proxy_gateway,
use_autoconfig=use_autoconfig,
shares=shares,
is_public=is_public,
is_protected=is_protected,
boot_from_volume=boot_from_volume,
boot_volume_type=boot_volume_type,
boot_volume_availability_zone=boot_volume_az,
boot_volume_local_to_instance=boot_volume_local
)
if volumes_per_node:
data.update({"volumes_per_node": volumes_per_node,
"volumes_size": volumes_size})
if volumes_availability_zone:
data.update({"volumes_availability_zone":
volumes_availability_zone})
if volume_type:
data.update({"volume_type": volume_type})
if volume_local_to_instance:
data.update(
{"volume_local_to_instance": volume_local_to_instance})
if volume_mount_prefix:
data.update({"volume_mount_prefix": volume_mount_prefix})
return self._create('/node-group-templates', data,
'node_group_template')
def update(self, ng_template_id, name=NotUpdated, plugin_name=NotUpdated,
hadoop_version=NotUpdated, flavor_id=NotUpdated,
description=NotUpdated, volumes_per_node=NotUpdated,
volumes_size=NotUpdated, node_processes=NotUpdated,
node_configs=NotUpdated, floating_ip_pool=NotUpdated,
security_groups=NotUpdated, auto_security_group=NotUpdated,
availability_zone=NotUpdated,
volumes_availability_zone=NotUpdated, volume_type=NotUpdated,
image_id=NotUpdated, is_proxy_gateway=NotUpdated,
volume_local_to_instance=NotUpdated, use_autoconfig=NotUpdated,
shares=NotUpdated, is_public=NotUpdated,
is_protected=NotUpdated, volume_mount_prefix=NotUpdated):
"""Update a Node Group Template."""
data = {}
self._copy_if_updated(
data, name=name, plugin_name=plugin_name,
hadoop_version=hadoop_version, flavor_id=flavor_id,
description=description, volumes_per_node=volumes_per_node,
volumes_size=volumes_size, node_processes=node_processes,
node_configs=node_configs, floating_ip_pool=floating_ip_pool,
security_groups=security_groups,
auto_security_group=auto_security_group,
availability_zone=availability_zone,
volumes_availability_zone=volumes_availability_zone,
volume_type=volume_type, image_id=image_id,
is_proxy_gateway=is_proxy_gateway,
volume_local_to_instance=volume_local_to_instance,
use_autoconfig=use_autoconfig, shares=shares,
is_public=is_public, is_protected=is_protected,
volume_mount_prefix=volume_mount_prefix
)
return self._update('/node-group-templates/%s' % ng_template_id, data,
'node_group_template')
def list(self, search_opts=None, marker=None,
limit=None, sort_by=None, reverse=None):
"""Get a list of Node Group Templates."""
query = base.get_query_string(search_opts, limit=limit, marker=marker,
sort_by=sort_by, reverse=reverse)
url = "/node-group-templates%s" % query
return self._page(url, 'node_group_templates', limit)
def get(self, ng_template_id):
"""Get information about a Node Group Template."""
return self._get('/node-group-templates/%s' % ng_template_id,
'node_group_template')
def delete(self, ng_template_id):
"""Delete a Node Group Template."""
self._delete('/node-group-templates/%s' % ng_template_id)
def export(self, ng_template_id):
"""Export a Node Group Template."""
return self._get('/node-group-templates/%s/export' % ng_template_id)
class NodeGroupTemplateManagerV2(NodeGroupTemplateManagerV1):
NotUpdated = base.NotUpdated()
def create(self, name, plugin_name, plugin_version, flavor_id,
description=None, volumes_per_node=None, volumes_size=None,
node_processes=None, node_configs=None, floating_ip_pool=None,
security_groups=None, auto_security_group=None,
availability_zone=None, volumes_availability_zone=None,
volume_type=None, image_id=None, is_proxy_gateway=None,
volume_local_to_instance=None, use_autoconfig=None,
shares=None, is_public=None, is_protected=None,
volume_mount_prefix=None, boot_from_volume=None,
boot_volume_type=None, boot_volume_availability_zone=None,
boot_volume_local_to_instance=None):
"""Create a Node Group Template."""
data = {
'name': name,
'plugin_name': plugin_name,
'plugin_version': plugin_version,
'flavor_id': flavor_id,
'node_processes': node_processes
}
return self._do_create(data, description, volumes_per_node,
volumes_size, node_configs, floating_ip_pool,
security_groups, auto_security_group,
availability_zone, volumes_availability_zone,
volume_type, image_id, is_proxy_gateway,
volume_local_to_instance, use_autoconfig,
shares, is_public, is_protected,
volume_mount_prefix, boot_from_volume,
boot_volume_type,
boot_volume_availability_zone,
boot_volume_local_to_instance)
def update(self, ng_template_id, name=NotUpdated, plugin_name=NotUpdated,
plugin_version=NotUpdated, flavor_id=NotUpdated,
description=NotUpdated, volumes_per_node=NotUpdated,
volumes_size=NotUpdated, node_processes=NotUpdated,
node_configs=NotUpdated, floating_ip_pool=NotUpdated,
security_groups=NotUpdated, auto_security_group=NotUpdated,
availability_zone=NotUpdated,
volumes_availability_zone=NotUpdated, volume_type=NotUpdated,
image_id=NotUpdated, is_proxy_gateway=NotUpdated,
volume_local_to_instance=NotUpdated, use_autoconfig=NotUpdated,
shares=NotUpdated, is_public=NotUpdated,
is_protected=NotUpdated, volume_mount_prefix=NotUpdated,
boot_from_volume=NotUpdated,
boot_volume_type=NotUpdated,
boot_volume_availability_zone=NotUpdated,
boot_volume_local_to_instance=NotUpdated):
"""Update a Node Group Template."""
data = {}
self._copy_if_updated(
data, name=name, plugin_name=plugin_name,
plugin_version=plugin_version, flavor_id=flavor_id,
description=description, volumes_per_node=volumes_per_node,
volumes_size=volumes_size, node_processes=node_processes,
node_configs=node_configs, floating_ip_pool=floating_ip_pool,
security_groups=security_groups,
auto_security_group=auto_security_group,
availability_zone=availability_zone,
volumes_availability_zone=volumes_availability_zone,
volume_type=volume_type, image_id=image_id,
is_proxy_gateway=is_proxy_gateway,
volume_local_to_instance=volume_local_to_instance,
use_autoconfig=use_autoconfig, shares=shares,
is_public=is_public, is_protected=is_protected,
volume_mount_prefix=volume_mount_prefix,
boot_from_volume=boot_from_volume,
boot_volume_type=boot_volume_type,
boot_volume_availability_zone=boot_volume_availability_zone,
boot_volume_local_to_instance=boot_volume_local_to_instance
)
return self._patch('/node-group-templates/%s' % ng_template_id, data,
'node_group_template')
# NOTE(jfreud): keep this around for backwards compatibility
NodeGroupTemplateManager = NodeGroupTemplateManagerV1

View File

@ -1,26 +0,0 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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.
class Parameter(object):
"""This bean is used for building config entries."""
def __init__(self, config):
self.name = config['name']
self.description = config.get('description', "No description")
self.required = not config['is_optional']
self.default_value = config.get('default_value', None)
self.initial_value = self.default_value
self.param_type = config['config_type']
self.priority = int(config.get('priority', 2))

View File

@ -1,92 +0,0 @@
# Copyright (c) 2013 Mirantis Inc.
#
# 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 urllib import parse as urlparse
from saharaclient.api import base
class Plugin(base.Resource):
resource_name = 'Plugin'
def __init__(self, manager, info):
base.Resource.__init__(self, manager, info)
# Horizon requires each object in table to have an id
self.id = self.name
class _PluginManager(base.ResourceManager):
resource_class = Plugin
def list(self, search_opts=None):
"""Get a list of Plugins."""
query = base.get_query_string(search_opts)
return self._list('/plugins%s' % query, 'plugins')
def get(self, plugin_name):
"""Get information about a Plugin."""
return self._get('/plugins/%s' % plugin_name, 'plugin')
def get_version_details(self, plugin_name, hadoop_version):
"""Get version details
Get the list of Services and Service Parameters for a specified
Plugin and Plugin Version.
"""
return self._get('/plugins/%s/%s' % (plugin_name, hadoop_version),
'plugin')
def update(self, plugin_name, values):
"""Update plugin and then return updated result to user
"""
return self._patch("/plugins/%s" % plugin_name, values, 'plugin')
class PluginManagerV1(_PluginManager):
def convert_to_cluster_template(self, plugin_name, hadoop_version,
template_name, filecontent):
"""Convert to cluster template
Create Cluster Template directly, avoiding Cluster Template
mechanism.
"""
resp = self.api.post('/plugins/%s/%s/convert-config/%s' %
(plugin_name,
hadoop_version,
urlparse.quote(template_name)),
data=filecontent)
if resp.status_code != 202:
raise RuntimeError('Failed to upload template file for plugin "%s"'
' and version "%s"' %
(plugin_name, hadoop_version))
else:
return base.get_json(resp)['cluster_template']
class PluginManagerV2(_PluginManager):
def get_version_details(self, plugin_name, plugin_version):
"""Get version details
Get the list of Services and Service Parameters for a specified
Plugin and Plugin Version.
"""
return self._get('/plugins/%s/%s' % (plugin_name, plugin_version),
'plugin')
# NOTE(jfreud): keep this around for backwards compatibility
PluginManager = PluginManagerV1

View File

@ -1,70 +0,0 @@
# Copyright (c) 2018 OpenStack Foundation
#
# 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 saharaclient.api import base
class JobTemplate(base.Resource):
resource_name = 'Job Template'
class JobTemplatesManagerV2(base.ResourceManager):
resource_class = JobTemplate
NotUpdated = base.NotUpdated()
def create(self, name, type, mains=None, libs=None, description=None,
interface=None, is_public=None, is_protected=None):
"""Create a Job Template."""
data = {
'name': name,
'type': type
}
self._copy_if_defined(data, description=description, mains=mains,
libs=libs, interface=interface,
is_public=is_public, is_protected=is_protected)
return self._create('/%s' % 'job-templates', data, 'job_template')
def list(self, search_opts=None, limit=None,
marker=None, sort_by=None, reverse=None):
"""Get a list of Job Templates."""
query = base.get_query_string(search_opts, limit=limit, marker=marker,
sort_by=sort_by, reverse=reverse)
url = "/%s%s" % ('job-templates', query)
return self._page(url, 'job_templates', limit)
def get(self, job_id):
"""Get information about a Job Template."""
return self._get('/%s/%s' % ('job-templates', job_id), 'job_template')
def get_configs(self, job_type):
"""Get config hints for a specified Job Template type."""
return self._get('/%s/config-hints/%s' % ('job-templates', job_type))
def delete(self, job_id):
"""Delete a Job Template."""
self._delete('/%s/%s' % ('job-templates', job_id))
def update(self, job_id, name=NotUpdated, description=NotUpdated,
is_public=NotUpdated, is_protected=NotUpdated):
"""Update a Job Template."""
data = {}
self._copy_if_updated(data, name=name, description=description,
is_public=is_public, is_protected=is_protected)
return self._patch('/%s/%s' % ('job-templates', job_id), data)

View File

@ -1,72 +0,0 @@
# Copyright (c) 2018 OpenStack Foundation
#
# 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 saharaclient.api import base
class Job(base.Resource):
resource_name = 'Job'
class JobsManagerV2(base.ResourceManager):
resource_class = Job
NotUpdated = base.NotUpdated()
def list(self, search_opts=None, marker=None, limit=None,
sort_by=None, reverse=None):
"""Get a list of Jobs."""
query = base.get_query_string(search_opts, limit=limit, marker=marker,
sort_by=sort_by, reverse=reverse)
url = "/jobs%s" % query
return self._page(url, 'jobs', limit)
def get(self, obj_id):
"""Get information about a Job."""
return self._get('/jobs/%s' % obj_id, 'job')
def delete(self, obj_id):
"""Delete a Job."""
self._delete('/jobs/%s' % obj_id)
def create(self, job_template_id, cluster_id, input_id=None,
output_id=None, configs=None, interface=None, is_public=None,
is_protected=None):
"""Launch a Job."""
data = {
"cluster_id": cluster_id,
"job_template_id": job_template_id
}
self._copy_if_defined(data, input_id=input_id, output_id=output_id,
job_configs=configs, interface=interface,
is_public=is_public, is_protected=is_protected)
return self._create('/jobs', data, 'job')
def refresh_status(self, obj_id):
"""Refresh Job Status."""
return self._get(
'/jobs/%s?refresh_status=True' % obj_id,
'job'
)
def update(self, obj_id, is_public=NotUpdated, is_protected=NotUpdated):
"""Update a Job."""
data = {}
self._copy_if_updated(data, is_public=is_public,
is_protected=is_protected)
return self._patch('/jobs/%s' % obj_id, data)

View File

@ -1,48 +0,0 @@
# Copyright (c) 2014 Mirantis Inc.
#
# 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 oslo_utils import importutils
class UnsupportedVersion(Exception):
"""Indication for using an unsupported version of the API.
Indicates that the user is trying to use an unsupported
version of the API.
"""
pass
def get_client_class(version):
version_map = {
'1.0': 'saharaclient.api.client.Client',
'1.1': 'saharaclient.api.client.Client',
'2': 'saharaclient.api.client.ClientV2',
}
try:
client_path = version_map[str(version)]
except (KeyError, ValueError):
supported_versions = ', '.join(version_map.keys())
msg = ("Invalid client version '%(version)s'; must be one of: "
"%(versions)s") % {'version': version,
'versions': supported_versions}
raise UnsupportedVersion(msg)
return importutils.import_class(client_path)
def Client(version, *args, **kwargs):
client_class = get_client_class(version)
return client_class(*args, **kwargs)

Some files were not shown because too many files have changed in this diff Show More