StarlingX open source release updates

Signed-off-by: Scott Little <scott.little@windriver.com>
This commit is contained in:
Scott Little 2018-08-07 11:51:16 -04:00
commit 640ee2a980
62 changed files with 7622 additions and 0 deletions

7
.coveragerc Normal file
View File

@ -0,0 +1,7 @@
[run]
branch = True
source = dcmanagerclient
omit = dcmanagerclient/openstack/*
[report]
ignore_errors = True

58
.gitignore vendored Normal file
View File

@ -0,0 +1,58 @@
*.py[cod]
# C extensions
*.so
# Packages
*.egg*
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
# Installer logs
pip-log.txt
# Unit test / coverage reports
cover/
.coverage*
!.coveragerc
.tox
nosetests.xml
.testrepository
.venv
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Complexity
output/*.html
output/*/index.html
# Sphinx
doc/build
# pbr generates these
AUTHORS
ChangeLog
# Editors
*~
.*.swp
.*sw?
# Files created by releasenotes build
releasenotes/build

3
.mailmap Normal file
View File

@ -0,0 +1,3 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>

7
.testr.conf Normal file
View File

@ -0,0 +1,7 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
${PYTHON:-python} -m subunit.run discover $DISCOVER_DIRECTORY $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

14
CONTRIBUTING.rst Normal file
View File

@ -0,0 +1,14 @@
If you would like to contribute to the development of OpenStack, you must
follow the steps in this page:
http://docs.openstack.org/infra/manual/developers.html
If you already have a good understanding of how the system works and your
OpenStack accounts are set up, you can skip to the development workflow
section of this documentation to learn how changes to OpenStack should be
submitted for review via the Gerrit tool:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.

11
CONTRIBUTORS.wrs Normal file
View File

@ -0,0 +1,11 @@
The following contributors from Wind River have developed the seed code in this
repository. We look forward to community collaboration and contributions for
additional features, enhancements and refactoring.
Contributors:
=============
Bart Wensley
Kevin Smith
Lachlan Plant
Saju Oommen
Tyler Smith

4
HACKING.rst Normal file
View File

@ -0,0 +1,4 @@
python-dcmanagerclient Style Commandments
===============================================
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/

176
LICENSE Normal file
View File

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

6
MANIFEST.in Normal file
View File

@ -0,0 +1,6 @@
include AUTHORS
include ChangeLog
exclude .gitignore
exclude .gitreview
global-exclude *.pyc

17
README.rst Normal file
View File

@ -0,0 +1,17 @@
Dcmanagerclient
================
Wind River's Distributed Cloud system supports an edge computing solution by providing
central management and orchestration for a geographically distributed network of Titanium
Cloud systems.
===============================
python-dcmanagerclient
===============================
Python client for dcmanager
This is a client library for Dcmanager built on the Dcmanager API. It
provides a Python API (the ``dcmanagerclient`` module) and a command-line tool
(``dcmanager``).

2
babel.cfg Normal file
View File

@ -0,0 +1,2 @@
[python: **.py]

View File

@ -0,0 +1,26 @@
# Copyright (c) 2016 Ericsson AB
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import pbr.version
__version__ = pbr.version.VersionInfo(
'distributedcloud_client').version_string()

View File

112
dcmanagerclient/api/base.py Normal file
View File

@ -0,0 +1,112 @@
# Copyright (c) 2016 Ericsson AB
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
from bs4 import BeautifulSoup
import json
from dcmanagerclient import exceptions
class Resource(object):
# This will be overridden by the actual resource
resource_name = 'Something'
class ResourceManager(object):
resource_class = None
def __init__(self, http_client):
self.http_client = http_client
def _generate_resource(self, json_response_key):
json_objects = [json_response_key[item] for item in json_response_key]
resource = []
for json_object in json_objects:
for resource_data in json_object:
resource.append(self.resource_class(self, resource_data,
json_object[resource_data]))
return resource
def _list(self, url, response_key=None):
resp = self.http_client.get(url)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_response_key = get_json(resp)
resource = self._generate_resource(json_response_key)
return resource
def _update(self, url, data):
data = json.dumps(data)
resp = self.http_client.put(url, data)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_response_key = get_json(resp)
result = self._generate_resource(json_response_key)
return result
def _sync(self, url, data=None):
resp = self.http_client.put(url, data)
if resp.status_code != 200:
self._raise_api_exception(resp)
def _detail(self, url):
resp = self.http_client.get(url)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_response_key = get_json(resp)
json_objects = [json_response_key[item] for item in json_response_key]
resource = []
for json_object in json_objects:
data = json_object.get('usage').keys()
for values in data:
resource.append(self.resource_class(self, values,
json_object['limits'][values],
json_object['usage'][values]))
return resource
def _delete(self, url):
resp = self.http_client.delete(url)
if resp.status_code != 200:
self._raise_api_exception(resp)
def _raise_api_exception(self, resp):
error_html = resp.content
soup = BeautifulSoup(error_html, 'html.parser')
# Get the raw html with get_text, strip out the blank lines on
# front and back, then get rid of the 2 lines of error code number
# and error code explanation so that we are left with just the
# meaningful error text.
try:
error_msg = soup.body.get_text().lstrip().rstrip().split('\n')[2]
except Exception:
error_msg = resp.content
raise exceptions.APIException(error_code=resp.status_code,
error_message=error_msg)
def get_json(response):
"""Get JSON representation of response."""
json_field_or_function = getattr(response, 'json', None)
if callable(json_field_or_function):
return response.json()
else:
return json.loads(response.content)

View File

@ -0,0 +1,62 @@
# Copyright 2016 - Ericsson AB
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import six
from dcmanagerclient.api.v1 import client as client_v1
def client(dcmanager_url=None, username=None, api_key=None,
project_name=None, auth_url=None, project_id=None,
endpoint_type='publicURL', service_type='dcmanager',
auth_token=None, user_id=None, cacert=None, insecure=False,
profile=None, auth_type='keystone', client_id=None,
client_secret=None, session=None, **kwargs):
if dcmanager_url and not isinstance(dcmanager_url, six.string_types):
raise RuntimeError('DC Manager url should be a string.')
return client_v1.Client(
dcmanager_url=dcmanager_url,
username=username,
api_key=api_key,
project_name=project_name,
auth_url=auth_url,
project_id=project_id,
endpoint_type=endpoint_type,
service_type=service_type,
auth_token=auth_token,
user_id=user_id,
cacert=cacert,
insecure=insecure,
profile=profile,
auth_type=auth_type,
client_id=client_id,
client_secret=client_secret,
session=session,
**kwargs
)
def determine_client_version(dcmanager_version):
if dcmanager_version.find("v1.0") != -1:
return 1
raise RuntimeError("Cannot determine DC Manager API version")

View File

@ -0,0 +1,128 @@
# Copyright 2013 - Mirantis, Inc.
# Copyright 2016 - StackStorm, Inc.
# Copyright 2016 - Ericsson AB.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import copy
import os
import requests
import logging
import osprofiler.web
LOG = logging.getLogger(__name__)
def log_request(func):
def decorator(self, *args, **kwargs):
resp = func(self, *args, **kwargs)
LOG.debug("HTTP %s %s %d" % (resp.request.method, resp.url,
resp.status_code))
return resp
return decorator
class HTTPClient(object):
def __init__(self, base_url, token=None, project_id=None, user_id=None,
cacert=None, insecure=False):
self.base_url = base_url
self.token = token
self.project_id = project_id
self.user_id = user_id
self.ssl_options = {}
if self.base_url.startswith('https'):
if cacert and not os.path.exists(cacert):
raise ValueError('Unable to locate cacert file '
'at %s.' % cacert)
if cacert and insecure:
LOG.warning('Client is set to not verify even though '
'cacert is provided.')
self.ssl_options['verify'] = not insecure
self.ssl_options['cert'] = cacert
@log_request
def get(self, url, headers=None):
options = self._get_request_options('get', headers)
return requests.get(self.base_url + url, **options)
@log_request
def post(self, url, body, headers=None):
options = self._get_request_options('post', headers)
return requests.post(self.base_url + url, body, **options)
@log_request
def put(self, url, body, headers=None):
options = self._get_request_options('put', headers)
return requests.put(self.base_url + url, body, **options)
@log_request
def patch(self, url, body, headers=None):
options = self._get_request_options('patch', headers)
return requests.patch(self.base_url + url, body, **options)
@log_request
def delete(self, url, headers=None):
options = self._get_request_options('delete', headers)
return requests.delete(self.base_url + url, **options)
def _get_request_options(self, method, headers):
headers = self._update_headers(headers)
if method in ['post', 'put', 'patch']:
content_type = headers.get('content-type', 'application/json')
headers['content-type'] = content_type
options = copy.deepcopy(self.ssl_options)
options['headers'] = headers
return options
def _update_headers(self, headers):
if not headers:
headers = {}
token = headers.get('x-auth-token', self.token)
if token:
headers['x-auth-token'] = token
project_id = headers.get('X-Project-Id', self.project_id)
if project_id:
headers['X-Project-Id'] = project_id
user_id = headers.get('X-User-Id', self.user_id)
if user_id:
headers['X-User-Id'] = user_id
# Add headers for osprofiler.
headers.update(osprofiler.web.get_trace_id_headers())
return headers

View File

View File

@ -0,0 +1,65 @@
# Copyright (c) 2017 Ericsson AB.
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
from dcmanagerclient.api import base
from dcmanagerclient.api.base import get_json
class AlarmSumary(base.Resource):
resource_name = 'alarms'
def __init__(self, manager, name, critical, major,
minor, warnings, status):
self.manger = manager
self.name = name
self.critical = critical
self.major = major
self.minor = minor
self.warnings = warnings
self.status = status
class alarm_manager(base.ResourceManager):
resource_class = AlarmSumary
def alarm_summary_list(self, url):
resp = self.http_client.get(url)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_response_key = get_json(resp)
json_objects = json_response_key['alarm_summary']
resource = []
for json_object in json_objects:
resource.append(
self.resource_class(
self,
name=json_object['region_name'],
critical=json_object['critical_alarms'],
major=json_object['major_alarms'],
minor=json_object['minor_alarms'],
warnings=json_object['warnings'],
status=json_object['cloud_status']))
return resource
def list_alarms(self):
url = '/alarms/'
return self.alarm_summary_list(url)

View File

@ -0,0 +1,166 @@
# Copyright 2014 - Mirantis, Inc.
# Copyright 2015 - StackStorm, Inc.
# Copyright 2016 - Ericsson AB.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import keystoneauth1.identity.generic as auth_plugin
from keystoneauth1 import session as ks_session
from dcmanagerclient.api import httpclient
from dcmanagerclient.api.v1 import alarm_manager as am
from dcmanagerclient.api.v1 import subcloud_manager as sm
from dcmanagerclient.api.v1 import sw_update_manager as sum
from dcmanagerclient.api.v1 import sw_update_options_manager as suom
import osprofiler.profiler
import six
_DEFAULT_DCMANAGER_URL = "http://localhost:8119/v1.0"
class Client(object):
"""Class where the communication from KB to Keystone happens."""
def __init__(self, dcmanager_url=None, username=None, api_key=None,
project_name=None, auth_url=None, project_id=None,
endpoint_type='publicURL', service_type='dcmanager',
auth_token=None, user_id=None, cacert=None, insecure=False,
profile=None, auth_type='keystone', client_id=None,
client_secret=None, session=None, **kwargs):
"""DC Manager communicates with Keystone to fetch necessary values."""
if dcmanager_url and not isinstance(dcmanager_url, six.string_types):
raise RuntimeError('DC Manager url should be a string.')
if auth_url or session:
if auth_type == 'keystone':
(dcmanager_url, auth_token, project_id, user_id) = (
authenticate(
dcmanager_url,
username,
api_key,
project_name,
auth_url,
project_id,
endpoint_type,
service_type,
auth_token,
user_id,
session,
cacert,
insecure,
**kwargs
)
)
else:
raise RuntimeError(
'Invalid authentication type [value=%s, valid_values=%s]'
% (auth_type, 'keystone')
)
if not dcmanager_url:
dcmanager_url = _DEFAULT_DCMANAGER_URL
if profile:
osprofiler.profiler.init(profile)
self.http_client = httpclient.HTTPClient(
dcmanager_url,
auth_token,
project_id,
user_id,
cacert=cacert,
insecure=insecure
)
# Create all managers
self.subcloud_manager = sm.subcloud_manager(self.http_client)
self.alarm_manager = am.alarm_manager(self.http_client)
self.sw_update_manager = sum.sw_update_manager(self.http_client)
self.sw_update_options_manager = \
suom.sw_update_options_manager(self.http_client)
self.strategy_step_manager = sum.strategy_step_manager(
self.http_client)
def authenticate(dcmanager_url=None, username=None,
api_key=None, project_name=None, auth_url=None,
project_id=None, endpoint_type='publicURL',
service_type='dcmanager', auth_token=None, user_id=None,
session=None, cacert=None, insecure=False, **kwargs):
"""Get token, project_id, user_id and Endpoint."""
if project_name and project_id:
raise RuntimeError(
'Only project name or project id should be set'
)
if username and user_id:
raise RuntimeError(
'Only user name or user id should be set'
)
user_domain_name = kwargs.get('user_domain_name')
user_domain_id = kwargs.get('user_domain_id')
project_domain_name = kwargs.get('project_domain_name')
project_domain_id = kwargs.get('project_domain_id')
if session is None:
if auth_token:
auth = auth_plugin.Token(
auth_url=auth_url,
token=auth_token,
project_id=project_id,
project_name=project_name,
project_domain_name=project_domain_name,
project_domain_id=project_domain_id,
cacert=cacert,
insecure=insecure)
elif api_key and (username or user_id):
auth = auth_plugin.Password(
auth_url=auth_url,
username=username,
user_id=user_id,
password=api_key,
project_id=project_id,
project_name=project_name,
user_domain_name=user_domain_name,
user_domain_id=user_domain_id,
project_domain_name=project_domain_name,
project_domain_id=project_domain_id)
else:
raise RuntimeError('You must either provide a valid token or'
'a password (api_key) and a user.')
if auth:
session = ks_session.Session(auth=auth)
if session:
token = session.get_token()
project_id = session.get_project_id()
user_id = session.get_user_id()
if not dcmanager_url:
dcmanager_url = session.get_endpoint(
service_type=service_type,
interface=endpoint_type)
return dcmanager_url, token, project_id, user_id

View File

@ -0,0 +1,204 @@
# Copyright (c) 2017 Ericsson AB.
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import json
from dcmanagerclient.api import base
from dcmanagerclient.api.base import get_json
class Subcloud(base.Resource):
resource_name = 'subclouds'
def __init__(self, manager, subcloud_id, name, description, location,
software_version, management_state, availability_status,
management_subnet, management_start_ip, management_end_ip,
management_gateway_ip, systemcontroller_gateway_ip,
created_at, updated_at, sync_status="unknown",
endpoint_sync_status={}):
self.manager = manager
self.subcloud_id = subcloud_id
self.name = name
self.description = description
self.location = location
self.software_version = software_version
self.management_subnet = management_subnet
self.management_state = management_state
self.availability_status = availability_status
self.management_start_ip = management_start_ip
self.management_end_ip = management_end_ip
self.management_gateway_ip = management_gateway_ip
self.systemcontroller_gateway_ip = systemcontroller_gateway_ip
self.created_at = created_at
self.updated_at = updated_at
self.sync_status = sync_status
self.endpoint_sync_status = endpoint_sync_status
class subcloud_manager(base.ResourceManager):
resource_class = Subcloud
def subcloud_create(self, url, data):
data = json.dumps(data)
resp = self.http_client.post(url, data)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_object = get_json(resp)
resource = list()
resource.append(
self.resource_class(
self,
subcloud_id=json_object['id'],
name=json_object['name'],
description=json_object['description'],
location=json_object['location'],
software_version=json_object['software-version'],
management_state=json_object['management-state'],
availability_status=json_object['availability-status'],
management_subnet=json_object['management-subnet'],
management_start_ip=json_object['management-start-ip'],
management_end_ip=json_object['management-end-ip'],
management_gateway_ip=json_object['management-gateway-ip'],
systemcontroller_gateway_ip=json_object[
'systemcontroller-gateway-ip'],
created_at=json_object['created-at'],
updated_at=json_object['updated-at']))
return resource
def subcloud_update(self, url, data):
data = json.dumps(data)
resp = self.http_client.patch(url, data)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_object = get_json(resp)
resource = list()
resource.append(
self.resource_class(
self,
subcloud_id=json_object['id'],
name=json_object['name'],
description=json_object['description'],
location=json_object['location'],
software_version=json_object['software-version'],
management_state=json_object['management-state'],
availability_status=json_object['availability-status'],
management_subnet=json_object['management-subnet'],
management_start_ip=json_object['management-start-ip'],
management_end_ip=json_object['management-end-ip'],
management_gateway_ip=json_object['management-gateway-ip'],
systemcontroller_gateway_ip=json_object[
'systemcontroller-gateway-ip'],
created_at=json_object['created-at'],
updated_at=json_object['updated-at']))
return resource
def subcloud_list(self, url):
resp = self.http_client.get(url)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_response_key = get_json(resp)
json_objects = json_response_key['subclouds']
resource = []
for json_object in json_objects:
resource.append(
self.resource_class(
self,
subcloud_id=json_object['id'],
name=json_object['name'],
description=json_object['description'],
location=json_object['location'],
software_version=json_object['software-version'],
management_state=json_object['management-state'],
availability_status=json_object['availability-status'],
management_subnet=json_object['management-subnet'],
management_start_ip=json_object['management-start-ip'],
management_end_ip=json_object['management-end-ip'],
management_gateway_ip=json_object['management-gateway-ip'],
systemcontroller_gateway_ip=json_object[
'systemcontroller-gateway-ip'],
created_at=json_object['created-at'],
updated_at=json_object['updated-at'],
sync_status=json_object['sync_status'],
endpoint_sync_status=json_object['endpoint_sync_status']))
return resource
def _subcloud_detail(self, url):
resp = self.http_client.get(url)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_object = get_json(resp)
resource = list()
resource.append(
self.resource_class(
self,
subcloud_id=json_object['id'],
name=json_object['name'],
description=json_object['description'],
location=json_object['location'],
software_version=json_object['software-version'],
management_state=json_object['management-state'],
availability_status=json_object['availability-status'],
management_subnet=json_object['management-subnet'],
management_start_ip=json_object['management-start-ip'],
management_end_ip=json_object['management-end-ip'],
management_gateway_ip=json_object['management-gateway-ip'],
systemcontroller_gateway_ip=json_object[
'systemcontroller-gateway-ip'],
created_at=json_object['created-at'],
updated_at=json_object['updated-at'],
endpoint_sync_status=json_object['endpoint_sync_status']))
return resource
def subcloud_generate_config(self, url, data):
data = json.dumps(data)
resp = self.http_client.post(url, data)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_object = get_json(resp)
return json_object['config']
def add_subcloud(self, **kwargs):
data = kwargs
url = '/subclouds/'
return self.subcloud_create(url, data)
def list_subclouds(self):
url = '/subclouds/'
return self.subcloud_list(url)
def subcloud_detail(self, subcloud_ref):
url = '/subclouds/%s' % subcloud_ref
return self._subcloud_detail(url)
def delete_subcloud(self, subcloud_ref):
url = '/subclouds/%s' % subcloud_ref
return self._delete(url)
def update_subcloud(self, subcloud_ref, **kwargs):
data = kwargs
url = '/subclouds/%s' % subcloud_ref
return self.subcloud_update(url, data)
def generate_config_subcloud(self, subcloud_ref, **kwargs):
data = kwargs
url = '/subclouds/%s/config' % subcloud_ref
return self.subcloud_generate_config(url, data)

View File

@ -0,0 +1,209 @@
# Copyright (c) 2017 Ericsson AB.
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import json
from dcmanagerclient.api import base
from dcmanagerclient.api.base import get_json
class SwUpdateStrategy(base.Resource):
resource_name = 'sw_update_strategy'
def __init__(self, manager, subcloud_apply_type, max_parallel_subclouds,
stop_on_failure, state,
created_at, updated_at):
self.manager = manager
self.subcloud_apply_type = subcloud_apply_type
self.max_parallel_subclouds = max_parallel_subclouds
self.stop_on_failure = stop_on_failure
self.state = state
self.created_at = created_at
self.updated_at = updated_at
class StrategyStep(base.Resource):
resource_name = 'strategy_step'
def __init__(self, manager, cloud, stage, state, details,
started_at, finished_at, created_at, updated_at):
self.manager = manager
self.cloud = cloud
self.stage = stage
self.state = state
self.details = details
self.started_at = started_at
self.finished_at = finished_at
self.created_at = created_at
self.updated_at = updated_at
class sw_update_manager(base.ResourceManager):
resource_class = SwUpdateStrategy
def create_patch_strategy(self, **kwargs):
data = kwargs
data.update({'type': 'patch'})
url = '/sw-update-strategy/'
return self.sw_update_create(url, data)
def patch_strategy_detail(self):
url = '/sw-update-strategy'
return self.sw_update_detail(url)
def delete_patch_strategy(self):
url = '/sw-update-strategy'
return self.sw_update_delete(url)
def apply_patch_strategy(self):
data = {'action': 'apply'}
url = '/sw-update-strategy/actions'
return self.sw_update_action(url, data)
def abort_patch_strategy(self):
data = {'action': 'abort'}
url = '/sw-update-strategy/actions'
return self.sw_update_action(url, data)
def sw_update_create(self, url, data):
data = json.dumps(data)
resp = self.http_client.post(url, data)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_object = get_json(resp)
resource = list()
resource.append(
self.resource_class(
self,
subcloud_apply_type=json_object['subcloud-apply-type'],
max_parallel_subclouds=json_object['max-parallel-subclouds'],
stop_on_failure=json_object['stop-on-failure'],
state=json_object['state'],
created_at=json_object['created-at'],
updated_at=json_object['updated-at']))
return resource
def sw_update_delete(self, url):
resp = self.http_client.delete(url)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_object = get_json(resp)
resource = list()
resource.append(
self.resource_class(
self,
subcloud_apply_type=json_object['subcloud-apply-type'],
max_parallel_subclouds=json_object['max-parallel-subclouds'],
stop_on_failure=json_object['stop-on-failure'],
state=json_object['state'],
created_at=json_object['created-at'],
updated_at=json_object['updated-at']))
return resource
def sw_update_detail(self, url):
resp = self.http_client.get(url)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_object = get_json(resp)
resource = list()
resource.append(
self.resource_class(
self,
subcloud_apply_type=json_object['subcloud-apply-type'],
max_parallel_subclouds=json_object['max-parallel-subclouds'],
stop_on_failure=json_object['stop-on-failure'],
state=json_object['state'],
created_at=json_object['created-at'],
updated_at=json_object['updated-at']))
return resource
def sw_update_action(self, url, data):
data = json.dumps(data)
resp = self.http_client.post(url, data)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_object = get_json(resp)
resource = list()
resource.append(
self.resource_class(
self,
subcloud_apply_type=json_object['subcloud-apply-type'],
max_parallel_subclouds=json_object['max-parallel-subclouds'],
stop_on_failure=json_object['stop-on-failure'],
state=json_object['state'],
created_at=json_object['created-at'],
updated_at=json_object['updated-at']))
return resource
class strategy_step_manager(base.ResourceManager):
resource_class = StrategyStep
def list_strategy_steps(self):
url = '/sw-update-strategy/steps'
return self.strategy_step_list(url)
def strategy_step_detail(self, cloud_name):
url = '/sw-update-strategy/steps/%s' % cloud_name
return self._strategy_step_detail(url)
def strategy_step_list(self, url):
resp = self.http_client.get(url)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_response_key = get_json(resp)
json_objects = json_response_key['strategy-steps']
resource = []
for json_object in json_objects:
resource.append(
self.resource_class(
self,
cloud=json_object['cloud'],
stage=json_object['stage'],
state=json_object['state'],
details=json_object['details'],
started_at=json_object['started-at'],
finished_at=json_object['finished-at'],
created_at=json_object['created-at'],
updated_at=json_object['updated-at'],
))
return resource
def _strategy_step_detail(self, url):
resp = self.http_client.get(url)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_object = get_json(resp)
resource = list()
resource.append(
self.resource_class(
self,
cloud=json_object['cloud'],
stage=json_object['stage'],
state=json_object['state'],
details=json_object['details'],
started_at=json_object['started-at'],
finished_at=json_object['finished-at'],
created_at=json_object['created-at'],
updated_at=json_object['updated-at'],
))
return resource

View File

@ -0,0 +1,143 @@
# Copyright (c) 2017 Ericsson AB.
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import json
from dcmanagerclient.api import base
from dcmanagerclient.api.base import get_json
DEFAULT_REGION_NAME = "RegionOne"
class SwUpdateOptions(base.Resource):
resource_name = 'sw_update_options'
def __init__(self, manager, cloud, storage_apply_type, compute_apply_type,
max_parallel_computes, alarm_restriction_type,
default_instance_action,
created_at, updated_at):
self.manager = manager
self.cloud = cloud
self.storage_apply_type = storage_apply_type
self.compute_apply_type = compute_apply_type
self.max_parallel_computes = max_parallel_computes
self.alarm_restriction_type = alarm_restriction_type
self.default_instance_action = default_instance_action
self.created_at = created_at
self.updated_at = updated_at
class sw_update_options_manager(base.ResourceManager):
resource_class = SwUpdateOptions
def sw_update_options_update(self, subcloud_ref, **kwargs):
data = kwargs
if subcloud_ref:
url = '/sw-update-options/%s' % subcloud_ref
else:
url = '/sw-update-options/%s' % DEFAULT_REGION_NAME
return self._sw_update_options_update(url, data)
def sw_update_options_list(self):
url = '/sw-update-options'
return self._sw_update_options_list(url)
def sw_update_options_detail(self, subcloud_ref):
if subcloud_ref:
url = '/sw-update-options/%s' % subcloud_ref
else:
url = '/sw-update-options/%s' % DEFAULT_REGION_NAME
return self._sw_update_options_detail(url)
def sw_update_options_delete(self, subcloud_ref):
if subcloud_ref:
url = '/sw-update-options/%s' % subcloud_ref
else:
url = '/sw-update-options/%s' % DEFAULT_REGION_NAME
return self._sw_update_options_delete(url)
def _sw_update_options_detail(self, url):
resp = self.http_client.get(url)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_object = get_json(resp)
resource = list()
resource.append(
self.resource_class(
self,
cloud=json_object['name'],
storage_apply_type=json_object['storage-apply-type'],
compute_apply_type=json_object['compute-apply-type'],
max_parallel_computes=json_object['max-parallel-computes'],
alarm_restriction_type=json_object['alarm-restriction-type'],
default_instance_action=json_object['default-instance-action'],
created_at=json_object['created-at'],
updated_at=json_object['updated-at']))
return resource
def _sw_update_options_list(self, url):
resp = self.http_client.get(url)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_response_key = get_json(resp)
json_objects = json_response_key['sw-update-options']
resource = []
for json_object in json_objects:
resource.append(
self.resource_class(
self,
cloud=json_object['name'],
storage_apply_type=json_object['storage-apply-type'],
compute_apply_type=json_object['compute-apply-type'],
max_parallel_computes=json_object['max-parallel-computes'],
alarm_restriction_type=json_object[
'alarm-restriction-type'],
default_instance_action=json_object[
'default-instance-action'],
created_at=json_object['created-at'],
updated_at=json_object['updated-at']))
return resource
def _sw_update_options_delete(self, url):
resp = self.http_client.delete(url)
if resp.status_code != 200:
self._raise_api_exception(resp)
def _sw_update_options_update(self, url, data):
data = json.dumps(data)
resp = self.http_client.post(url, data)
if resp.status_code != 200:
self._raise_api_exception(resp)
json_object = get_json(resp)
resource = list()
resource.append(
self.resource_class(
self,
cloud=json_object['name'],
storage_apply_type=json_object['storage-apply-type'],
compute_apply_type=json_object['compute-apply-type'],
max_parallel_computes=json_object['max-parallel-computes'],
alarm_restriction_type=json_object['alarm-restriction-type'],
default_instance_action=json_object['default-instance-action'],
created_at=json_object['created-at'],
updated_at=json_object['updated-at']))
return resource

View File

@ -0,0 +1,5 @@
===============================
Commands
================================
This module helps in mapping dcmanager commands to APIs.

View File

View File

View File

@ -0,0 +1,63 @@
# Copyright (c) 2017 Ericsson AB.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
from dcmanagerclient.commands.v1 import base
def format(alarms=None):
columns = (
'NAME',
'CRITICAL_ALARMS',
'MAJOR_ALARMS',
'MINOR_ALARMS',
'WARNINGS',
'STATUS'
)
if alarms:
data = (
alarms.name if alarms.name >= 0 else '-',
alarms.critical if alarms.critical >= 0 else '-',
alarms.major if alarms.major >= 0 else '-',
alarms.minor if alarms.minor >= 0 else '-',
alarms.warnings if alarms.warnings >= 0 else '-',
alarms.status
)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class ListAlarmSummary(base.DCManagerLister):
"""List alarm summaries of subclouds."""
def _get_format_function(self):
return format
def get_parser(self, parsed_args):
parser = super(ListAlarmSummary, self).get_parser(parsed_args)
return parser
def _get_resources(self, parsed_args):
dcmanager_client = self.app.client_manager.alarm_manager
return dcmanager_client.alarm_manager.list_alarms()

View File

@ -0,0 +1,90 @@
# Copyright (c) 2016 Ericsson AB
# 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.
#
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import abc
from osc_lib.command import command
import six
@six.add_metaclass(abc.ABCMeta)
class DCManagerLister(command.Lister):
@abc.abstractmethod
def _get_format_function(self):
raise NotImplementedError
@abc.abstractmethod
def _get_resources(self, parsed_args):
"""Get a list of API resources (e.g. using client)."""
raise NotImplementedError
def _validate_parsed_args(self, parsed_args):
# No-op by default.
pass
def take_action(self, parsed_args):
self._validate_parsed_args(parsed_args)
f = self._get_format_function()
ret = self._get_resources(parsed_args)
if not isinstance(ret, list):
ret = [ret]
data = [f(r)[1] for r in ret]
if data:
return f()[0], data
else:
return f()
@six.add_metaclass(abc.ABCMeta)
class DCManagerShowOne(command.ShowOne):
@abc.abstractmethod
def _get_format_function(self):
raise NotImplementedError
@abc.abstractmethod
def _get_resources(self, parsed_args):
"""Get a list of API resources (e.g. using client)."""
raise NotImplementedError
def _validate_parsed_args(self, parsed_args):
# No-op by default.
pass
def take_action(self, parsed_args):
self._validate_parsed_args(parsed_args)
f = self._get_format_function()
ret = self._get_resources(parsed_args)
if not isinstance(ret, list):
ret = [ret]
columns = [f(r)[0] for r in ret]
data = [f(r)[1] for r in ret]
if data:
return (columns[0], data[0])
else:
return f()

View File

@ -0,0 +1,473 @@
# Copyright (c) 2017 Ericsson AB.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
from osc_lib.command import command
from dcmanagerclient.commands.v1 import base
from dcmanagerclient import exceptions
def format(subcloud=None):
columns = (
'id',
'name',
'management',
'availability',
'sync'
)
if subcloud:
data = (
subcloud.subcloud_id,
subcloud.name,
subcloud.management_state,
subcloud.availability_status,
subcloud.sync_status
)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
def detail_format(subcloud=None):
columns = (
'id',
'name',
'description',
'location',
'software_version',
'management',
'availability',
'management_subnet',
'management_start_ip',
'management_end_ip',
'management_gateway_ip',
'systemcontroller_gateway_ip',
'created_at',
'updated_at',
)
if subcloud:
data = (
subcloud.subcloud_id,
subcloud.name,
subcloud.description,
subcloud.location,
subcloud.software_version,
subcloud.management_state,
subcloud.availability_status,
subcloud.management_subnet,
subcloud.management_start_ip,
subcloud.management_end_ip,
subcloud.management_gateway_ip,
subcloud.systemcontroller_gateway_ip,
subcloud.created_at,
subcloud.updated_at,
)
for listitem, sync_status in enumerate(subcloud.endpoint_sync_status
):
added_field = (sync_status['endpoint_type'] +
"_sync_status",)
added_value = (sync_status['sync_status'],)
columns += tuple(added_field)
data += tuple(added_value)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class AddSubcloud(base.DCManagerShowOne):
"""Add a new subcloud."""
def _get_format_function(self):
return detail_format
def get_parser(self, parsed_args):
parser = super(AddSubcloud, self).get_parser(parsed_args)
parser.add_argument(
'--name',
required=True,
help='Name of subcloud.'
)
parser.add_argument(
'--description',
required=False,
help='Description of subcloud.'
)
parser.add_argument(
'--location',
required=False,
help='Location of subcloud.'
)
parser.add_argument(
'--management-subnet',
required=True,
help='Management subnet for subcloud in CIDR format.'
)
parser.add_argument(
'--management-start-ip',
required=True,
help='Start of management IP address range for subcloud'
)
parser.add_argument(
'--management-end-ip',
required=True,
help='End of management IP address range for subcloud',
)
parser.add_argument(
'--management-gateway-ip',
required=True,
help='Management gateway IP for subcloud',
)
parser.add_argument(
'--systemcontroller-gateway-ip',
required=True,
help='Central gateway IP',
)
return parser
def _get_resources(self, parsed_args):
dcmanager_client = self.app.client_manager.subcloud_manager
kwargs = dict()
kwargs['name'] = parsed_args.name
if parsed_args.description:
kwargs['description'] = parsed_args.description
if parsed_args.location:
kwargs['location'] = parsed_args.location
kwargs['management-subnet'] = parsed_args.management_subnet
kwargs['management-start-ip'] = parsed_args.management_start_ip
kwargs['management-end-ip'] = parsed_args.management_end_ip
kwargs['management-gateway-ip'] = parsed_args.management_gateway_ip
kwargs['systemcontroller-gateway-ip'] = \
parsed_args.systemcontroller_gateway_ip
return dcmanager_client.subcloud_manager.add_subcloud(**kwargs)
class ListSubcloud(base.DCManagerLister):
"""List subclouds."""
def _get_format_function(self):
return format
def get_parser(self, parsed_args):
parser = super(ListSubcloud, self).get_parser(parsed_args)
return parser
def _get_resources(self, parsed_args):
dcmanager_client = self.app.client_manager.subcloud_manager
return dcmanager_client.subcloud_manager.list_subclouds()
class ShowSubcloud(base.DCManagerShowOne):
"""Show the details of a subcloud."""
def _get_format_function(self):
return detail_format
def get_parser(self, parsed_args):
parser = super(ShowSubcloud, self).get_parser(parsed_args)
parser.add_argument(
'subcloud',
help='Name or ID of subcloud to view the details.'
)
return parser
def _get_resources(self, parsed_args):
subcloud_ref = parsed_args.subcloud
dcmanager_client = self.app.client_manager.subcloud_manager
return dcmanager_client.subcloud_manager.subcloud_detail(subcloud_ref)
class DeleteSubcloud(command.Command):
"""Delete subcloud details from the database."""
def get_parser(self, prog_name):
parser = super(DeleteSubcloud, self).get_parser(prog_name)
parser.add_argument(
'subcloud',
help='Name or ID of the subcloud to delete.'
)
return parser
def take_action(self, parsed_args):
subcloud_ref = parsed_args.subcloud
dcmanager_client = self.app.client_manager.subcloud_manager
try:
dcmanager_client.subcloud_manager.delete_subcloud(subcloud_ref)
except Exception as e:
print (e)
error_msg = "Unable to delete subcloud %s" % (subcloud_ref)
raise exceptions.DCManagerClientException(error_msg)
class UnmanageSubcloud(base.DCManagerShowOne):
"""Unmanage a subcloud."""
def _get_format_function(self):
return detail_format
def get_parser(self, prog_name):
parser = super(UnmanageSubcloud, self).get_parser(prog_name)
parser.add_argument(
'subcloud',
help='Name or ID of the subcloud to unmanage.'
)
return parser
def _get_resources(self, parsed_args):
subcloud_ref = parsed_args.subcloud
dcmanager_client = self.app.client_manager.subcloud_manager
kwargs = dict()
kwargs['management-state'] = 'unmanaged'
try:
return dcmanager_client.subcloud_manager.update_subcloud(
subcloud_ref, **kwargs)
except Exception as e:
print (e)
error_msg = "Unable to unmanage subcloud %s" % (subcloud_ref)
raise exceptions.DCManagerClientException(error_msg)
class ManageSubcloud(base.DCManagerShowOne):
"""Manage a subcloud."""
def _get_format_function(self):
return detail_format
def get_parser(self, prog_name):
parser = super(ManageSubcloud, self).get_parser(prog_name)
parser.add_argument(
'subcloud',
help='Name or ID of the subcloud to manage.'
)
return parser
def _get_resources(self, parsed_args):
subcloud_ref = parsed_args.subcloud
dcmanager_client = self.app.client_manager.subcloud_manager
kwargs = dict()
kwargs['management-state'] = 'managed'
try:
return dcmanager_client.subcloud_manager.update_subcloud(
subcloud_ref, **kwargs)
except Exception as e:
print (e)
error_msg = "Unable to manage subcloud %s" % (subcloud_ref)
raise exceptions.DCManagerClientException(error_msg)
class UpdateSubcloud(base.DCManagerShowOne):
"""Update attributes of a subcloud."""
def _get_format_function(self):
return detail_format
def get_parser(self, prog_name):
parser = super(UpdateSubcloud, self).get_parser(prog_name)
parser.add_argument(
'subcloud',
help='Name or ID of the subcloud to update.'
)
parser.add_argument(
'--description',
required=False,
help='Description of subcloud.'
)
parser.add_argument(
'--location',
required=False,
help='Location of subcloud.'
)
return parser
def _get_resources(self, parsed_args):
subcloud_ref = parsed_args.subcloud
dcmanager_client = self.app.client_manager.subcloud_manager
kwargs = dict()
if parsed_args.description:
kwargs['description'] = parsed_args.description
if parsed_args.location:
kwargs['location'] = parsed_args.location
if len(kwargs) == 0:
error_msg = "Nothing to update"
raise exceptions.DCManagerClientException(error_msg)
try:
return dcmanager_client.subcloud_manager.update_subcloud(
subcloud_ref, **kwargs)
except Exception as e:
print (e)
error_msg = "Unable to update subcloud %s" % (subcloud_ref)
raise exceptions.DCManagerClientException(error_msg)
class GenerateConfigSubcloud(command.Command):
"""Generate configuration for a subcloud."""
def get_parser(self, prog_name):
parser = super(GenerateConfigSubcloud, self).get_parser(prog_name)
parser.add_argument(
'subcloud',
help='Name or ID of the subcloud to generate config.'
)
parser.add_argument(
'--pxe-subnet',
required=False,
help='PXE boot subnet for subcloud in CIDR format.'
)
parser.add_argument(
'--management-vlan',
required=False,
help='VLAN for subcloud management network.'
)
parser.add_argument(
'--management-interface-port',
required=False,
help='Subcloud management interface port.'
)
parser.add_argument(
'--management-interface-mtu',
required=False,
help='Subcloud management interface mtu.'
)
parser.add_argument(
'--oam-subnet',
required=False,
help='OAM subnet for subcloud in CIDR format.'
)
parser.add_argument(
'--oam-gateway-ip',
required=False,
help='OAM gateway IP for subcloud.'
)
parser.add_argument(
'--oam-floating-ip',
required=False,
help='OAM floating IP address for subcloud.'
)
parser.add_argument(
'--oam-unit-0-ip',
required=False,
help='OAM unit 0 IP address for subcloud.'
)
parser.add_argument(
'--oam-unit-1-ip',
required=False,
help='OAM unit 1 IP address for subcloud.'
)
parser.add_argument(
'--oam-interface-port',
required=False,
help='Subcloud OAM interface port.'
)
parser.add_argument(
'--oam-interface-mtu',
required=False,
help='Subcloud OAM interface mtu.'
)
parser.add_argument(
'--system-mode',
required=False,
help='System mode',
choices=['simplex', 'duplex', 'duplex-direct']
)
return parser
def take_action(self, parsed_args):
subcloud_ref = parsed_args.subcloud
dcmanager_client = self.app.client_manager.subcloud_manager
kwargs = dict()
if parsed_args.pxe_subnet:
kwargs['pxe-subnet'] = \
parsed_args.pxe_subnet
if parsed_args.management_vlan:
kwargs['management-vlan'] = \
parsed_args.management_vlan
if parsed_args.management_interface_port:
kwargs['management-interface-port'] = \
parsed_args.management_interface_port
if parsed_args.management_interface_mtu:
kwargs['management-interface-mtu'] = \
parsed_args.management_interface_mtu
if parsed_args.oam_subnet:
kwargs['oam-subnet'] = parsed_args.oam_subnet
if parsed_args.oam_gateway_ip:
kwargs['oam-gateway-ip'] = parsed_args.oam_gateway_ip
if parsed_args.oam_floating_ip:
kwargs['oam-floating-ip'] = parsed_args.oam_floating_ip
if parsed_args.oam_unit_0_ip:
kwargs['oam-unit-0-ip'] = parsed_args.oam_unit_0_ip
if parsed_args.oam_unit_1_ip:
kwargs['oam-unit-1-ip'] = parsed_args.oam_unit_1_ip
if parsed_args.oam_interface_port:
kwargs['oam-interface-port'] = parsed_args.oam_interface_port
if parsed_args.oam_interface_mtu:
kwargs['oam-interface-mtu'] = parsed_args.oam_interface_mtu
if parsed_args.system_mode:
kwargs['system-mode'] = parsed_args.system_mode
try:
subcloud_config = dcmanager_client.subcloud_manager.\
generate_config_subcloud(subcloud_ref, **kwargs)
return subcloud_config
except Exception as e:
print (e)
error_msg = "Unable to generate config for subcloud %s" % \
(subcloud_ref)
raise exceptions.DCManagerClientException(error_msg)

View File

@ -0,0 +1,272 @@
# Copyright (c) 2017 Ericsson AB.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
from dcmanagerclient.commands.v1 import base
from dcmanagerclient import exceptions
def detail_format(sw_update_strategy=None):
columns = (
'subcloud apply type',
'max parallel subclouds',
'stop on failure',
'state',
'created_at',
'updated_at',
)
if sw_update_strategy:
data = (
sw_update_strategy.subcloud_apply_type,
sw_update_strategy.max_parallel_subclouds,
sw_update_strategy.stop_on_failure,
sw_update_strategy.state,
sw_update_strategy.created_at,
sw_update_strategy.updated_at,
)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
def strategy_step_format(strategy_step=None):
columns = (
'cloud',
'stage',
'state',
'details',
'started_at',
'finished_at',
)
if strategy_step:
data = (
strategy_step.cloud,
strategy_step.stage,
strategy_step.state,
strategy_step.details,
strategy_step.started_at,
strategy_step.finished_at,
)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
def detail_strategy_step_format(strategy_step=None):
columns = (
'cloud',
'stage',
'state',
'details',
'started_at',
'finished_at',
'created_at',
'updated_at',
)
if strategy_step:
data = (
strategy_step.cloud,
strategy_step.stage,
strategy_step.state,
strategy_step.details,
strategy_step.started_at,
strategy_step.finished_at,
strategy_step.created_at,
strategy_step.updated_at,
)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class CreatePatchStrategy(base.DCManagerShowOne):
"""Create a patch strategy."""
def _get_format_function(self):
return detail_format
def get_parser(self, parsed_args):
parser = super(CreatePatchStrategy, self).get_parser(parsed_args)
parser.add_argument(
'--subcloud-apply-type',
required=False,
choices=['parallel', 'serial'],
help='Subcloud apply type (parallel or serial).'
)
parser.add_argument(
'--max-parallel-subclouds',
required=False,
type=int,
help='Maximum number of parallel subclouds.'
)
parser.add_argument(
'--stop-on-failure',
required=False,
action='store_true',
help='Do not patch any additional subclouds after a failure.'
)
parser.add_argument(
'cloud_name',
nargs='?',
default=None,
help='Name of a single cloud to patch.'
)
return parser
def _get_resources(self, parsed_args):
dcmanager_client = self.app.client_manager.sw_update_manager
kwargs = dict()
if parsed_args.subcloud_apply_type:
kwargs['subcloud-apply-type'] = parsed_args.subcloud_apply_type
if parsed_args.max_parallel_subclouds:
kwargs['max-parallel-subclouds'] = \
parsed_args.max_parallel_subclouds
if parsed_args.stop_on_failure:
kwargs['stop-on-failure'] = 'true'
if parsed_args.cloud_name is not None:
kwargs['cloud_name'] = parsed_args.cloud_name
return dcmanager_client.sw_update_manager.create_patch_strategy(
**kwargs)
class ShowPatchStrategy(base.DCManagerShowOne):
"""Show the details of a patch strategy for a subcloud."""
def _get_format_function(self):
return detail_format
def get_parser(self, parsed_args):
parser = super(ShowPatchStrategy, self).get_parser(parsed_args)
return parser
def _get_resources(self, parsed_args):
dcmanager_client = self.app.client_manager.sw_update_manager
return dcmanager_client.sw_update_manager.patch_strategy_detail()
class DeletePatchStrategy(base.DCManagerShowOne):
"""Delete patch strategy from the database."""
def _get_format_function(self):
return detail_format
def get_parser(self, prog_name):
parser = super(DeletePatchStrategy, self).get_parser(prog_name)
return parser
def _get_resources(self, parsed_args):
dcmanager_client = self.app.client_manager.sw_update_manager
try:
return dcmanager_client.sw_update_manager.delete_patch_strategy()
except Exception as e:
print (e)
error_msg = "Unable to delete patch strategy"
raise exceptions.DCManagerClientException(error_msg)
class ApplyPatchStrategy(base.DCManagerShowOne):
"""Apply a patch strategy."""
def _get_format_function(self):
return detail_format
def get_parser(self, prog_name):
parser = super(ApplyPatchStrategy, self).get_parser(prog_name)
return parser
def _get_resources(self, parsed_args):
dcmanager_client = self.app.client_manager.sw_update_manager
try:
return dcmanager_client.sw_update_manager.apply_patch_strategy()
except Exception as e:
print (e)
error_msg = "Unable to apply patch strategy"
raise exceptions.DCManagerClientException(error_msg)
class AbortPatchStrategy(base.DCManagerShowOne):
"""Abort a patch strategy."""
def _get_format_function(self):
return detail_format
def get_parser(self, prog_name):
parser = super(AbortPatchStrategy, self).get_parser(prog_name)
return parser
def _get_resources(self, parsed_args):
dcmanager_client = self.app.client_manager.sw_update_manager
try:
return dcmanager_client.sw_update_manager.abort_patch_strategy()
except Exception as e:
print (e)
error_msg = "Unable to abort patch strategy"
raise exceptions.DCManagerClientException(error_msg)
class ListStrategyStep(base.DCManagerLister):
"""List strategy steps."""
def _get_format_function(self):
return strategy_step_format
def get_parser(self, parsed_args):
parser = super(ListStrategyStep, self).get_parser(parsed_args)
return parser
def _get_resources(self, parsed_args):
dcmanager_client = self.app.client_manager.strategy_step_manager
return dcmanager_client.strategy_step_manager.list_strategy_steps()
class ShowStrategyStep(base.DCManagerShowOne):
"""Show the details of a strategy step."""
def _get_format_function(self):
return detail_strategy_step_format
def get_parser(self, parsed_args):
parser = super(ShowStrategyStep, self).get_parser(parsed_args)
parser.add_argument(
'cloud_name',
help='Name of cloud to view the details.'
)
return parser
def _get_resources(self, parsed_args):
cloud_name = parsed_args.cloud_name
dcmanager_client = self.app.client_manager.strategy_step_manager
return dcmanager_client.strategy_step_manager.strategy_step_detail(
cloud_name)

View File

@ -0,0 +1,223 @@
# Copyright (c) 2017 Ericsson AB.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
from osc_lib.command import command
from dcmanagerclient.commands.v1 import base
from dcmanagerclient import exceptions
DEFAULT_REGION_NAME = "RegionOne"
def options_detail_format(sw_update_options=None):
columns = (
'cloud',
'storage apply type',
'compute apply type',
'max parallel computes',
'alarm restriction type',
'default instance action',
'created_at',
'updated_at',
)
if sw_update_options:
data = (
sw_update_options.cloud,
sw_update_options.storage_apply_type,
sw_update_options.compute_apply_type,
sw_update_options.max_parallel_computes,
sw_update_options.alarm_restriction_type,
sw_update_options.default_instance_action,
sw_update_options.created_at,
sw_update_options.updated_at,
)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
def options_list_format(sw_update_option=None):
columns = (
'cloud',
'storage apply type',
'compute apply type',
'max parallel computes',
'alarm restriction type',
'default instance action',
)
if sw_update_option:
data = (
sw_update_option.cloud,
sw_update_option.storage_apply_type,
sw_update_option.compute_apply_type,
sw_update_option.max_parallel_computes,
sw_update_option.alarm_restriction_type,
sw_update_option.default_instance_action,
)
else:
data = (tuple('<none>' for _ in range(len(columns))),)
return columns, data
class UpdateSwUpdateOptions(base.DCManagerShowOne):
"""Update patch options, defaults or per subcloud."""
def _get_format_function(self):
return options_detail_format
def get_parser(self, parsed_args):
parser = super(UpdateSwUpdateOptions, self).get_parser(parsed_args)
parser.add_argument(
'--storage-apply-type',
required=True,
choices=['parallel', 'serial'],
help='Storage node apply type (parallel or serial).'
)
parser.add_argument(
'--compute-apply-type',
required=True,
choices=['parallel', 'serial'],
help='Compute node apply type (parallel or serial).'
)
parser.add_argument(
'--max-parallel-computes',
required=True,
type=int,
help='Maximum number of parallel computes.'
)
parser.add_argument(
'--alarm-restriction-type',
required=True,
choices=['strict', 'relaxed'],
help='Whether to allow patching if subcloud alarms are present or '
'not (strict, relaxed).'
)
parser.add_argument(
'--default-instance-action',
required=True,
choices=['stop-start', 'migrate'],
help='How instances should be handled.'
)
parser.add_argument(
'subcloud',
nargs='?',
default=None,
help='Subcloud name or id, omit to set default options.'
)
return parser
def _get_resources(self, parsed_args):
subcloud_ref = parsed_args.subcloud
dcmanager_client = self.app.client_manager.sw_update_options_manager
kwargs = dict()
kwargs['storage-apply-type'] = parsed_args.storage_apply_type
kwargs['compute-apply-type'] = parsed_args.compute_apply_type
kwargs['max-parallel-computes'] = parsed_args.max_parallel_computes
kwargs['alarm-restriction-type'] = parsed_args.alarm_restriction_type
kwargs['default-instance-action'] = parsed_args.default_instance_action
try:
return dcmanager_client.sw_update_options_manager.\
sw_update_options_update(subcloud_ref, **kwargs)
except Exception as e:
print (e)
error_msg = "Unable to update patch options for subcloud %s" % \
(subcloud_ref)
raise exceptions.DCManagerClientException(error_msg)
class ListSwUpdateOptions(base.DCManagerLister):
"""List patch options."""
def _get_format_function(self):
return options_list_format
def get_parser(self, parsed_args):
parser = super(ListSwUpdateOptions, self).get_parser(parsed_args)
return parser
def _get_resources(self, parsed_args):
dcmanager_client = self.app.client_manager.sw_update_options_manager
return dcmanager_client.sw_update_options_manager.\
sw_update_options_list()
class ShowSwUpdateOptions(base.DCManagerShowOne):
"""Show patch options, defaults or per subcloud."""
def _get_format_function(self):
return options_detail_format
def get_parser(self, parsed_args):
parser = super(ShowSwUpdateOptions, self).get_parser(parsed_args)
parser.add_argument(
'subcloud',
nargs='?',
default=None,
help='Subcloud name or id, omit to show default options.'
)
return parser
def _get_resources(self, parsed_args):
subcloud_ref = parsed_args.subcloud
dcmanager_client = self.app.client_manager.sw_update_options_manager
return dcmanager_client.sw_update_options_manager.\
sw_update_options_detail(subcloud_ref)
class DeleteSwUpdateOptions(command.Command):
"""Delete per subcloud patch options."""
def get_parser(self, prog_name):
parser = super(DeleteSwUpdateOptions, self).get_parser(prog_name)
parser.add_argument(
'subcloud',
help='Subcloud name or id'
)
return parser
def take_action(self, parsed_args):
subcloud_ref = parsed_args.subcloud
dcmanager_client = self.app.client_manager.sw_update_options_manager
try:
return dcmanager_client.sw_update_options_manager.\
sw_update_options_delete(subcloud_ref)
except Exception as e:
print (e)
error_msg = "Unable to delete patch options"
raise exceptions.DCManagerClientException(error_msg)

View File

@ -0,0 +1,63 @@
# Copyright 2016 Ericsson AB
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
class DCManagerClientException(Exception):
"""Base Exception for DC Manager client
To correctly use this class, inherit from it and define
a 'message' and 'code' properties.
"""
message = "An unknown exception occurred"
code = "UNKNOWN_EXCEPTION"
def __str__(self):
return self.message
def __init__(self, message=message):
self.message = message
super(DCManagerClientException, self).__init__(
'%s: %s' % (self.code, self.message))
class IllegalArgumentException(DCManagerClientException):
message = "IllegalArgumentException occurred"
code = "ILLEGAL_ARGUMENT_EXCEPTION"
def __init__(self, message=None):
if message:
self.message = message
class CommandError(DCManagerClientException):
message = "CommandErrorException occurred"
code = "COMMAND_ERROR_EXCEPTION"
def __init__(self, message=None):
if message:
self.message = message
class APIException(Exception):
def __init__(self, error_code=None, error_message=None):
super(APIException, self).__init__(error_message)
self.error_code = error_code
self.error_message = error_message

View File

View File

@ -0,0 +1,24 @@
#
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import six
six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox'))

View File

@ -0,0 +1,231 @@
# Copyright 2013 OpenStack Foundation
# Copyright 2013 Spanish National Research Council.
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
# E0202: An attribute inherited from %s hide this method
# pylint: disable=E0202
import abc
import argparse
import os
import six
from stevedore import extension
from dcmanagerclient.openstack.common.apiclient import exceptions
_discovered_plugins = {}
def discover_auth_systems():
"""Discover the available auth-systems.
This won't take into account the old style auth-systems.
"""
global _discovered_plugins
_discovered_plugins = {}
def add_plugin(ext):
_discovered_plugins[ext.name] = ext.plugin
ep_namespace = "dcmanagerclient.openstack.common.apiclient.auth"
mgr = extension.ExtensionManager(ep_namespace)
mgr.map(add_plugin)
def load_auth_system_opts(parser):
"""Load options needed by the available auth-systems into a parser.
This function will try to populate the parser with options from the
available plugins.
"""
group = parser.add_argument_group("Common auth options")
BaseAuthPlugin.add_common_opts(group)
for name, auth_plugin in six.iteritems(_discovered_plugins):
group = parser.add_argument_group(
"Auth-system '%s' options" % name,
conflict_handler="resolve")
auth_plugin.add_opts(group)
def load_plugin(auth_system):
try:
plugin_class = _discovered_plugins[auth_system]
except KeyError:
raise exceptions.AuthSystemNotFound(auth_system)
return plugin_class(auth_system=auth_system)
def load_plugin_from_args(args):
"""Load required plugin and populate it with options.
Try to guess auth system if it is not specified. Systems are tried in
alphabetical order.
:type args: argparse.Namespace
:raises: AuthPluginOptionsMissing
"""
auth_system = args.os_auth_system
if auth_system:
plugin = load_plugin(auth_system)
plugin.parse_opts(args)
plugin.sufficient_options()
return plugin
for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)):
plugin_class = _discovered_plugins[plugin_auth_system]
plugin = plugin_class()
plugin.parse_opts(args)
try:
plugin.sufficient_options()
except exceptions.AuthPluginOptionsMissing:
continue
return plugin
raise exceptions.AuthPluginOptionsMissing(["auth_system"])
@six.add_metaclass(abc.ABCMeta)
class BaseAuthPlugin(object):
"""Base class for authentication plugins.
An authentication plugin needs to override at least the authenticate
method to be a valid plugin.
"""
auth_system = None
opt_names = []
common_opt_names = [
"auth_system",
"username",
"password",
"tenant_name",
"token",
"auth_url",
]
def __init__(self, auth_system=None, **kwargs):
self.auth_system = auth_system or self.auth_system
self.opts = dict((name, kwargs.get(name))
for name in self.opt_names)
@staticmethod
def _parser_add_opt(parser, opt):
"""Add an option to parser in two variants.
:param opt: option name (with underscores)
"""
dashed_opt = opt.replace("_", "-")
env_var = "OS_%s" % opt.upper()
arg_default = os.environ.get(env_var, "")
arg_help = "Defaults to env[%s]." % env_var
parser.add_argument(
"--os-%s" % dashed_opt,
metavar="<%s>" % dashed_opt,
default=arg_default,
help=arg_help)
parser.add_argument(
"--os_%s" % opt,
metavar="<%s>" % dashed_opt,
help=argparse.SUPPRESS)
@classmethod
def add_opts(cls, parser):
"""Populate the parser with the options for this plugin.
"""
for opt in cls.opt_names:
# use `BaseAuthPlugin.common_opt_names` since it is never
# changed in child classes
if opt not in BaseAuthPlugin.common_opt_names:
cls._parser_add_opt(parser, opt)
@classmethod
def add_common_opts(cls, parser):
"""Add options that are common for several plugins.
"""
for opt in cls.common_opt_names:
cls._parser_add_opt(parser, opt)
@staticmethod
def get_opt(opt_name, args):
"""Return option name and value.
:param opt_name: name of the option, e.g., "username"
:param args: parsed arguments
"""
return (opt_name, getattr(args, "os_%s" % opt_name, None))
def parse_opts(self, args):
"""Parse the actual auth-system options if any.
This method is expected to populate the attribute `self.opts` with a
dict containing the options and values needed to make authentication.
"""
self.opts.update(dict(self.get_opt(opt_name, args)
for opt_name in self.opt_names))
def authenticate(self, http_client):
"""Authenticate using plugin defined method.
The method usually analyses `self.opts` and performs
a request to authentication server.
:param http_client: client object that needs authentication
:type http_client: HTTPClient
:raises: AuthorizationFailure
"""
self.sufficient_options()
self._do_authenticate(http_client)
@abc.abstractmethod
def _do_authenticate(self, http_client):
"""Protected method for authentication.
"""
def sufficient_options(self):
"""Check if all required options are present.
:raises: AuthPluginOptionsMissing
"""
missing = [opt
for opt in self.opt_names
if not self.opts.get(opt)]
if missing:
raise exceptions.AuthPluginOptionsMissing(missing)
@abc.abstractmethod
def token_and_endpoint(self, endpoint_type, service_type):
"""Return token and endpoint.
:param service_type: Service type of the endpoint
:type service_type: string
:param endpoint_type: Type of endpoint.
Possible values: public or publicURL,
internal or internalURL,
admin or adminURL
:type endpoint_type: string
:returns: tuple of token and endpoint strings
:raises: EndpointException
"""

View File

@ -0,0 +1,515 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 OpenStack Foundation
# Copyright 2012 Grid Dynamics
# Copyright 2013 OpenStack Foundation
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
"""
Base utilities to build API operation managers and objects on top of.
"""
# E1102: %s is not callable
# pylint: disable=E1102
import abc
import copy
import six
from six.moves.urllib import parse
from dcmanagerclient.openstack.common.apiclient import exceptions
from dcmanagerclient.openstack.common.gettextutils import _
from dcmanagerclient.openstack.common import strutils
def getid(obj):
"""Return id if argument is a Resource.
Abstracts the common pattern of allowing both an object or an object's ID
(UUID) as a parameter when dealing with relationships.
"""
try:
if obj.uuid:
return obj.uuid
except AttributeError:
pass
try:
return obj.id
except AttributeError:
return obj
# TODO(aababilov): call run_hooks() in HookableMixin's child classes
class HookableMixin(object):
"""Mixin so classes can register and run hooks."""
_hooks_map = {}
@classmethod
def add_hook(cls, hook_type, hook_func):
"""Add a new hook of specified type.
:param cls: class that registers hooks
:param hook_type: hook type, e.g., '__pre_parse_args__'
:param hook_func: hook function
"""
if hook_type not in cls._hooks_map:
cls._hooks_map[hook_type] = []
cls._hooks_map[hook_type].append(hook_func)
@classmethod
def run_hooks(cls, hook_type, *args, **kwargs):
"""Run all hooks of specified type.
:param cls: class that registers hooks
:param hook_type: hook type, e.g., '__pre_parse_args__'
:param args: args to be passed to every hook function
:param kwargs: kwargs to be passed to every hook function
"""
hook_funcs = cls._hooks_map.get(hook_type) or []
for hook_func in hook_funcs:
hook_func(*args, **kwargs)
class BaseManager(HookableMixin):
"""Basic manager type providing common operations.
Managers interact with a particular type of API (servers, flavors, images,
etc.) and provide CRUD operations for them.
"""
resource_class = None
def __init__(self, client):
"""Initializes BaseManager with `client`.
:param client: instance of BaseClient descendant for HTTP requests
"""
super(BaseManager, self).__init__()
self.client = client
def _list(self, url, response_key, obj_class=None, json=None):
"""List the collection.
:param url: a partial URL, e.g., '/servers'
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'
:param obj_class: class for constructing the returned objects
(self.resource_class will be used by default)
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
"""
if json:
body = self.client.post(url, json=json).json()
else:
body = self.client.get(url).json()
if obj_class is None:
obj_class = self.resource_class
data = body[response_key]
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
# unlike other services which just return the list...
try:
data = data['values']
except (KeyError, TypeError):
pass
return [obj_class(self, res, loaded=True) for res in data if res]
def _get(self, url, response_key):
"""Get an object from collection.
:param url: a partial URL, e.g., '/servers'
:param response_key: the key to be looked up in response dictionary,
e.g., 'server'
"""
body = self.client.get(url).json()
return self.resource_class(self, body[response_key], loaded=True)
def _head(self, url):
"""Retrieve request headers for an object.
:param url: a partial URL, e.g., '/servers'
"""
resp = self.client.head(url)
return resp.status_code == 204
def _post(self, url, json, response_key, return_raw=False):
"""Create an object.
:param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'
:param return_raw: flag to force returning raw JSON instead of
Python object of self.resource_class
"""
body = self.client.post(url, json=json).json()
if return_raw:
return body[response_key]
return self.resource_class(self, body[response_key])
def _put(self, url, json=None, response_key=None):
"""Update an object with PUT method.
:param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'
"""
resp = self.client.put(url, json=json)
# PUT requests may not return a body
if resp.content:
body = resp.json()
if response_key is not None:
return self.resource_class(self, body[response_key])
else:
return self.resource_class(self, body)
def _patch(self, url, json=None, response_key=None):
"""Update an object with PATCH method.
:param url: a partial URL, e.g., '/servers'
:param json: data that will be encoded as JSON and passed in POST
request (GET will be sent by default)
:param response_key: the key to be looked up in response dictionary,
e.g., 'servers'
"""
body = self.client.patch(url, json=json).json()
if response_key is not None:
return self.resource_class(self, body[response_key])
else:
return self.resource_class(self, body)
def _delete(self, url):
"""Delete an object.
:param url: a partial URL, e.g., '/servers/my-server'
"""
return self.client.delete(url)
@six.add_metaclass(abc.ABCMeta)
class ManagerWithFind(BaseManager):
"""Manager with additional `find()`/`findall()` methods."""
@abc.abstractmethod
def list(self):
pass
def find(self, **kwargs):
"""Find a single item with attributes matching ``**kwargs``.
This isn't very efficient: it loads the entire list then filters on
the Python side.
"""
matches = self.findall(**kwargs)
num_matches = len(matches)
if num_matches == 0:
msg = _("No %(name)s matching %(args)s.") % {
'name': self.resource_class.__name__,
'args': kwargs
}
raise exceptions.NotFound(msg)
elif num_matches > 1:
raise exceptions.NoUniqueMatch()
else:
return matches[0]
def findall(self, **kwargs):
"""Find all items with attributes matching ``**kwargs``.
This isn't very efficient: it loads the entire list then filters on
the Python side.
"""
found = []
searches = kwargs.items()
for obj in self.list():
try:
if all(getattr(obj, attr) == value
for (attr, value) in searches):
found.append(obj)
except AttributeError:
continue
return found
class CrudManager(BaseManager):
"""Base manager class for manipulating entities.
Children of this class are expected to define a `collection_key` and `key`.
- `collection_key`: Usually a plural noun by convention (e.g. `entities`);
used to refer collections in both URL's (e.g. `/v3/entities`) and JSON
objects containing a list of member resources (e.g. `{'entities': [{},
{}, {}]}`).
- `key`: Usually a singular noun by convention (e.g. `entity`); used to
refer to an individual member of the collection.
"""
collection_key = None
key = None
def build_url(self, base_url=None, **kwargs):
"""Builds a resource URL for the given kwargs.
Given an example collection where `collection_key = 'entities'` and
`key = 'entity'`, the following URL's could be generated.
By default, the URL will represent a collection of entities, e.g.::
/entities
If kwargs contains an `entity_id`, then the URL will represent a
specific member, e.g.::
/entities/{entity_id}
:param base_url: if provided, the generated URL will be appended to it
"""
url = base_url if base_url is not None else ''
url += '/%s' % self.collection_key
# do we have a specific entity?
entity_id = kwargs.get('%s_id' % self.key)
if entity_id is not None:
url += '/%s' % entity_id
return url
def _filter_kwargs(self, kwargs):
"""Drop null values and handle ids."""
for key, ref in six.iteritems(kwargs.copy()):
if ref is None:
kwargs.pop(key)
else:
if isinstance(ref, Resource):
kwargs.pop(key)
kwargs['%s_id' % key] = getid(ref)
return kwargs
def create(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._post(
self.build_url(**kwargs),
{self.key: kwargs},
self.key)
def get(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._get(
self.build_url(**kwargs),
self.key)
def head(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._head(self.build_url(**kwargs))
def list(self, base_url=None, **kwargs):
"""List the collection.
:param base_url: if provided, the generated URL will be appended to it
"""
kwargs = self._filter_kwargs(kwargs)
return self._list(
'%(base_url)s%(query)s' % {
'base_url': self.build_url(base_url=base_url, **kwargs),
'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
},
self.collection_key)
def put(self, base_url=None, **kwargs):
"""Update an element.
:param base_url: if provided, the generated URL will be appended to it
"""
kwargs = self._filter_kwargs(kwargs)
return self._put(self.build_url(base_url=base_url, **kwargs))
def update(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
params = kwargs.copy()
params.pop('%s_id' % self.key)
return self._patch(
self.build_url(**kwargs),
{self.key: params},
self.key)
def delete(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._delete(
self.build_url(**kwargs))
def find(self, base_url=None, **kwargs):
"""Find a single item with attributes matching ``**kwargs``.
:param base_url: if provided, the generated URL will be appended to it
"""
kwargs = self._filter_kwargs(kwargs)
rl = self._list(
'%(base_url)s%(query)s' % {
'base_url': self.build_url(base_url=base_url, **kwargs),
'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
},
self.collection_key)
num = len(rl)
if num == 0:
msg = _("No %(name)s matching %(args)s.") % {
'name': self.resource_class.__name__,
'args': kwargs
}
raise exceptions.NotFound(404, msg)
elif num > 1:
raise exceptions.NoUniqueMatch
else:
return rl[0]
class Extension(HookableMixin):
"""Extension descriptor."""
SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__')
manager_class = None
def __init__(self, name, module):
super(Extension, self).__init__()
self.name = name
self.module = module
self._parse_extension_module()
def _parse_extension_module(self):
self.manager_class = None
for attr_name, attr_value in self.module.__dict__.items():
if attr_name in self.SUPPORTED_HOOKS:
self.add_hook(attr_name, attr_value)
else:
try:
if issubclass(attr_value, BaseManager):
self.manager_class = attr_value
except TypeError:
pass
def __repr__(self):
return "<Extension '%s'>" % self.name
class Resource(object):
"""Base class for OpenStack resources (tenant, user, etc.).
This is pretty much just a bag for attributes.
"""
HUMAN_ID = False
NAME_ATTR = 'name'
def __init__(self, manager, info, loaded=False):
"""Populate and bind to a manager.
:param manager: BaseManager object
:param info: dictionary representing resource attributes
:param loaded: prevent lazy-loading if set to True
"""
self.manager = manager
self._info = info
self._add_details(info)
self._loaded = loaded
def __repr__(self):
reprkeys = sorted(k
for k in self.__dict__.keys()
if k[0] != '_' and k != 'manager')
info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
return "<%s %s>" % (self.__class__.__name__, info)
@property
def human_id(self):
"""Human-readable ID which can be used for bash completion.
"""
if self.NAME_ATTR in self.__dict__ and self.HUMAN_ID:
return strutils.to_slug(getattr(self, self.NAME_ATTR))
return None
def _add_details(self, info):
for (k, v) in six.iteritems(info):
try:
setattr(self, k, v)
self._info[k] = v
except AttributeError:
# In this case we already defined the attribute on the class
pass
def __getattr__(self, k):
if k not in self.__dict__:
# NOTE(bcwaldon): disallow lazy-loading if already loaded once
if not self.is_loaded():
self.get()
return self.__getattr__(k)
raise AttributeError(k)
else:
return self.__dict__[k]
def get(self):
"""Support for lazy loading details.
Some clients, such as novaclient have the option to lazy load the
details, details which can be loaded with this function.
"""
# set_loaded() first ... so if we have to bail, we know we tried.
self.set_loaded(True)
if not hasattr(self.manager, 'get'):
return
new = self.manager.get(self.id)
if new:
self._add_details(new._info)
def __eq__(self, other):
if not isinstance(other, Resource):
return NotImplemented
# two resources of different types are not equal
if not isinstance(other, self.__class__):
return False
if hasattr(self, 'id') and hasattr(other, 'id'):
return self.id == other.id
return self._info == other._info
def is_loaded(self):
return self._loaded
def set_loaded(self, val):
self._loaded = val
def to_dict(self):
return copy.deepcopy(self._info)

View File

@ -0,0 +1,370 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 OpenStack Foundation
# Copyright 2011 Piston Cloud Computing, Inc.
# Copyright 2013 Alessio Ababilov
# Copyright 2013 Grid Dynamics
# Copyright 2013 OpenStack Foundation
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
"""
OpenStack Client interface. Handles the REST calls and responses.
"""
# E0202: An attribute inherited from %s hide this method
# pylint: disable=E0202
import logging
import time
try:
import simplejson as json
except ImportError:
import json
import requests
from dcmanagerclient.openstack.common.apiclient import exceptions
from dcmanagerclient.openstack.common.gettextutils import _
from dcmanagerclient.openstack.common import importutils
_logger = logging.getLogger(__name__)
class HTTPClient(object):
"""This client handles sending HTTP requests to OpenStack servers.
Features:
- share authentication information between several clients to different
services (e.g., for compute and image clients);
- reissue authentication request for expired tokens;
- encode/decode JSON bodies;
- raise exceptions on HTTP errors;
- pluggable authentication;
- store authentication information in a keyring;
- store time spent for requests;
- register clients for particular services, so one can use
`http_client.identity` or `http_client.compute`;
- log requests and responses in a format that is easy to copy-and-paste
into terminal and send the same request with curl.
"""
user_agent = "dcmanagerclient.openstack.common.apiclient"
def __init__(self,
auth_plugin,
region_name=None,
endpoint_type="publicURL",
original_ip=None,
verify=True,
cert=None,
timeout=None,
timings=False,
keyring_saver=None,
debug=False,
user_agent=None,
http=None):
self.auth_plugin = auth_plugin
self.endpoint_type = endpoint_type
self.region_name = region_name
self.original_ip = original_ip
self.timeout = timeout
self.verify = verify
self.cert = cert
self.keyring_saver = keyring_saver
self.debug = debug
self.user_agent = user_agent or self.user_agent
self.times = [] # [("item", starttime, endtime), ...]
self.timings = timings
# requests within the same session can reuse TCP connections from pool
self.http = http or requests.Session()
self.cached_token = None
def _http_log_req(self, method, url, kwargs):
if not self.debug:
return
string_parts = [
"curl -i",
"-X '%s'" % method,
"'%s'" % url,
]
for element in kwargs['headers']:
header = "-H '%s: %s'" % (element, kwargs['headers'][element])
string_parts.append(header)
_logger.debug("REQ: %s" % " ".join(string_parts))
if 'data' in kwargs:
_logger.debug("REQ BODY: %s\n" % (kwargs['data']))
def _http_log_resp(self, resp):
if not self.debug:
return
_logger.debug(
"RESP: [%s] %s\n",
resp.status_code,
resp.headers)
if resp._content_consumed:
_logger.debug(
"RESP BODY: %s\n",
resp.text)
def serialize(self, kwargs):
if kwargs.get('json') is not None:
kwargs['headers']['Content-Type'] = 'application/json'
kwargs['data'] = json.dumps(kwargs['json'])
try:
del kwargs['json']
except KeyError:
pass
def get_timings(self):
return self.times
def reset_timings(self):
self.times = []
def request(self, method, url, **kwargs):
"""Send an http request with the specified characteristics.
Wrapper around `requests.Session.request` to handle tasks such as
setting headers, JSON encoding/decoding, and error handling.
:param method: method of HTTP request
:param url: URL of HTTP request
:param kwargs: any other parameter that can be passed to
requests.Session.request (such as `headers`) or `json`
that will be encoded as JSON and used as `data` argument
"""
kwargs.setdefault("headers", kwargs.get("headers", {}))
kwargs["headers"]["User-Agent"] = self.user_agent
if self.original_ip:
kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % (
self.original_ip, self.user_agent)
if self.timeout is not None:
kwargs.setdefault("timeout", self.timeout)
kwargs.setdefault("verify", self.verify)
if self.cert is not None:
kwargs.setdefault("cert", self.cert)
self.serialize(kwargs)
self._http_log_req(method, url, kwargs)
if self.timings:
start_time = time.time()
resp = self.http.request(method, url, **kwargs)
if self.timings:
self.times.append(("%s %s" % (method, url),
start_time, time.time()))
self._http_log_resp(resp)
if resp.status_code >= 400:
_logger.debug(
"Request returned failure status: %s",
resp.status_code)
raise exceptions.from_response(resp, method, url)
return resp
@staticmethod
def concat_url(endpoint, url):
"""Concatenate endpoint and final URL.
E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to
"http://keystone/v2.0/tokens".
:param endpoint: the base URL
:param url: the final URL
"""
return "%s/%s" % (endpoint.rstrip("/"), url.strip("/"))
def client_request(self, client, method, url, **kwargs):
"""Send an http request using `client`'s endpoint and specified `url`.
If request was rejected as unauthorized (possibly because the token is
expired), issue one authorization attempt and send the request once
again.
:param client: instance of BaseClient descendant
:param method: method of HTTP request
:param url: URL of HTTP request
:param kwargs: any other parameter that can be passed to
`HTTPClient.request`
"""
filter_args = {
"endpoint_type": client.endpoint_type or self.endpoint_type,
"service_type": client.service_type,
}
token, endpoint = (self.cached_token, client.cached_endpoint)
just_authenticated = False
if not (token and endpoint):
try:
token, endpoint = self.auth_plugin.token_and_endpoint(
**filter_args)
except exceptions.EndpointException:
pass
if not (token and endpoint):
self.authenticate()
just_authenticated = True
token, endpoint = self.auth_plugin.token_and_endpoint(
**filter_args)
if not (token and endpoint):
raise exceptions.AuthorizationFailure(
_("Cannot find endpoint or token for request"))
old_token_endpoint = (token, endpoint)
kwargs.setdefault("headers", {})["X-Auth-Token"] = token
self.cached_token = token
client.cached_endpoint = endpoint
# Perform the request once. If we get Unauthorized, then it
# might be because the auth token expired, so try to
# re-authenticate and try again. If it still fails, bail.
try:
return self.request(
method, self.concat_url(endpoint, url), **kwargs)
except exceptions.Unauthorized as unauth_ex:
if just_authenticated:
raise
self.cached_token = None
client.cached_endpoint = None
self.authenticate()
try:
token, endpoint = self.auth_plugin.token_and_endpoint(
**filter_args)
except exceptions.EndpointException:
raise unauth_ex
if (not (token and endpoint) or
old_token_endpoint == (token, endpoint)):
raise unauth_ex
self.cached_token = token
client.cached_endpoint = endpoint
kwargs["headers"]["X-Auth-Token"] = token
return self.request(
method, self.concat_url(endpoint, url), **kwargs)
def add_client(self, base_client_instance):
"""Add a new instance of :class:`BaseClient` descendant.
`self` will store a reference to `base_client_instance`.
Example:
>>> def test_clients():
... from keystoneclient.auth import keystone
... from openstack.common.apiclient import client
... auth = keystone.KeystoneAuthPlugin(
... username="user", password="pass", tenant_name="tenant",
... auth_url="http://auth:5000/v2.0")
... openstack_client = client.HTTPClient(auth)
... # create nova client
... from novaclient.v1_1 import client
... client.Client(openstack_client)
... # create keystone client
... from keystoneclient.v2_0 import client
... client.Client(openstack_client)
... # use them
... openstack_client.identity.tenants.list()
... openstack_client.compute.servers.list()
"""
service_type = base_client_instance.service_type
if service_type and not hasattr(self, service_type):
setattr(self, service_type, base_client_instance)
def authenticate(self):
self.auth_plugin.authenticate(self)
# Store the authentication results in the keyring for later requests
if self.keyring_saver:
self.keyring_saver.save(self)
class BaseClient(object):
"""Top-level object to access the OpenStack API.
This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient`
will handle a bunch of issues such as authentication.
"""
service_type = None
endpoint_type = None # "publicURL" will be used
cached_endpoint = None
def __init__(self, http_client, extensions=None):
self.http_client = http_client
http_client.add_client(self)
# Add in any extensions...
if extensions:
for extension in extensions:
if extension.manager_class:
setattr(self, extension.name,
extension.manager_class(self))
def client_request(self, method, url, **kwargs):
return self.http_client.client_request(
self, method, url, **kwargs)
def head(self, url, **kwargs):
return self.client_request("HEAD", url, **kwargs)
def get(self, url, **kwargs):
return self.client_request("GET", url, **kwargs)
def post(self, url, **kwargs):
return self.client_request("POST", url, **kwargs)
def put(self, url, **kwargs):
return self.client_request("PUT", url, **kwargs)
def delete(self, url, **kwargs):
return self.client_request("DELETE", url, **kwargs)
def patch(self, url, **kwargs):
return self.client_request("PATCH", url, **kwargs)
@staticmethod
def get_class(api_name, version, version_map):
"""Returns the client class for the requested API version
:param api_name: the name of the API, e.g. 'compute', 'image', etc
:param version: the requested API version
:param version_map: a dict of client classes keyed by version
:rtype: a client class for the requested API version
"""
try:
client_path = version_map[str(version)]
except (KeyError, ValueError):
msg = _("Invalid %(api_name)s client version '%(version)s'. "
"Must be one of: %(version_map)s") % \
{'api_name': api_name,
'version': version,
'version_map': ', '.join(version_map.keys())
}
raise exceptions.UnsupportedVersion(msg)
return importutils.import_class(client_path)

View File

@ -0,0 +1,473 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 Nebula, Inc.
# Copyright 2013 Alessio Ababilov
# Copyright 2013 OpenStack Foundation
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
"""
Exception definitions.
"""
import inspect
import sys
import six
from dcmanagerclient.openstack.common.gettextutils import _
class ClientException(Exception):
"""The base exception class for all exceptions this library raises.
"""
pass
class MissingArgs(ClientException):
"""Supplied arguments are not sufficient for calling a function."""
def __init__(self, missing):
self.missing = missing
msg = _("Missing arguments: %s") % ", ".join(missing)
super(MissingArgs, self).__init__(msg)
class ValidationError(ClientException):
"""Error in validation on API client side."""
pass
class UnsupportedVersion(ClientException):
"""User is trying to use an unsupported version of the API."""
pass
class CommandError(ClientException):
"""Error in CLI tool."""
pass
class AuthorizationFailure(ClientException):
"""Cannot authorize API client."""
pass
class ConnectionRefused(ClientException):
"""Cannot connect to API service."""
pass
class AuthPluginOptionsMissing(AuthorizationFailure):
"""Auth plugin misses some options."""
def __init__(self, opt_names):
super(AuthPluginOptionsMissing, self).__init__(
_("Authentication failed. Missing options: %s") %
", ".join(opt_names))
self.opt_names = opt_names
class AuthSystemNotFound(AuthorizationFailure):
"""User has specified a AuthSystem that is not installed."""
def __init__(self, auth_system):
super(AuthSystemNotFound, self).__init__(
_("AuthSystemNotFound: %s") % repr(auth_system))
self.auth_system = auth_system
class NoUniqueMatch(ClientException):
"""Multiple entities found instead of one."""
pass
class EndpointException(ClientException):
"""Something is rotten in Service Catalog."""
pass
class EndpointNotFound(EndpointException):
"""Could not find requested endpoint in Service Catalog."""
pass
class AmbiguousEndpoints(EndpointException):
"""Found more than one matching endpoint in Service Catalog."""
def __init__(self, endpoints=None):
super(AmbiguousEndpoints, self).__init__(
_("AmbiguousEndpoints: %s") % repr(endpoints))
self.endpoints = endpoints
class HttpError(ClientException):
"""The base exception class for all HTTP exceptions."""
http_status = 0
message = _("HTTP Error")
def __init__(self, message=None, details=None,
response=None, request_id=None,
url=None, method=None, http_status=None):
self.http_status = http_status or self.http_status
self.message = message or self.message
self.details = details
self.request_id = request_id
self.response = response
self.url = url
self.method = method
formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
if request_id:
formatted_string += " (Request-ID: %s)" % request_id
super(HttpError, self).__init__(formatted_string)
class HTTPRedirection(HttpError):
"""HTTP Redirection."""
message = _("HTTP Redirection")
class HTTPClientError(HttpError):
"""Client-side HTTP error.
Exception for cases in which the client seems to have erred.
"""
message = _("HTTP Client Error")
class HttpServerError(HttpError):
"""Server-side HTTP error.
Exception for cases in which the server is aware that it has
erred or is incapable of performing the request.
"""
message = _("HTTP Server Error")
class MultipleChoices(HTTPRedirection):
"""HTTP 300 - Multiple Choices.
Indicates multiple options for the resource that the client may follow.
"""
http_status = 300
message = _("Multiple Choices")
class BadRequest(HTTPClientError):
"""HTTP 400 - Bad Request.
The request cannot be fulfilled due to bad syntax.
"""
http_status = 400
message = _("Bad Request")
class Unauthorized(HTTPClientError):
"""HTTP 401 - Unauthorized.
Similar to 403 Forbidden, but specifically for use when authentication
is required and has failed or has not yet been provided.
"""
http_status = 401
message = _("Unauthorized")
class PaymentRequired(HTTPClientError):
"""HTTP 402 - Payment Required.
Reserved for future use.
"""
http_status = 402
message = _("Payment Required")
class Forbidden(HTTPClientError):
"""HTTP 403 - Forbidden.
The request was a valid request, but the server is refusing to respond
to it.
"""
http_status = 403
message = _("Forbidden")
class NotFound(HTTPClientError):
"""HTTP 404 - Not Found.
The requested resource could not be found but may be available again
in the future.
"""
http_status = 404
message = _("Not Found")
class MethodNotAllowed(HTTPClientError):
"""HTTP 405 - Method Not Allowed.
A request was made of a resource using a request method not supported
by that resource.
"""
http_status = 405
message = _("Method Not Allowed")
class NotAcceptable(HTTPClientError):
"""HTTP 406 - Not Acceptable.
The requested resource is only capable of generating content not
acceptable according to the Accept headers sent in the request.
"""
http_status = 406
message = _("Not Acceptable")
class ProxyAuthenticationRequired(HTTPClientError):
"""HTTP 407 - Proxy Authentication Required.
The client must first authenticate itself with the proxy.
"""
http_status = 407
message = _("Proxy Authentication Required")
class RequestTimeout(HTTPClientError):
"""HTTP 408 - Request Timeout.
The server timed out waiting for the request.
"""
http_status = 408
message = _("Request Timeout")
class Conflict(HTTPClientError):
"""HTTP 409 - Conflict.
Indicates that the request could not be processed because of conflict
in the request, such as an edit conflict.
"""
http_status = 409
message = _("Conflict")
class Gone(HTTPClientError):
"""HTTP 410 - Gone.
Indicates that the resource requested is no longer available and will
not be available again.
"""
http_status = 410
message = _("Gone")
class LengthRequired(HTTPClientError):
"""HTTP 411 - Length Required.
The request did not specify the length of its content, which is
required by the requested resource.
"""
http_status = 411
message = _("Length Required")
class PreconditionFailed(HTTPClientError):
"""HTTP 412 - Precondition Failed.
The server does not meet one of the preconditions that the requester
put on the request.
"""
http_status = 412
message = _("Precondition Failed")
class RequestEntityTooLarge(HTTPClientError):
"""HTTP 413 - Request Entity Too Large.
The request is larger than the server is willing or able to process.
"""
http_status = 413
message = _("Request Entity Too Large")
def __init__(self, *args, **kwargs):
try:
self.retry_after = int(kwargs.pop('retry_after'))
except (KeyError, ValueError):
self.retry_after = 0
super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
class RequestUriTooLong(HTTPClientError):
"""HTTP 414 - Request-URI Too Long.
The URI provided was too long for the server to process.
"""
http_status = 414
message = _("Request-URI Too Long")
class UnsupportedMediaType(HTTPClientError):
"""HTTP 415 - Unsupported Media Type.
The request entity has a media type which the server or resource does
not support.
"""
http_status = 415
message = _("Unsupported Media Type")
class RequestedRangeNotSatisfiable(HTTPClientError):
"""HTTP 416 - Requested Range Not Satisfiable.
The client has asked for a portion of the file, but the server cannot
supply that portion.
"""
http_status = 416
message = _("Requested Range Not Satisfiable")
class ExpectationFailed(HTTPClientError):
"""HTTP 417 - Expectation Failed.
The server cannot meet the requirements of the Expect request-header field.
"""
http_status = 417
message = _("Expectation Failed")
class UnprocessableEntity(HTTPClientError):
"""HTTP 422 - Unprocessable Entity.
The request was well-formed but was unable to be followed due to semantic
errors.
"""
http_status = 422
message = _("Unprocessable Entity")
class InternalServerError(HttpServerError):
"""HTTP 500 - Internal Server Error.
A generic error message, given when no more specific message is suitable.
"""
http_status = 500
message = _("Internal Server Error")
# NotImplemented is a python keyword.
class HttpNotImplemented(HttpServerError):
"""HTTP 501 - Not Implemented.
The server either does not recognize the request method, or it lacks
the ability to fulfill the request.
"""
http_status = 501
message = _("Not Implemented")
class BadGateway(HttpServerError):
"""HTTP 502 - Bad Gateway.
The server was acting as a gateway or proxy and received an invalid
response from the upstream server.
"""
http_status = 502
message = _("Bad Gateway")
class ServiceUnavailable(HttpServerError):
"""HTTP 503 - Service Unavailable.
The server is currently unavailable.
"""
http_status = 503
message = _("Service Unavailable")
class GatewayTimeout(HttpServerError):
"""HTTP 504 - Gateway Timeout.
The server was acting as a gateway or proxy and did not receive a timely
response from the upstream server.
"""
http_status = 504
message = _("Gateway Timeout")
class HttpVersionNotSupported(HttpServerError):
"""HTTP 505 - HttpVersion Not Supported.
The server does not support the HTTP protocol version used in the request.
"""
http_status = 505
message = _("HTTP Version Not Supported")
# _code_map contains all the classes that have http_status attribute.
_code_map = dict(
(getattr(obj, 'http_status', None), obj)
for name, obj in six.iteritems(vars(sys.modules[__name__]))
if inspect.isclass(obj) and getattr(obj, 'http_status', False)
)
def from_response(response, method, url):
"""Returns an instance of :class:`HttpError` or subclass based on response.
:param response: instance of `requests.Response` class
:param method: HTTP method used for request
:param url: URL used for request
"""
req_id = response.headers.get("x-openstack-request-id")
# NOTE(hdd) true for older versions of nova and cinder
if not req_id:
req_id = response.headers.get("x-compute-request-id")
kwargs = {
"http_status": response.status_code,
"response": response,
"method": method,
"url": url,
"request_id": req_id,
}
if "retry-after" in response.headers:
kwargs["retry_after"] = response.headers["retry-after"]
content_type = response.headers.get("Content-Type", "")
if content_type.startswith("application/json"):
try:
body = response.json()
except ValueError:
pass
else:
if isinstance(body, dict):
error = list(body.values())[0]
kwargs["message"] = error.get("message")
kwargs["details"] = error.get("details")
elif content_type.startswith("text/"):
kwargs["details"] = response.text
try:
cls = _code_map[response.status_code]
except KeyError:
if 500 <= response.status_code < 600:
cls = HttpServerError
elif 400 <= response.status_code < 500:
cls = HTTPClientError
else:
cls = HttpError
return cls(**kwargs)

View File

@ -0,0 +1,184 @@
# Copyright 2013 OpenStack Foundation
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
"""
A fake server that "responds" to API methods with pre-canned responses.
All of these responses come from the spec, so if for some reason the spec's
wrong the tests might raise AssertionError. I've indicated in comments the
places where actual behavior differs from the spec.
"""
# W0102: Dangerous default value %s as argument
# pylint: disable=W0102
import json
import requests
import six
from six.moves.urllib import parse
from dcmanagerclient.openstack.common.apiclient import client
def assert_has_keys(dct, required=None, optional=None):
required = required or []
optional = optional or []
for k in required:
try:
assert k in dct
except AssertionError:
extra_keys = set(dct.keys()).difference(set(required + optional))
raise AssertionError("found unexpected keys: %s" %
list(extra_keys))
class TestResponse(requests.Response):
"""Wrap requests.Response and provide a convenient initialization.
"""
def __init__(self, data):
super(TestResponse, self).__init__()
self._content_consumed = True
if isinstance(data, dict):
self.status_code = data.get('status_code', 200)
# Fake the text attribute to streamline Response creation
text = data.get('text', "")
if isinstance(text, (dict, list)):
self._content = json.dumps(text)
default_headers = {
"Content-Type": "application/json",
}
else:
self._content = text
default_headers = {}
if six.PY3 and isinstance(self._content, six.string_types):
self._content = self._content.encode('utf-8', 'strict')
self.headers = data.get('headers') or default_headers
else:
self.status_code = data
def __eq__(self, other):
return (self.status_code == other.status_code and
self.headers == other.headers and
self._content == other._content)
class FakeHTTPClient(client.HTTPClient):
def __init__(self, *args, **kwargs):
self.callstack = []
self.fixtures = kwargs.pop("fixtures", None) or {}
if not args and not ("auth_plugin" in kwargs):
args = (None,)
super(FakeHTTPClient, self).__init__(*args, **kwargs)
def assert_called(self, method, url, body=None, pos=-1):
"""Assert than an API method was just called.
"""
expected = (method, url)
called = self.callstack[pos][0:2]
assert self.callstack, \
"Expected %s %s but no calls were made." % expected
assert expected == called, 'Expected %s %s; got %s %s' % \
(expected + called)
if body is not None:
if self.callstack[pos][3] != body:
raise AssertionError('%r != %r' %
(self.callstack[pos][3], body))
def assert_called_anytime(self, method, url, body=None):
"""Assert than an API method was called anytime in the test.
"""
expected = (method, url)
assert self.callstack, \
"Expected %s %s but no calls were made." % expected
found = False
entry = None
for entry in self.callstack:
if expected == entry[0:2]:
found = True
break
assert found, 'Expected %s %s; got %s' % \
(method, url, self.callstack)
if body is not None:
assert entry[3] == body, "%s != %s" % (entry[3], body)
self.callstack = []
def clear_callstack(self):
self.callstack = []
def authenticate(self):
pass
def client_request(self, client, method, url, **kwargs):
# Check that certain things are called correctly
if method in ["GET", "DELETE"]:
assert "json" not in kwargs
# Note the call
self.callstack.append(
(method,
url,
kwargs.get("headers") or {},
kwargs.get("json") or kwargs.get("data")))
try:
fixture = self.fixtures[url][method]
except KeyError:
pass
else:
return TestResponse({"headers": fixture[0],
"text": fixture[1]})
# Call the method
args = parse.parse_qsl(parse.urlparse(url)[4])
kwargs.update(args)
munged_url = url.rsplit('?', 1)[0]
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
munged_url = munged_url.replace('-', '_')
callback = "%s_%s" % (method.lower(), munged_url)
if not hasattr(self, callback):
raise AssertionError('Called unknown API method: %s %s, '
'expected fakes method name: %s' %
(method, url, callback))
resp = getattr(self, callback)(**kwargs)
if len(resp) == 3:
status, headers, body = resp
else:
status, body = resp
headers = {}
return TestResponse({
"status_code": status,
"text": body,
"headers": headers,
})

View File

@ -0,0 +1,319 @@
# Copyright 2012 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
# W0603: Using the global statement
# W0621: Redefining name %s from outer scope
# pylint: disable=W0603,W0621
from __future__ import print_function
import getpass
import inspect
import os
import sys
import textwrap
import prettytable
import six
from six import moves
from dcmanagerclient.openstack.common.apiclient import exceptions
from dcmanagerclient.openstack.common.gettextutils import _
from dcmanagerclient.openstack.common import strutils
from dcmanagerclient.openstack.common import uuidutils
def validate_args(fn, *args, **kwargs):
"""Check that the supplied args are sufficient for calling a function.
>>> validate_args(lambda a: None)
Traceback (most recent call last):
...
MissingArgs: Missing argument(s): a
>>> validate_args(lambda a, b, c, d: None, 0, c=1)
Traceback (most recent call last):
...
MissingArgs: Missing argument(s): b, d
:param fn: the function to check
:param arg: the positional arguments supplied
:param kwargs: the keyword arguments supplied
"""
argspec = inspect.getargspec(fn)
num_defaults = len(argspec.defaults or [])
required_args = argspec.args[:len(argspec.args) - num_defaults]
def isbound(method):
return getattr(method, '__self__', None) is not None
if isbound(fn):
required_args.pop(0)
missing = [arg for arg in required_args if arg not in kwargs]
missing = missing[len(args):]
if missing:
raise exceptions.MissingArgs(missing)
def arg(*args, **kwargs):
"""Decorator for CLI args.
Example:
>>> @arg("name", help="Name of the new entity")
... def entity_create(args):
... pass
"""
def _decorator(func):
add_arg(func, *args, **kwargs)
return func
return _decorator
def env(*args, **kwargs):
"""Returns the first environment variable set.
If all are empty, defaults to '' or keyword arg `default`.
"""
for arg in args:
value = os.environ.get(arg)
if value:
return value
return kwargs.get('default', '')
def add_arg(func, *args, **kwargs):
"""Bind CLI arguments to a shell.py `do_foo` function."""
if not hasattr(func, 'arguments'):
func.arguments = []
# NOTE(sirp): avoid dups that can occur when the module is shared across
# tests.
if (args, kwargs) not in func.arguments:
# Because of the semantics of decorator composition if we just append
# to the options list positional options will appear to be backwards.
func.arguments.insert(0, (args, kwargs))
def unauthenticated(func):
"""Adds 'unauthenticated' attribute to decorated function.
Usage:
>>> @unauthenticated
... def mymethod(f):
... pass
"""
func.unauthenticated = True
return func
def isunauthenticated(func):
"""Checks if the function does not require authentication.
Mark such functions with the `@unauthenticated` decorator.
:returns: bool
"""
return getattr(func, 'unauthenticated', False)
def print_list(objs, fields, formatters=None, sortby_index=0,
mixed_case_fields=None):
"""Print a list or objects as a table, one row per object.
:param objs: iterable of :class:`Resource`
:param fields: attributes that correspond to columns, in order
:param formatters: `dict` of callables for field formatting
:param sortby_index: index of the field for sorting table rows
:param mixed_case_fields: fields corresponding to object attributes that
have mixed case names (e.g., 'serverId')
"""
formatters = formatters or {}
mixed_case_fields = mixed_case_fields or []
if sortby_index is None:
kwargs = {}
else:
kwargs = {'sortby': fields[sortby_index]}
pt = prettytable.PrettyTable(fields, caching=False)
pt.align = 'l'
for o in objs:
row = []
for field in fields:
if field in formatters:
row.append(formatters[field](o))
else:
if field in mixed_case_fields:
field_name = field.replace(' ', '_')
else:
field_name = field.lower().replace(' ', '_')
data = getattr(o, field_name, '')
row.append(data)
pt.add_row(row)
print(strutils.safe_encode(pt.get_string(**kwargs)))
def print_dict(dct, dict_property="Property", wrap=0):
"""Print a `dict` as a table of two columns.
:param dct: `dict` to print
:param dict_property: name of the first column
:param wrap: wrapping for the second column
"""
pt = prettytable.PrettyTable([dict_property, 'Value'], caching=False)
pt.align = 'l'
for k, v in six.iteritems(dct):
# convert dict to str to check length
if isinstance(v, dict):
v = six.text_type(v)
if wrap > 0:
v = textwrap.fill(six.text_type(v), wrap)
# if value has a newline, add in multiple rows
# e.g. fault with stacktrace
if v and isinstance(v, six.string_types) and r'\n' in v:
lines = v.strip().split(r'\n')
col1 = k
for line in lines:
pt.add_row([col1, line])
col1 = ''
else:
pt.add_row([k, v])
print(strutils.safe_encode(pt.get_string()))
def get_password(max_password_prompts=3):
"""Read password from TTY."""
verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD"))
pw = None
if hasattr(sys.stdin, "isatty") and sys.stdin.isatty():
# Check for Ctrl-D
try:
for __ in moves.range(max_password_prompts):
pw1 = getpass.getpass("OS Password: ")
if verify:
pw2 = getpass.getpass("Please verify: ")
else:
pw2 = pw1
if pw1 == pw2 and pw1:
pw = pw1
break
except EOFError:
pass
return pw
def find_resource(manager, name_or_id, **find_args):
"""Look for resource in a given manager.
Used as a helper for the _find_* methods.
Example:
def _find_hypervisor(cs, hypervisor):
#Get a hypervisor by name or ID.
return cliutils.find_resource(cs.hypervisors, hypervisor)
"""
# first try to get entity as integer id
try:
return manager.get(int(name_or_id))
except (TypeError, ValueError, exceptions.NotFound):
pass
# now try to get entity as uuid
try:
if six.PY2:
tmp_id = strutils.safe_encode(name_or_id)
else:
tmp_id = strutils.safe_decode(name_or_id)
if uuidutils.is_uuid_like(tmp_id):
return manager.get(tmp_id)
except (TypeError, ValueError, exceptions.NotFound):
pass
# for str id which is not uuid
if getattr(manager, 'is_alphanum_id_allowed', False):
try:
return manager.get(name_or_id)
except exceptions.NotFound:
pass
try:
try:
return manager.find(human_id=name_or_id, **find_args)
except exceptions.NotFound:
pass
# finally try to find entity by name
try:
resource = getattr(manager, 'resource_class', None)
name_attr = resource.NAME_ATTR if resource else 'name'
kwargs = {name_attr: name_or_id}
kwargs.update(find_args)
return manager.find(**kwargs)
except exceptions.NotFound:
msg = _("No %(name)s with a name or "
"ID of '%(name_or_id)s' exists.") % \
{
"name": manager.resource_class.__name__.lower(),
"name_or_id": name_or_id
}
raise exceptions.CommandError(msg)
except exceptions.NoUniqueMatch:
msg = _("Multiple %(name)s matches found for "
"'%(name_or_id)s', use an ID to be more specific.") % \
{
"name": manager.resource_class.__name__.lower(),
"name_or_id": name_or_id
}
raise exceptions.CommandError(msg)
def service_type(stype):
"""Adds 'service_type' attribute to decorated function.
Usage:
@service_type('volume')
def mymethod(f):
...
"""
def inner(f):
f.service_type = stype
return f
return inner
def get_service_type(f):
"""Retrieves service type from function."""
return getattr(f, 'service_type', None)
def pretty_choice_list(l):
return ', '.join("'%s'" % i for i in l)
def exit(msg=''):
if msg:
print (msg, file=sys.stderr)
sys.exit(1)

View File

@ -0,0 +1,506 @@
# Copyright 2012 Red Hat, Inc.
# Copyright 2013 IBM Corp.
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
"""
gettext for openstack-common modules.
Usual usage in an openstack.common module:
from dcmanagerclient.openstack.common.gettextutils import _
"""
import copy
import functools
import gettext
import locale
from logging import handlers
import os
from babel import localedata
import six
_AVAILABLE_LANGUAGES = {}
# FIXME(dhellmann): Remove this when moving to oslo.i18n.
USE_LAZY = False
class TranslatorFactory(object):
"""Create translator functions
"""
def __init__(self, domain, lazy=False, localedir=None):
"""Establish a set of translation functions for the domain.
:param domain: Name of translation domain,
specifying a message catalog.
:type domain: str
:param lazy: Delays translation until a message is emitted.
Defaults to False.
:type lazy: Boolean
:param localedir: Directory with translation catalogs.
:type localedir: str
"""
self.domain = domain
self.lazy = lazy
if localedir is None:
localedir = os.environ.get(domain.upper() + '_LOCALEDIR')
self.localedir = localedir
def _make_translation_func(self, domain=None):
"""Return a new translation function ready for use.
Takes into account whether or not lazy translation is being
done.
The domain can be specified to override the default from the
factory, but the localedir from the factory is always used
because we assume the log-level translation catalogs are
installed in the same directory as the main application
catalog.
"""
if domain is None:
domain = self.domain
if self.lazy:
return functools.partial(Message, domain=domain)
t = gettext.translation(
domain,
localedir=self.localedir,
fallback=True,
)
if six.PY3:
return t.gettext
return t.ugettext
@property
def primary(self):
"The default translation function."
return self._make_translation_func()
def _make_log_translation_func(self, level):
return self._make_translation_func(self.domain + '-log-' + level)
@property
def log_info(self):
"Translate info-level log messages."
return self._make_log_translation_func('info')
@property
def log_warning(self):
"Translate warning-level log messages."
return self._make_log_translation_func('warning')
@property
def log_error(self):
"Translate error-level log messages."
return self._make_log_translation_func('error')
@property
def log_critical(self):
"Translate critical-level log messages."
return self._make_log_translation_func('critical')
# NOTE(dhellmann): When this module moves out of the incubator into
# oslo.i18n, these global variables can be moved to an integration
# module within each application.
# Create the global translation functions.
_translators = TranslatorFactory('dcmanagerclient')
# The primary translation function using the well-known name "_"
_ = _translators.primary
# Translators for log levels.
#
# The abbreviated names are meant to reflect the usual use of a short
# name like '_'. The "L" is for "log" and the other letter comes from
# the level.
_LI = _translators.log_info
_LW = _translators.log_warning
_LE = _translators.log_error
_LC = _translators.log_critical
# NOTE(dhellmann): End of globals that will move to the application's
# integration module.
def enable_lazy():
"""Convenience function for configuring _() to use lazy gettext
Call this at the start of execution to enable the gettextutils._
function to use lazy gettext functionality. This is useful if
your project is importing _ directly instead of using the
gettextutils.install() way of importing the _ function.
"""
# FIXME(dhellmann): This function will be removed in oslo.i18n,
# because the TranslatorFactory makes it superfluous.
global _, _LI, _LW, _LE, _LC, USE_LAZY
tf = TranslatorFactory('dcmanagerclient', lazy=True)
_ = tf.primary
_LI = tf.log_info
_LW = tf.log_warning
_LE = tf.log_error
_LC = tf.log_critical
USE_LAZY = True
def install(domain, lazy=False):
"""Install a _() function using the given translation domain.
Given a translation domain, install a _() function using gettext's
install() function.
The main difference from gettext.install() is that we allow
overriding the default localedir (e.g. /usr/share/locale) using
a translation-domain-specific environment variable (e.g.
NOVA_LOCALEDIR).
:param domain: the translation domain
:param lazy: indicates whether or not to install the lazy _() function.
The lazy _() introduces a way to do deferred translation
of messages by installing a _ that builds Message objects,
instead of strings, which can then be lazily translated into
any available locale.
"""
if lazy:
from six import moves
tf = TranslatorFactory(domain, lazy=True)
moves.builtins.__dict__['_'] = tf.primary
else:
localedir = '%s_LOCALEDIR' % domain.upper()
if six.PY3:
gettext.install(domain,
localedir=os.environ.get(localedir))
else:
gettext.install(domain,
localedir=os.environ.get(localedir),
unicode=True)
class Message(six.text_type):
"""A Message object is a unicode object that can be translated.
Translation of Message is done explicitly using the translate() method.
For all non-translation intents and purposes, a Message is simply unicode,
and can be treated as such.
"""
def __new__(cls, msgid, msgtext=None, params=None,
domain='dcmanagerclient', *args):
"""Create a new Message object.
In order for translation to work gettext requires a message ID, this
msgid will be used as the base unicode text. It is also possible
for the msgid and the base unicode text to be different by passing
the msgtext parameter.
"""
# If the base msgtext is not given, we use the default translation
# of the msgid (which is in English) just in case the system locale is
# not English, so that the base text will be in that locale by default.
if not msgtext:
msgtext = Message._translate_msgid(msgid, domain)
# We want to initialize the parent unicode with the actual object that
# would have been plain unicode if 'Message' was not enabled.
msg = super(Message, cls).__new__(cls, msgtext)
msg.msgid = msgid
msg.domain = domain
msg.params = params
return msg
def translate(self, desired_locale=None):
"""Translate this message to the desired locale.
:param desired_locale: The desired locale to translate the message to,
if no locale is provided the message will be
translated to the system's default locale.
:returns: the translated message in unicode
"""
translated_message = Message._translate_msgid(self.msgid,
self.domain,
desired_locale)
if self.params is None:
# No need for more translation
return translated_message
# This Message object may have been formatted with one or more
# Message objects as substitution arguments, given either as a single
# argument, part of a tuple, or as one or more values in a dictionary.
# When translating this Message we need to translate those Messages too
translated_params = _translate_args(self.params, desired_locale)
translated_message = translated_message % translated_params
return translated_message
@staticmethod
def _translate_msgid(msgid, domain, desired_locale=None):
if not desired_locale:
system_locale = locale.getdefaultlocale()
# If the system locale is not available to the runtime use English
if not system_locale[0]:
desired_locale = 'en_US'
else:
desired_locale = system_locale[0]
locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR')
lang = gettext.translation(domain,
localedir=locale_dir,
languages=[desired_locale],
fallback=True)
if six.PY3:
translator = lang.gettext
else:
translator = lang.ugettext
translated_message = translator(msgid)
return translated_message
def __mod__(self, other):
# When we mod a Message we want the actual operation to be performed
# by the parent class (i.e. unicode()), the only thing we do here is
# save the original msgid and the parameters in case of a translation
params = self._sanitize_mod_params(other)
unicode_mod = super(Message, self).__mod__(params)
modded = Message(self.msgid,
msgtext=unicode_mod,
params=params,
domain=self.domain)
return modded
def _sanitize_mod_params(self, other):
"""Sanitize the object being modded with this Message.
- Add support for modding 'None' so translation supports it
- Trim the modded object, which can be a large dictionary, to only
those keys that would actually be used in a translation
- Snapshot the object being modded, in case the message is
translated, it will be used as it was when the Message was created
"""
if other is None:
params = (other,)
elif isinstance(other, dict):
# Merge the dictionaries
# Copy each item in case one does not support deep copy.
params = {}
if isinstance(self.params, dict):
for key, val in self.params.items():
params[key] = self._copy_param(val)
for key, val in other.items():
params[key] = self._copy_param(val)
else:
params = self._copy_param(other)
return params
def _copy_param(self, param):
try:
return copy.deepcopy(param)
except Exception:
# Fallback to casting to unicode this will handle the
# python code-like objects that can't be deep-copied
return six.text_type(param)
def __add__(self, other):
msg = _('Message objects do not support addition.')
raise TypeError(msg)
def __radd__(self, other):
return self.__add__(other)
if six.PY2:
def __str__(self):
# NOTE(luisg): Logging in python 2.6 tries to str() log records,
# and it expects specifically a UnicodeError in order to proceed.
msg = _('Message objects do not support str() because they may '
'contain non-ascii characters. '
'Please use unicode() or translate() instead.')
raise UnicodeError(msg)
def get_available_languages(domain):
"""Lists the available languages for the given translation domain.
:param domain: the domain to get languages for
"""
if domain in _AVAILABLE_LANGUAGES:
return copy.copy(_AVAILABLE_LANGUAGES[domain])
localedir = '%s_LOCALEDIR' % domain.upper()
find = lambda x: gettext.find(domain,
localedir=os.environ.get(localedir),
languages=[x])
# NOTE(mrodden): en_US should always be available (and first in case
# order matters) since our in-line message strings are en_US
language_list = ['en_US']
# NOTE(luisg): Babel <1.0 used a function called list(), which was
# renamed to locale_identifiers() in >=1.0, the requirements master list
# requires >=0.9.6, uncapped, so defensively work with both. We can remove
# this check when the master list updates to >=1.0, and update all projects
list_identifiers = (getattr(localedata, 'list', None) or
getattr(localedata, 'locale_identifiers'))
locale_identifiers = list_identifiers()
for i in locale_identifiers:
if find(i) is not None:
language_list.append(i)
# NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported
# locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they
# are perfectly legitimate locales:
# https://github.com/mitsuhiko/babel/issues/37
# In Babel 1.3 they fixed the bug and they support these locales, but
# they are still not explicitly "listed" by locale_identifiers().
# That is why we add the locales here explicitly if necessary so that
# they are listed as supported.
aliases = {'zh': 'zh_CN',
'zh_Hant_HK': 'zh_HK',
'zh_Hant': 'zh_TW',
'fil': 'tl_PH'}
for (loc, alias) in six.iteritems(aliases):
if loc in language_list and alias not in language_list:
language_list.append(alias)
_AVAILABLE_LANGUAGES[domain] = language_list
return copy.copy(language_list)
def translate(obj, desired_locale=None):
"""Gets the translated unicode representation of the given object.
If the object is not translatable it is returned as-is.
If the locale is None the object is translated to the system locale.
:param obj: the object to translate
:param desired_locale: the locale to translate the message to, if None the
default system locale will be used
:returns: the translated object in unicode, or the original object if
it could not be translated
"""
message = obj
if not isinstance(message, Message):
# If the object to translate is not already translatable,
# let's first get its unicode representation
message = six.text_type(obj)
if isinstance(message, Message):
# Even after unicoding() we still need to check if we are
# running with translatable unicode before translating
return message.translate(desired_locale)
return obj
def _translate_args(args, desired_locale=None):
"""Translates all the translatable elements of the given arguments object.
This method is used for translating the translatable values in method
arguments which include values of tuples or dictionaries.
If the object is not a tuple or a dictionary the object itself is
translated if it is translatable.
If the locale is None the object is translated to the system locale.
:param args: the args to translate
:param desired_locale: the locale to translate the args to, if None the
default system locale will be used
:returns: a new args object with the translated contents of the original
"""
if isinstance(args, tuple):
return tuple(translate(v, desired_locale) for v in args)
if isinstance(args, dict):
translated_dict = {}
for (k, v) in six.iteritems(args):
translated_v = translate(v, desired_locale)
translated_dict[k] = translated_v
return translated_dict
return translate(args, desired_locale)
class TranslationHandler(handlers.MemoryHandler):
"""Handler that translates records before logging them.
The TranslationHandler takes a locale and a target logging.Handler object
to forward LogRecord objects to after translating them. This handler
depends on Message objects being logged, instead of regular strings.
The handler can be configured declaratively in the logging.conf as follows:
[handlers]
keys = translatedlog, translator
[handler_translatedlog]
class = handlers.WatchedFileHandler
args = ('/var/log/api-localized.log',)
formatter = context
[handler_translator]
class = openstack.common.log.TranslationHandler
target = translatedlog
args = ('zh_CN',)
If the specified locale is not available in the system, the handler will
log in the default locale.
"""
def __init__(self, locale=None, target=None):
"""Initialize a TranslationHandler
:param locale: locale to use for translating messages
:param target: logging.Handler object to forward
LogRecord objects to after translation
"""
# NOTE(luisg): In order to allow this handler to be a wrapper for
# other handlers, such as a FileHandler, and still be able to
# configure it using logging.conf, this handler has to extend
# MemoryHandler because only the MemoryHandlers' logging.conf
# parsing is implemented such that it accepts a target handler.
handlers.MemoryHandler.__init__(self, capacity=0, target=target)
self.locale = locale
def setFormatter(self, fmt):
self.target.setFormatter(fmt)
def emit(self, record):
# We save the message from the original record to restore it
# after translation, so other handlers are not affected by this
original_msg = record.msg
original_args = record.args
try:
self._translate_and_log_record(record)
finally:
record.msg = original_msg
record.args = original_args
def _translate_and_log_record(self, record):
record.msg = translate(record.msg, self.locale)
# In addition to translating the message, we also need to translate
# arguments that were passed to the log method that were not part
# of the main message e.g., log.info(_('Some message %s'), this_one))
record.args = _translate_args(record.args, self.locale)
self.target.emit(record)

View File

@ -0,0 +1,80 @@
# Copyright 2011 OpenStack Foundation.
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
"""
Import related utilities and helper functions.
"""
import sys
import traceback
def import_class(import_str):
"""Returns a class from a string including module and class."""
mod_str, _sep, class_str = import_str.rpartition('.')
__import__(mod_str)
try:
return getattr(sys.modules[mod_str], class_str)
except AttributeError:
raise ImportError('Class %s cannot be found (%s)' %
(class_str,
traceback.format_exception(*sys.exc_info())))
def import_object(import_str, *args, **kwargs):
"""Import a class and return an instance of it."""
return import_class(import_str)(*args, **kwargs)
def import_object_ns(name_space, import_str, *args, **kwargs):
"""Tries to import object from default namespace.
Imports a class and return an instance of it, first by trying
to find the class in a default namespace, then failing back to
a full path if not found in the default namespace.
"""
import_value = "%s.%s" % (name_space, import_str)
try:
return import_class(import_value)(*args, **kwargs)
except ImportError:
return import_class(import_str)(*args, **kwargs)
def import_module(import_str):
"""Import a module."""
__import__(import_str)
return sys.modules[import_str]
def import_versioned_module(version, submodule=None):
module = 'dcmanagerclient.v%s' % version
if submodule:
module = '.'.join((module, submodule))
return import_module(module)
def try_import(import_str, default=None):
"""Try to import a module and if it fails return default."""
try:
return import_module(import_str)
except ImportError:
return default

View File

@ -0,0 +1,247 @@
# Copyright 2011 OpenStack Foundation.
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
"""
System-level utilities and helper functions.
"""
import math
import re
import sys
import unicodedata
import six
from dcmanagerclient.openstack.common.gettextutils import _
UNIT_PREFIX_EXPONENT = {
'k': 1,
'K': 1,
'Ki': 1,
'M': 2,
'Mi': 2,
'G': 3,
'Gi': 3,
'T': 4,
'Ti': 4,
}
UNIT_SYSTEM_INFO = {
'IEC': (1024, re.compile(r'(^[-+]?\d*\.?\d+)([KMGT]i?)?(b|bit|B)$')),
'SI': (1000, re.compile(r'(^[-+]?\d*\.?\d+)([kMGT])?(b|bit|B)$')),
}
TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes')
FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no')
SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]")
SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+")
def int_from_bool_as_string(subject):
"""Interpret a string as a boolean and return either 1 or 0.
Any string value in:
('True', 'true', 'On', 'on', '1')
is interpreted as a boolean True.
Useful for JSON-decoded stuff and config file parsing
"""
return bool_from_string(subject) and 1 or 0
def bool_from_string(subject, strict=False, default=False):
"""Interpret a string as a boolean.
A case-insensitive match is performed such that strings matching 't',
'true', 'on', 'y', 'yes', or '1' are considered True and, when
`strict=False`, anything else returns the value specified by 'default'.
Useful for JSON-decoded stuff and config file parsing.
If `strict=True`, unrecognized values, including None, will raise a
ValueError which is useful when parsing values passed in from an API call.
Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'.
"""
if not isinstance(subject, six.string_types):
subject = six.text_type(subject)
lowered = subject.strip().lower()
if lowered in TRUE_STRINGS:
return True
elif lowered in FALSE_STRINGS:
return False
elif strict:
acceptable = ', '.join(
"'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS))
msg = _("Unrecognized value '%(val)s', acceptable values are:"
" %(acceptable)s") % {'val': subject,
'acceptable': acceptable}
raise ValueError(msg)
else:
return default
def safe_decode(text, incoming=None, errors='strict'):
"""Decodes incoming text/bytes string using `incoming`
if they're not already unicode.
:param incoming: Text's current encoding
:param errors: Errors handling policy. See here for valid
values http://docs.python.org/2/library/codecs.html
:returns: text or a unicode `incoming` encoded
representation of it.
:raises TypeError: If text is not an instance of str
"""
if not isinstance(text, (six.string_types, six.binary_type)):
raise TypeError("%s can't be decoded" % type(text))
if isinstance(text, six.text_type):
return text
if not incoming:
incoming = (sys.stdin.encoding or
sys.getdefaultencoding())
try:
return text.decode(incoming, errors)
except UnicodeDecodeError:
# Note(flaper87) If we get here, it means that
# sys.stdin.encoding / sys.getdefaultencoding
# didn't return a suitable encoding to decode
# text. This happens mostly when global LANG
# var is not set correctly and there's no
# default encoding. In this case, most likely
# python will use ASCII or ANSI encoders as
# default encodings but they won't be capable
# of decoding non-ASCII characters.
#
# Also, UTF-8 is being used since it's an ASCII
# extension.
return text.decode('utf-8', errors)
def safe_encode(text, incoming=None,
encoding='utf-8', errors='strict'):
"""Encodes incoming text/bytes string using `encoding`.
If incoming is not specified, text is expected to be encoded with
current python's default encoding. (`sys.getdefaultencoding`)
:param incoming: Text's current encoding
:param encoding: Expected encoding for text (Default UTF-8)
:param errors: Errors handling policy. See here for valid
values http://docs.python.org/2/library/codecs.html
:returns: text or a bytestring `encoding` encoded
representation of it.
:raises TypeError: If text is not an instance of str
"""
if not isinstance(text, (six.string_types, six.binary_type)):
raise TypeError("%s can't be encoded" % type(text))
if not incoming:
incoming = (sys.stdin.encoding or
sys.getdefaultencoding())
if isinstance(text, six.text_type):
return text.encode(encoding, errors)
elif text and encoding != incoming:
# Decode text before encoding it with `encoding`
text = safe_decode(text, incoming, errors)
return text.encode(encoding, errors)
else:
return text
def string_to_bytes(text, unit_system='IEC', return_int=False):
"""Converts a string into an float representation of bytes.
The units supported for IEC ::
Kb(it), Kib(it), Mb(it), Mib(it), Gb(it), Gib(it), Tb(it), Tib(it)
KB, KiB, MB, MiB, GB, GiB, TB, TiB
The units supported for SI ::
kb(it), Mb(it), Gb(it), Tb(it)
kB, MB, GB, TB
Note that the SI unit system does not support capital letter 'K'
:param text: String input for bytes size conversion.
:param unit_system: Unit system for byte size conversion.
:param return_int: If True, returns integer representation of text
in bytes. (default: decimal)
:returns: Numerical representation of text in bytes.
:raises ValueError: If text has an invalid value.
"""
try:
base, reg_ex = UNIT_SYSTEM_INFO[unit_system]
except KeyError:
msg = _('Invalid unit system: "%s"') % unit_system
raise ValueError(msg)
match = reg_ex.match(text)
if match:
magnitude = float(match.group(1))
unit_prefix = match.group(2)
if match.group(3) in ['b', 'bit']:
magnitude /= 8
else:
msg = _('Invalid string format: %s') % text
raise ValueError(msg)
if not unit_prefix:
res = magnitude
else:
res = magnitude * pow(base, UNIT_PREFIX_EXPONENT[unit_prefix])
if return_int:
return int(math.ceil(res))
return res
def to_slug(value, incoming=None, errors="strict"):
"""Normalize string.
Convert to lowercase, remove non-word characters, and convert spaces
to hyphens.
Inspired by Django's `slugify` filter.
:param value: Text to slugify
:param incoming: Text's current encoding
:param errors: Errors handling policy. See here for valid
values http://docs.python.org/2/library/codecs.html
:returns: slugified unicode representation of `value`
:raises TypeError: If text is not an instance of str
"""
value = safe_decode(value, incoming, errors)
# NOTE(aababilov): no need to use safe_(encode|decode) here:
# encodings are always "ascii", error handling is always "ignore"
# and types are always known (first: unicode; second: str)
value = unicodedata.normalize("NFKD", value).encode(
"ascii", "ignore").decode("ascii")
value = SLUGIFY_STRIP_RE.sub("", value).strip().lower()
return SLUGIFY_HYPHENATE_RE.sub("-", value)

View File

@ -0,0 +1,44 @@
# Copyright (c) 2012 Intel Corporation.
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
"""
UUID related utilities and helper functions.
"""
import uuid
def generate_uuid():
return str(uuid.uuid4())
def is_uuid_like(val):
"""Returns validation of a value as a UUID.
For our purposes, a UUID is a canonical form string:
aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
"""
try:
return str(uuid.UUID(val)) == val
except (TypeError, ValueError, AttributeError):
return False

View File

@ -0,0 +1,70 @@
#
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
"""OpenStackClient plugin for DC Manager."""
import logging
from osc_lib import utils
LOG = logging.getLogger(__name__)
DEFAULT_DCMANAGER_API_VERSION = '1'
API_VERSION_OPTION = 'os_dcmanager_api_version'
API_NAME = 'dcmanager'
API_VERSIONS = {
'1': 'dcmanagerclient.api.v1.client.Client',
}
def make_client(instance):
"""Return a dcmanager client."""
version = instance._api_version[API_NAME]
dcmanager_client = utils.get_client_class(
API_NAME,
version,
API_VERSIONS)
LOG.debug('Instantiating dcmanager client: %s', dcmanager_client)
dcmanager_url = instance.get_endpoint_for_service_type(
'dcmanager',
interface='publicURL'
)
client = dcmanager_client(dcmanager_url=dcmanager_url,
session=instance.session)
return client
def build_option_parser(parser):
"""Hook to add global options."""
parser.add_argument(
'--os-dcmanager-api-version',
metavar='<dcmanager-api-version>',
default=utils.env(
'OS_DCMANAGER_API_VERSION',
default=DEFAULT_DCMANAGER_API_VERSION),
help='DCMANAGER API version, default=' +
DEFAULT_DCMANAGER_API_VERSION +
' (Env: OS_DCMANAGER_API_VERSION)')
return parser

504
dcmanagerclient/shell.py Normal file
View File

@ -0,0 +1,504 @@
# Copyright 2015 - Ericsson AB.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
"""
Command-line interface to the DC Manager APIs
"""
import logging
import sys
from dcmanagerclient import __version__ as dcmanager_version
from dcmanagerclient.api import client
from dcmanagerclient import exceptions
from dcmanagerclient.openstack.common import cliutils as c
from cliff import app
from cliff import commandmanager
from osc_lib.command import command
import argparse
from dcmanagerclient.commands.v1 import alarm_manager as am
from dcmanagerclient.commands.v1 import subcloud_manager as sm
from dcmanagerclient.commands.v1 import sw_update_manager as sum
from dcmanagerclient.commands.v1 import sw_update_options_manager as suom
LOG = logging.getLogger(__name__)
class OpenStackHelpFormatter(argparse.HelpFormatter):
def __init__(self, prog, indent_increment=2, max_help_position=32,
width=None):
super(OpenStackHelpFormatter, self).__init__(
prog,
indent_increment,
max_help_position,
width
)
def start_section(self, heading):
# Title-case the headings.
heading = '%s%s' % (heading[0].upper(), heading[1:])
super(OpenStackHelpFormatter, self).start_section(heading)
class HelpAction(argparse.Action):
"""Custom help action.
Provide a custom action so the -h and --help options
to the main app will print a list of the commands.
The commands are determined by checking the CommandManager
instance, passed in as the "default" value for the action.
"""
def __call__(self, parser, namespace, values, option_string=None):
outputs = []
max_len = 0
app = self.default
parser.print_help(app.stdout)
app.stdout.write('\nCommands for API v1 :\n')
for name, ep in sorted(app.command_manager):
factory = ep.load()
cmd = factory(self, None)
one_liner = cmd.get_description().split('\n')[0]
outputs.append((name, one_liner))
max_len = max(len(name), max_len)
for (name, one_liner) in outputs:
app.stdout.write(' %s %s\n' % (name.ljust(max_len), one_liner))
sys.exit(0)
class BashCompletionCommand(command.Command):
"""Prints all of the commands and options for bash-completion."""
def take_action(self, parsed_args):
commands = set()
options = set()
for option, _action in self.app.parser._option_string_actions.items():
options.add(option)
for command_name, _cmd in self.app.command_manager:
commands.add(command_name)
print(' '.join(commands | options))
class DCManagerShell(app.App):
def __init__(self):
super(DCManagerShell, self).__init__(
description=__doc__.strip(),
version=dcmanager_version,
command_manager=commandmanager.CommandManager('dcmanager.cli'),
)
# Set v1 commands by default
self._set_shell_commands(self._get_commands(version=1))
def configure_logging(self):
log_lvl = logging.DEBUG if self.options.debug else logging.WARNING
logging.basicConfig(
format="%(levelname)s (%(module)s) %(message)s",
level=log_lvl
)
logging.getLogger('iso8601').setLevel(logging.WARNING)
if self.options.verbose_level <= 1:
logging.getLogger('requests').setLevel(logging.WARNING)
def build_option_parser(self, description, version,
argparse_kwargs=None):
"""Return an argparse option parser for this application.
Subclasses may override this method to extend
the parser with more global options.
:param description: full description of the application
:paramtype description: str
:param version: version number for the application
:paramtype version: str
:param argparse_kwargs: extra keyword argument passed to the
ArgumentParser constructor
:paramtype extra_kwargs: dict
"""
argparse_kwargs = argparse_kwargs or {}
parser = argparse.ArgumentParser(
description=description,
add_help=False,
formatter_class=OpenStackHelpFormatter,
**argparse_kwargs
)
parser.add_argument(
'--version',
action='version',
version='%(prog)s {0}'.format(version),
help='Show program\'s version number and exit.'
)
parser.add_argument(
'-v', '--verbose',
action='count',
dest='verbose_level',
default=self.DEFAULT_VERBOSE_LEVEL,
help='Increase verbosity of output. Can be repeated.',
)
parser.add_argument(
'--log-file',
action='store',
default=None,
help='Specify a file to log output. Disabled by default.',
)
parser.add_argument(
'-q', '--quiet',
action='store_const',
dest='verbose_level',
const=0,
help='Suppress output except warnings and errors.',
)
parser.add_argument(
'-h', '--help',
action=HelpAction,
nargs=0,
default=self, # tricky
help="Show this help message and exit.",
)
parser.add_argument(
'--debug',
default=False,
action='store_true',
help='Show tracebacks on errors.',
)
parser.add_argument(
'--dcmanager-url',
action='store',
dest='dcmanager_url',
default=c.env('DCMANAGER_URL'),
help='DC Manager API host (Env: DCMANAGER_URL)'
)
parser.add_argument(
'--dcmanager-api-version',
action='store',
dest='dcmanager_version',
default=c.env('DCMANAGER_API_VERSION', default='v1.0'),
help='DC Manager API version (default = v1.0) (Env: '
'DCMANAGER_API_VERSION)'
)
parser.add_argument(
'--dcmanager-service-type',
action='store',
dest='service_type',
default=c.env('DCMANAGER_SERVICE_TYPE',
default='dcmanager'),
help='DC Manager service-type (should be the same name as in '
'keystone-endpoint) (default = dcmanager) (Env: '
'DCMANAGER_SERVICE_TYPE)'
)
parser.add_argument(
'--os-endpoint-type',
action='store',
dest='endpoint_type',
default=c.env('OS_ENDPOINT_TYPE',
default='internalURL'),
help='DC Manager endpoint-type (should be the same name as in '
'keystone-endpoint) (default = OS_ENDPOINT_TYPE)'
)
parser.add_argument(
'--os-username',
action='store',
dest='username',
default=c.env('OS_USERNAME', default='admin'),
help='Authentication username (Env: OS_USERNAME)'
)
parser.add_argument(
'--os-password',
action='store',
dest='password',
default=c.env('OS_PASSWORD'),
help='Authentication password (Env: OS_PASSWORD)'
)
parser.add_argument(
'--os-tenant-id',
action='store',
dest='tenant_id',
default=c.env('OS_TENANT_ID', 'OS_PROJECT_ID'),
help='Authentication tenant identifier (Env: OS_TENANT_ID)'
)
parser.add_argument(
'--os-project-id',
action='store',
dest='project_id',
default=c.env('OS_TENANT_ID', 'OS_PROJECT_ID'),
help='Authentication project identifier (Env: OS_TENANT_ID'
' or OS_PROJECT_ID), will use tenant_id if both tenant_id'
' and project_id are set'
)
parser.add_argument(
'--os-tenant-name',
action='store',
dest='tenant_name',
default=c.env('OS_TENANT_NAME', 'OS_PROJECT_NAME'),
help='Authentication tenant name (Env: OS_TENANT_NAME)'
)
parser.add_argument(
'--os-project-name',
action='store',
dest='project_name',
default=c.env('OS_TENANT_NAME', 'OS_PROJECT_NAME'),
help='Authentication project name (Env: OS_TENANT_NAME'
' or OS_PROJECT_NAME), will use tenant_name if both'
' tenant_name and project_name are set'
)
parser.add_argument(
'--os-auth-token',
action='store',
dest='token',
default=c.env('OS_AUTH_TOKEN'),
help='Authentication token (Env: OS_AUTH_TOKEN)'
)
parser.add_argument(
'--os-project-domain-name',
action='store',
dest='project_domain_name',
default=c.env('OS_PROJECT_DOMAIN_NAME'),
help='Authentication project domain name or ID'
' (Env: OS_PROJECT_DOMAIN_NAME)'
)
parser.add_argument(
'--os-project-domain-id',
action='store',
dest='project_domain_id',
default=c.env('OS_PROJECT_DOMAIN_ID'),
help='Authentication project domain ID'
' (Env: OS_PROJECT_DOMAIN_ID)'
)
parser.add_argument(
'--os-user-domain-name',
action='store',
dest='user_domain_name',
default=c.env('OS_USER_DOMAIN_NAME'),
help='Authentication user domain name'
' (Env: OS_USER_DOMAIN_NAME)'
)
parser.add_argument(
'--os-user-domain-id',
action='store',
dest='user_domain_id',
default=c.env('OS_USER_DOMAIN_ID'),
help='Authentication user domain name'
' (Env: OS_USER_DOMAIN_ID)'
)
parser.add_argument(
'--os-auth-url',
action='store',
dest='auth_url',
default=c.env('OS_AUTH_URL'),
help='Authentication URL (Env: OS_AUTH_URL)'
)
parser.add_argument(
'--os-cacert',
action='store',
dest='cacert',
default=c.env('OS_CACERT'),
help='Authentication CA Certificate (Env: OS_CACERT)'
)
parser.add_argument(
'--insecure',
action='store_true',
dest='insecure',
default=c.env('DCMANAGERCLIENT_INSECURE', default=False),
help='Disables SSL/TLS certificate verification '
'(Env: DCMANAGERCLIENT_INSECURE)'
)
parser.add_argument(
'--profile',
dest='profile',
metavar='HMAC_KEY',
help='HMAC key to use for encrypting context data for performance '
'profiling of operation. This key should be one of the '
'values configured for the osprofiler middleware in '
'dcmanager, it is specified in the profiler section of the '
'dcmanager configuration '
'(i.e. /etc/dcmanager/dcmanager.conf). '
'Without the key, profiling will not be triggered even if '
'osprofiler is enabled on the server side.'
)
return parser
def initialize_app(self, argv):
self._clear_shell_commands()
ver = client.determine_client_version(self.options.dcmanager_version)
self._set_shell_commands(self._get_commands(ver))
do_help = ['help', '-h', 'bash-completion', 'complete']
# bash-completion should not require authentication.
skip_auth = ''.join(argv) in do_help
if skip_auth:
self.options.auth_url = None
if self.options.auth_url and not self.options.token \
and not skip_auth:
if not self.options.tenant_name:
raise exceptions.CommandError(
("You must provide a tenant_name "
"via --os-tenantname env[OS_TENANT_NAME]")
)
if not self.options.username:
raise exceptions.CommandError(
("You must provide a username "
"via --os-username env[OS_USERNAME]")
)
if not self.options.password:
raise exceptions.CommandError(
("You must provide a password "
"via --os-password env[OS_PASSWORD]")
)
kwargs = {
'user_domain_name': self.options.user_domain_name,
'user_domain_id': self.options.user_domain_id,
'project_domain_name': self.options.project_domain_name,
'project_domain_id': self.options.project_domain_id
}
self.client = client.client(
dcmanager_url=self.options.dcmanager_url,
username=self.options.username,
api_key=self.options.password,
project_name=self.options.tenant_name or self.options.project_name,
auth_url=self.options.auth_url,
project_id=self.options.tenant_id,
endpoint_type=self.options.endpoint_type,
service_type=self.options.service_type,
auth_token=self.options.token,
cacert=self.options.cacert,
insecure=self.options.insecure,
profile=self.options.profile,
**kwargs
)
if not self.options.auth_url and not skip_auth:
raise exceptions.CommandError(
("You must provide an auth url via either "
"--os-auth-url or env[OS_AUTH_URL] or "
"specify an auth_system which defines a"
" default url with --os-auth-system or env[OS_AUTH_SYSTEM]")
)
# Adding client_manager variable to make dcmanager client work with
# unified OpenStack client.
ClientManager = type(
'ClientManager',
(object,),
dict(subcloud_manager=self.client,
alarm_manager=self.client,
sw_update_manager=self.client,
strategy_step_manager=self.client,
sw_update_options_manager=self.client)
)
self.client_manager = ClientManager()
def _set_shell_commands(self, cmds_dict):
for k, v in cmds_dict.items():
self.command_manager.add_command(k, v)
def _clear_shell_commands(self):
exclude_cmds = ['help', 'complete']
cmds = self.command_manager.commands.copy()
for k, v in cmds.items():
if k not in exclude_cmds:
self.command_manager.commands.pop(k)
def _get_commands(self, version):
if version == 1:
return self._get_commands_v1()
return {}
@staticmethod
def _get_commands_v1():
return {
'bash-completion': BashCompletionCommand,
'subcloud add': sm.AddSubcloud,
'subcloud delete': sm.DeleteSubcloud,
'subcloud list': sm.ListSubcloud,
'subcloud show': sm.ShowSubcloud,
'subcloud unmanage': sm.UnmanageSubcloud,
'subcloud manage': sm.ManageSubcloud,
'subcloud update': sm.UpdateSubcloud,
'subcloud generate-config': sm.GenerateConfigSubcloud,
'alarm summary': am.ListAlarmSummary,
'patch-strategy create': sum.CreatePatchStrategy,
'patch-strategy delete': sum.DeletePatchStrategy,
'patch-strategy apply': sum.ApplyPatchStrategy,
'patch-strategy abort': sum.AbortPatchStrategy,
'patch-strategy show': sum.ShowPatchStrategy,
'strategy-step list': sum.ListStrategyStep,
'strategy-step show': sum.ShowStrategyStep,
'patch-strategy-config update': suom.UpdateSwUpdateOptions,
'patch-strategy-config list': suom.ListSwUpdateOptions,
'patch-strategy-config show': suom.ShowSwUpdateOptions,
'patch-strategy-config delete': suom.DeleteSwUpdateOptions,
}
def main(argv=sys.argv[1:]):
return DCManagerShell().run(argv)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

View File

View File

@ -0,0 +1,88 @@
# Copyright 2013 - Mirantis, Inc.
# Copyright 2016 - Ericsson AB.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import json
import mock
import unittest2
class FakeResponse(object):
"""Fake response for testing DC Manager Client."""
def __init__(self, status_code, content=None):
self.status_code = status_code
self.content = content
self.headers = {}
def json(self):
return json.loads(self.content)
class BaseClientTest(unittest2.TestCase):
_client = None
def mock_http_get(self, content, status_code=200):
if isinstance(content, dict):
content = json.dumps(content)
self._client.http_client.get = mock.MagicMock(
return_value=FakeResponse(status_code, content))
return self._client.http_client.get
def mock_http_post(self, content, status_code=201):
if isinstance(content, dict):
content = json.dumps(content)
self._client.http_client.post = mock.MagicMock(
return_value=FakeResponse(status_code, content))
return self._client.http_client.post
def mock_http_put(self, content, status_code=200):
if isinstance(content, dict):
content = json.dumps(content)
self._client.http_client.put = mock.MagicMock(
return_value=FakeResponse(status_code, content))
return self._client.http_client.put
def mock_http_delete(self, status_code=204):
self._client.http_client.delete = mock.MagicMock(
return_value=FakeResponse(status_code))
return self._client.http_client.delete
class BaseCommandTest(unittest2.TestCase):
def setUp(self):
self.app = mock.Mock()
self.client = self.app.client_manager.subcloud_manager
def call(self, command, app_args=[], prog_name=''):
cmd = command(self.app, app_args)
parsed_args = cmd.get_parser(prog_name).parse_args(app_args)
return cmd.take_action(parsed_args)

View File

@ -0,0 +1,55 @@
# Copyright 2015 Huawei Technologies Co., Ltd.
# Copyright 2016 Ericsson AB.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import os
import sys
import six
import testtools
from dcmanagerclient import shell
class BaseShellTests(testtools.TestCase):
def shell(self, argstr):
orig = (sys.stdout, sys.stderr)
clean_env = {}
_old_env, os.environ = os.environ, clean_env.copy()
try:
sys.stdout = six.moves.cStringIO()
sys.stderr = six.moves.cStringIO()
_shell = shell.DCManagerShell()
_shell.run(argstr.split())
except SystemExit:
exc_type, exc_value, exc_traceback = sys.exc_info()
self.assertEqual(0, exc_value.code)
finally:
stdout = sys.stdout.getvalue()
stderr = sys.stderr.getvalue()
sys.stdout.close()
sys.stderr.close()
sys.stdout, sys.stderr = orig
os.environ = _old_env
return stdout, stderr

View File

@ -0,0 +1,233 @@
# Copyright 2015 - Huawei Technologies Co., Ltd.
# Copyright 2016 - StackStorm, Inc.
# Copyright 2016 - Ericsson AB.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import os
import tempfile
import uuid
import mock
import testtools
import osprofiler.profiler
from dcmanagerclient.api import client
AUTH_HTTP_URL = 'http://localhost:35357/v3'
AUTH_HTTPS_URL = AUTH_HTTP_URL.replace('http', 'https')
DCMANAGER_HTTP_URL = 'http://localhost:8119/v1.0'
DCMANAGER_HTTPS_URL = DCMANAGER_HTTP_URL.replace('http', 'https')
PROFILER_HMAC_KEY = 'SECRET_HMAC_KEY'
FAKE_KWARGS = {'user_domain_name': 'fake_user_domain_name',
'user_domain_id': 'fake_user_domain_id',
'project_domain_name': 'fake_project_domain_name',
'project_domain_id': 'fake_project_domain_id'}
class BaseClientTests(testtools.TestCase):
@mock.patch('keystoneauth1.session.Session')
@mock.patch('dcmanagerclient.api.httpclient.HTTPClient')
def test_dcmanager_url_default(self, mock, mock_keystone_auth_session):
keystone_session_instance = mock_keystone_auth_session.return_value
token = keystone_session_instance.get_token.return_value = \
str(uuid.uuid4())
project_id = keystone_session_instance.get_project_id.return_value = \
str(uuid.uuid4())
user_id = keystone_session_instance.get_user_id.return_value = \
str(uuid.uuid4())
keystone_session_instance.get_endpoint.return_value = \
DCMANAGER_HTTP_URL
expected_args = (
DCMANAGER_HTTP_URL, token, project_id, user_id)
expected_kwargs = {
'cacert': None,
'insecure': False
}
client.client(username='dcmanager', project_name='dcmanager',
auth_url=AUTH_HTTP_URL, api_key='password',
**FAKE_KWARGS)
self.assertTrue(mock.called)
self.assertEqual(mock.call_args[0], expected_args)
self.assertDictEqual(mock.call_args[1], expected_kwargs)
@mock.patch('keystoneauth1.session.Session')
@mock.patch('dcmanagerclient.api.httpclient.HTTPClient')
def test_dcmanager_url_https_insecure(self, mock,
mock_keystone_auth_session):
keystone_session_instance = mock_keystone_auth_session.return_value
token = keystone_session_instance.get_token.return_value = \
str(uuid.uuid4())
project_id = keystone_session_instance.get_project_id.return_value = \
str(uuid.uuid4())
user_id = keystone_session_instance.get_user_id.return_value = \
str(uuid.uuid4())
keystone_session_instance.get_endpoint.return_value = \
DCMANAGER_HTTP_URL
expected_args = (DCMANAGER_HTTPS_URL, token, project_id, user_id)
expected_kwargs = {
'cacert': None,
'insecure': True
}
client.client(dcmanager_url=DCMANAGER_HTTPS_URL, username='dcmanager',
project_name='dcmanager', auth_url=AUTH_HTTP_URL,
api_key='password', cacert=None, insecure=True,
**FAKE_KWARGS)
self.assertTrue(mock.called)
self.assertEqual(mock.call_args[0], expected_args)
self.assertDictEqual(mock.call_args[1], expected_kwargs)
@mock.patch('keystoneauth1.session.Session')
@mock.patch('dcmanagerclient.api.httpclient.HTTPClient')
def test_dcmanager_url_https_secure(self, mock,
mock_keystone_auth_session):
fd, path = tempfile.mkstemp(suffix='.pem')
keystone_session_instance = mock_keystone_auth_session.return_value
token = keystone_session_instance.get_token.return_value = \
str(uuid.uuid4())
project_id = keystone_session_instance.get_project_id.return_value = \
str(uuid.uuid4())
user_id = keystone_session_instance.get_user_id.return_value = \
str(uuid.uuid4())
keystone_session_instance.get_endpoint.return_value = \
DCMANAGER_HTTPS_URL
expected_args = (DCMANAGER_HTTPS_URL, token, project_id, user_id)
expected_kwargs = {
'cacert': path,
'insecure': False
}
try:
client.client(
dcmanager_url=DCMANAGER_HTTPS_URL,
username='dcmanager',
project_name='dcmanager',
auth_url=AUTH_HTTP_URL,
api_key='password',
cacert=path,
insecure=False, **FAKE_KWARGS)
finally:
os.close(fd)
os.unlink(path)
self.assertTrue(mock.called)
self.assertEqual(mock.call_args[0], expected_args)
self.assertDictEqual(mock.call_args[1], expected_kwargs)
@mock.patch('keystoneauth1.session.Session')
def test_dcmanager_url_https_bad_cacert(self, mock_keystone_auth_session):
self.assertRaises(
ValueError,
client.client,
dcmanager_url=DCMANAGER_HTTPS_URL,
username='dcmanager',
project_name='dcmanager',
api_key='password',
auth_url=AUTH_HTTP_URL,
cacert='/path/to/foobar',
insecure=False, **FAKE_KWARGS)
@mock.patch('logging.Logger.warning')
@mock.patch('keystoneauth1.session.Session')
def test_dcmanager_url_https_bad_insecure(self, mock_keystone_auth_session,
log_warning_mock):
fd, path = tempfile.mkstemp(suffix='.pem')
try:
client.client(
dcmanager_url=DCMANAGER_HTTPS_URL,
username='dcmanager',
project_name='dcmanager',
api_key='password',
auth_url=AUTH_HTTP_URL,
cacert=path,
insecure=True,
**FAKE_KWARGS)
finally:
os.close(fd)
os.unlink(path)
self.assertTrue(log_warning_mock.called)
@mock.patch('keystoneauth1.session.Session')
@mock.patch('dcmanagerclient.api.httpclient.HTTPClient')
def test_dcmanager_profile_enabled(self, mock, mock_keystone_auth_session):
keystone_session_instance = mock_keystone_auth_session.return_value
token = keystone_session_instance.get_token.return_value = \
str(uuid.uuid4())
project_id = keystone_session_instance.get_project_id.return_value = \
str(uuid.uuid4())
user_id = keystone_session_instance.get_user_id.return_value = \
str(uuid.uuid4())
keystone_session_instance.get_endpoint.return_value = \
DCMANAGER_HTTP_URL
expected_args = (DCMANAGER_HTTP_URL, token, project_id, user_id)
expected_kwargs = {
'cacert': None,
'insecure': False
}
client.client(
username='dcmanager',
project_name='dcmanager',
auth_url=AUTH_HTTP_URL,
api_key='password',
profile=PROFILER_HMAC_KEY,
**FAKE_KWARGS)
self.assertTrue(mock.called)
self.assertEqual(mock.call_args[0], expected_args)
self.assertDictEqual(mock.call_args[1], expected_kwargs)
profiler = osprofiler.profiler.get()
self.assertEqual(profiler.hmac_key, PROFILER_HMAC_KEY)
def test_no_api_key(self):
self.assertRaises(RuntimeError, client.client,
dcmanager_url=DCMANAGER_HTTP_URL,
username='dcmanager', project_name='dcmanager',
auth_url=AUTH_HTTP_URL, **FAKE_KWARGS)
def test_project_name_and_project_id(self):
self.assertRaises(RuntimeError, client.client,
dcmanager_url=DCMANAGER_HTTP_URL,
username='dcmanager', project_name='dcmanager',
project_id=str(uuid.uuid4()),
auth_url=AUTH_HTTP_URL, **FAKE_KWARGS)
def test_user_name_and_user_id(self):
self.assertRaises(RuntimeError, client.client,
dcmanager_url=DCMANAGER_HTTP_URL,
username='dcmanager', project_name='dcmanager',
user_id=str(uuid.uuid4()),
auth_url=AUTH_HTTP_URL, **FAKE_KWARGS)

View File

@ -0,0 +1,45 @@
# Copyright 2016 Ericsson AB.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import re
from testtools import matchers
from dcmanagerclient.tests import base_shell_test as base
class TestCLIBashCompletionV1(base.BaseShellTests):
def test_bash_completion(self):
bash_completion, stderr = self.shell('bash-completion')
self.assertIn('bash-completion', bash_completion)
self.assertFalse(stderr)
class TestCLIHelp(base.BaseShellTests):
def test_help(self):
required = [
'.*?^usage: ',
'.*?^\s+help\s+print detailed help for another command'
]
kb_help, stderr = self.shell('help')
for r in required:
self.assertThat((kb_help + stderr),
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))

View File

@ -0,0 +1,303 @@
# Copyright 2016 - StackStorm, Inc.
# Copyright 2016 - Ericsson AB.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import copy
import uuid
import mock
import requests
import testtools
from osprofiler import _utils as osprofiler_utils
import osprofiler.profiler
from dcmanagerclient.api import httpclient
API_BASE_URL = 'http://localhost:8119/v1.0'
API_URL = '/os-quota-sets'
EXPECTED_URL = API_BASE_URL + API_URL
AUTH_TOKEN = str(uuid.uuid4())
PROJECT_ID = str(uuid.uuid4())
USER_ID = str(uuid.uuid4())
PROFILER_HMAC_KEY = 'SECRET_HMAC_KEY'
PROFILER_TRACE_ID = str(uuid.uuid4())
EXPECTED_AUTH_HEADERS = {
'x-auth-token': AUTH_TOKEN,
'X-Project-Id': PROJECT_ID,
'X-User-Id': USER_ID
}
EXPECTED_REQ_OPTIONS = {
'headers': EXPECTED_AUTH_HEADERS
}
EXPECTED_BODY = {
'k1': 'abc',
'k2': 123,
'k3': True
}
class FakeRequest(object):
def __init__(self, method):
self.method = method
class FakeResponse(object):
def __init__(self, method, url, status_code):
self.request = FakeRequest(method)
self.url = url
self.status_code = status_code
class HTTPClientTest(testtools.TestCase):
def setUp(self):
super(HTTPClientTest, self).setUp()
osprofiler.profiler.init(None)
self.client = httpclient.HTTPClient(
API_BASE_URL,
AUTH_TOKEN,
PROJECT_ID,
USER_ID
)
@mock.patch.object(
requests,
'get',
mock.MagicMock(return_value=FakeResponse('get', EXPECTED_URL, 200))
)
def test_get_request_options(self):
self.client.get(API_URL)
requests.get.assert_called_with(
EXPECTED_URL,
**EXPECTED_REQ_OPTIONS
)
@mock.patch.object(
requests,
'get',
mock.MagicMock(return_value=FakeResponse('get', EXPECTED_URL, 200))
)
def test_get_request_options_with_headers_for_get(self):
headers = {'foo': 'bar'}
self.client.get(API_URL, headers=headers)
expected_options = copy.deepcopy(EXPECTED_REQ_OPTIONS)
expected_options['headers'].update(headers)
requests.get.assert_called_with(
EXPECTED_URL,
**expected_options
)
@mock.patch.object(
osprofiler.profiler._Profiler,
'get_base_id',
mock.MagicMock(return_value=PROFILER_TRACE_ID)
)
@mock.patch.object(
osprofiler.profiler._Profiler,
'get_id',
mock.MagicMock(return_value=PROFILER_TRACE_ID)
)
@mock.patch.object(
requests,
'get',
mock.MagicMock(return_value=FakeResponse('get', EXPECTED_URL, 200))
)
def test_get_request_options_with_profile_enabled(self):
osprofiler.profiler.init(PROFILER_HMAC_KEY)
data = {'base_id': PROFILER_TRACE_ID, 'parent_id': PROFILER_TRACE_ID}
signed_data = osprofiler_utils.signed_pack(data, PROFILER_HMAC_KEY)
headers = {
'X-Trace-Info': signed_data[0],
'X-Trace-HMAC': signed_data[1]
}
self.client.get(API_URL)
expected_options = copy.deepcopy(EXPECTED_REQ_OPTIONS)
expected_options['headers'].update(headers)
requests.get.assert_called_with(
EXPECTED_URL,
**expected_options
)
@mock.patch.object(
requests,
'post',
mock.MagicMock(return_value=FakeResponse('post', EXPECTED_URL, 201))
)
def test_get_request_options_with_headers_for_post(self):
headers = {'foo': 'bar'}
self.client.post(API_URL, EXPECTED_BODY, headers=headers)
expected_options = copy.deepcopy(EXPECTED_REQ_OPTIONS)
expected_options['headers'].update(headers)
expected_options['headers']['content-type'] = 'application/json'
requests.post.assert_called_with(
EXPECTED_URL,
EXPECTED_BODY,
**expected_options
)
@mock.patch.object(
requests,
'put',
mock.MagicMock(return_value=FakeResponse('put', EXPECTED_URL, 200))
)
def test_get_request_options_with_headers_for_put(self):
headers = {'foo': 'bar'}
self.client.put(API_URL, EXPECTED_BODY, headers=headers)
expected_options = copy.deepcopy(EXPECTED_REQ_OPTIONS)
expected_options['headers'].update(headers)
expected_options['headers']['content-type'] = 'application/json'
requests.put.assert_called_with(
EXPECTED_URL,
EXPECTED_BODY,
**expected_options
)
@mock.patch.object(
requests,
'delete',
mock.MagicMock(return_value=FakeResponse('delete', EXPECTED_URL, 200))
)
def test_get_request_options_with_headers_for_delete(self):
headers = {'foo': 'bar'}
self.client.delete(API_URL, headers=headers)
expected_options = copy.deepcopy(EXPECTED_REQ_OPTIONS)
expected_options['headers'].update(headers)
requests.delete.assert_called_with(
EXPECTED_URL,
**expected_options
)
@mock.patch.object(
httpclient.HTTPClient,
'_get_request_options',
mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS))
)
@mock.patch.object(
requests,
'get',
mock.MagicMock(return_value=FakeResponse('get', EXPECTED_URL, 200))
)
def test_http_get(self):
self.client.get(API_URL)
httpclient.HTTPClient._get_request_options.assert_called_with(
'get',
None
)
requests.get.assert_called_with(
EXPECTED_URL,
**EXPECTED_REQ_OPTIONS
)
@mock.patch.object(
httpclient.HTTPClient,
'_get_request_options',
mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS))
)
@mock.patch.object(
requests,
'post',
mock.MagicMock(return_value=FakeResponse('post', EXPECTED_URL, 201))
)
def test_http_post(self):
self.client.post(API_URL, EXPECTED_BODY)
httpclient.HTTPClient._get_request_options.assert_called_with(
'post',
None
)
requests.post.assert_called_with(
EXPECTED_URL,
EXPECTED_BODY,
**EXPECTED_REQ_OPTIONS
)
@mock.patch.object(
httpclient.HTTPClient,
'_get_request_options',
mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS))
)
@mock.patch.object(
requests,
'put',
mock.MagicMock(return_value=FakeResponse('put', EXPECTED_URL, 200))
)
def test_http_put(self):
self.client.put(API_URL, EXPECTED_BODY)
httpclient.HTTPClient._get_request_options.assert_called_with(
'put',
None
)
requests.put.assert_called_with(
EXPECTED_URL,
EXPECTED_BODY,
**EXPECTED_REQ_OPTIONS
)
@mock.patch.object(
httpclient.HTTPClient,
'_get_request_options',
mock.MagicMock(return_value=copy.deepcopy(EXPECTED_REQ_OPTIONS))
)
@mock.patch.object(
requests,
'delete',
mock.MagicMock(return_value=FakeResponse('delete', EXPECTED_URL, 200))
)
def test_http_delete(self):
self.client.delete(API_URL)
httpclient.HTTPClient._get_request_options.assert_called_with(
'delete',
None
)
requests.delete.assert_called_with(
EXPECTED_URL,
**EXPECTED_REQ_OPTIONS
)

View File

@ -0,0 +1,196 @@
# Copyright 2016 EricssonAB.
#
# 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import mock
from dcmanagerclient.tests import base_shell_test as base
class TestShell(base.BaseShellTests):
@mock.patch('dcmanagerclient.api.client.determine_client_version')
def test_dcmanager_version(self, mock):
self.shell(
'--os-dcmanager-version=v1 quota-defaults'
)
self.assertTrue(mock.called)
dcmanager_version = mock.call_args
self.assertEqual('v1', dcmanager_version[0][0])
@mock.patch('dcmanagerclient.api.client.determine_client_version')
def test_default_dcmanager_version(self, mock):
default_version = 'v1.0'
self.shell('quota defaults')
self.assertTrue(mock.called)
dcmanager_version = mock.call_args
self.assertEqual(default_version, dcmanager_version[0][0])
@mock.patch('dcmanagerclient.api.client.client')
def test_env_variables(self, mock):
self.shell(
'--os-auth-url=https://127.0.0.1:35357/v3 '
'--os-username=admin '
'--os-password=1234 '
'--os-tenant-name=admin '
'quota defaults'
)
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('https://127.0.0.1:35357/v3', params[1]['auth_url'])
self.assertEqual('admin', params[1]['username'])
self.assertEqual('admin', params[1]['project_name'])
@mock.patch('dcmanagerclient.api.client.client')
def test_env_without_auth_url(self, mock):
self.shell(
'--os-username=admin '
'--os-password=1234 '
'--os-tenant-name=admin '
'quota defaults'
)
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('', params[1]['auth_url'])
self.assertEqual('admin', params[1]['username'])
self.assertEqual('admin', params[1]['project_name'])
@mock.patch('dcmanagerclient.api.client.client')
def test_kb_service_type(self, mock):
self.shell('--os-service-type=dcmanager')
self.assertTrue(mock.called)
parameters = mock.call_args
self.assertEqual('dcmanager', parameters[1]['service_type'])
@mock.patch('dcmanagerclient.api.client.client')
def test_kb_default_service_type(self, mock):
self.shell('quota defaults')
self.assertTrue(mock.called)
params = mock.call_args
# Default service type is dcmanager
self.assertEqual('dcmanager', params[1]['service_type'])
@mock.patch('dcmanagerclient.api.client.client')
def test_kb_endpoint_type(self, mock):
self.shell('--os-dcmanager-endpoint-type=adminURL quota-defaults')
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('adminURL', params[1]['endpoint_type'])
@mock.patch('dcmanagerclient.api.client.client')
def test_kb_default_endpoint_type(self, mock):
self.shell('quota defaults')
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('internalURL', params[1]['endpoint_type'])
@mock.patch('dcmanagerclient.api.client.client')
def test_os_auth_token(self, mock):
self.shell(
'--os-auth-token=abcd1234 '
'quota defaults'
)
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('abcd1234', params[1]['auth_token'])
@mock.patch('dcmanagerclient.api.client.client')
def test_command_without_dcmanager_url(self, mock):
self.shell(
'quota defaults'
)
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('', params[1]['dcmanager_url'])
@mock.patch('dcmanagerclient.api.client.client')
def test_command_with_dcmanager_url(self, mock):
self.shell(
'--os-dcmanager-url=http://localhost:8118/v1 quota-defaults'
)
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('http://localhost:8118/v1',
params[1]['dcmanager_url'])
@mock.patch('dcmanagerclient.api.client.client')
def test_command_without_project_name(self, mock):
self.shell(
'quota defaults'
)
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('', params[1]['project_name'])
@mock.patch('dcmanagerclient.api.client.client')
def test_dcmanager_profile(self, mock):
self.shell('--profile=SECRET_HMAC_KEY quota defaults')
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('SECRET_HMAC_KEY', params[1]['profile'])
@mock.patch('dcmanagerclient.api.client.client')
def test_dcmanager_without_profile(self, mock):
self.shell('quota defaults')
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual(None, params[1]['profile'])
@mock.patch('dcmanagerclient.api.client.client')
def test_dcmanager_project_name(self, mock):
self.shell('--os-project-name default quota defaults')
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('default', params[1]['project_name'])
@mock.patch('dcmanagerclient.api.client.client')
def test_dcmanager_tenant_name(self, mock):
self.shell('--os-tenant-name default quota defaults')
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('default', params[1]['project_name'])
@mock.patch('dcmanagerclient.api.client.client')
def test_dcmanager_project_domain_name(self, mock):
self.shell('--os-project-domain-name default quota defaults')
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('default', params[1]['project_domain_name'])
@mock.patch('dcmanagerclient.api.client.client')
def test_dcmanager_project_domain_id(self, mock):
self.shell('--os-project-domain-id default quota defaults')
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('default', params[1]['project_domain_id'])
@mock.patch('dcmanagerclient.api.client.client')
def test_dcmanager_user_domain_name(self, mock):
self.shell('--os-user-domain-name default quota defaults')
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('default', params[1]['user_domain_name'])
@mock.patch('dcmanagerclient.api.client.client')
def test_dcmanager_user_domain_id(self, mock):
self.shell('--os-user-domain-id default quota defaults')
self.assertTrue(mock.called)
params = mock.call_args
self.assertEqual('default', params[1]['user_domain_id'])

View File

@ -0,0 +1,64 @@
# Copyright 2015 - StackStorm, 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import json
import os.path
import tempfile
import testtools
import yaml
from dcmanagerclient import utils
ENV_DICT = {'k1': 'abc', 'k2': 123, 'k3': True}
ENV_STR = json.dumps(ENV_DICT)
ENV_YAML = yaml.safe_dump(ENV_DICT, default_flow_style=False)
class UtilityTest(testtools.TestCase):
def test_load_empty(self):
self.assertDictEqual(dict(), utils.load_content(None))
self.assertDictEqual(dict(), utils.load_content(''))
self.assertDictEqual(dict(), utils.load_content('{}'))
self.assertListEqual(list(), utils.load_content('[]'))
def test_load_json_content(self):
self.assertDictEqual(ENV_DICT, utils.load_content(ENV_STR))
def test_load_json_file(self):
with tempfile.NamedTemporaryFile() as f:
f.write(ENV_STR.encode('utf-8'))
f.flush()
file_path = os.path.abspath(f.name)
self.assertDictEqual(ENV_DICT, utils.load_file(file_path))
def test_load_yaml_content(self):
self.assertDictEqual(ENV_DICT, utils.load_content(ENV_YAML))
def test_load_yaml_file(self):
with tempfile.NamedTemporaryFile() as f:
f.write(ENV_YAML.encode('utf-8'))
f.flush()
file_path = os.path.abspath(f.name)
self.assertDictEqual(ENV_DICT, utils.load_file(file_path))

View File

View File

@ -0,0 +1,250 @@
# Copyright (c) 2017 Ericsson AB.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import copy
import mock
from oslo_utils import timeutils
from dcmanagerclient.api.v1 import subcloud_manager as sm
from dcmanagerclient.commands.v1 import subcloud_manager as subcloud_cmd
from dcmanagerclient.tests import base
TIME_NOW = timeutils.utcnow().isoformat()
ID = '1'
ID_1 = '2'
NAME = 'subcloud1'
DESCRIPTION = 'subcloud1 description'
LOCATION = 'subcloud1 location'
SOFTWARE_VERSION = '12.34'
MANAGEMENT_STATE = 'unmanaged'
AVAILABILITY_STATUS = 'offline'
MANAGEMENT_SUBNET = '192.168.101.0/24'
MANAGEMENT_START_IP = '192.168.101.2'
MANAGEMENT_END_IP = '192.168.101.50'
MANAGEMENT_GATEWAY_IP = '192.168.101.1'
SYSTEMCONTROLLER_GATEWAY_IP = '192.168.204.101'
SUBCLOUD_DICT = {
'SUBCLOUD_ID': ID,
'NAME': NAME,
'DESCRIPTION': DESCRIPTION,
'LOCATION': LOCATION,
'SOFTWARE_VERSION': SOFTWARE_VERSION,
'MANAGEMENT_STATE': MANAGEMENT_STATE,
'AVAILABILITY_STATUS': AVAILABILITY_STATUS,
'MANAGEMENT_SUBNET': MANAGEMENT_SUBNET,
'MANAGEMENT_START_IP': MANAGEMENT_START_IP,
'MANAGEMENT_END_IP': MANAGEMENT_END_IP,
'MANAGEMENT_GATEWAY_IP': MANAGEMENT_GATEWAY_IP,
'SYSTEMCONTROLLER_GATEWAY_IP': SYSTEMCONTROLLER_GATEWAY_IP,
'CREATED_AT': TIME_NOW,
'UPDATED_AT': TIME_NOW
}
SUBCLOUD = sm.Subcloud(
mock,
subcloud_id=SUBCLOUD_DICT['SUBCLOUD_ID'],
name=SUBCLOUD_DICT['NAME'],
description=SUBCLOUD_DICT['DESCRIPTION'],
location=SUBCLOUD_DICT['LOCATION'],
software_version=SUBCLOUD_DICT['SOFTWARE_VERSION'],
management_state=SUBCLOUD_DICT['MANAGEMENT_STATE'],
availability_status=SUBCLOUD_DICT['AVAILABILITY_STATUS'],
management_subnet=SUBCLOUD_DICT['MANAGEMENT_SUBNET'],
management_start_ip=SUBCLOUD_DICT['MANAGEMENT_START_IP'],
management_end_ip=SUBCLOUD_DICT['MANAGEMENT_END_IP'],
management_gateway_ip=SUBCLOUD_DICT['MANAGEMENT_GATEWAY_IP'],
systemcontroller_gateway_ip=SUBCLOUD_DICT['SYSTEMCONTROLLER_GATEWAY_IP'],
created_at=SUBCLOUD_DICT['CREATED_AT'],
updated_at=SUBCLOUD_DICT['UPDATED_AT'])
class TestCLISubcloudManagerV1(base.BaseCommandTest):
def test_list_subclouds(self):
self.client.subcloud_manager.list_subclouds.return_value = [SUBCLOUD]
actual_call = self.call(subcloud_cmd.ListSubcloud)
self.assertEqual([(ID, NAME, MANAGEMENT_STATE, AVAILABILITY_STATUS,
"unknown")],
actual_call[1])
def test_negative_list_subclouds(self):
self.client.subcloud_manager.list_subclouds.return_value = []
actual_call = self.call(subcloud_cmd.ListSubcloud)
self.assertEqual((('<none>', '<none>', '<none>', '<none>',
'<none>'),),
actual_call[1])
def test_delete_subcloud_with_subcloud_id(self):
self.call(subcloud_cmd.DeleteSubcloud, app_args=[ID])
self.client.subcloud_manager.delete_subcloud.\
assert_called_once_with(ID)
def test_delete_subcloud_without_subcloud_id(self):
self.assertRaises(SystemExit, self.call,
subcloud_cmd.DeleteSubcloud, app_args=[])
def test_show_subcloud_with_subcloud_id(self):
self.client.subcloud_manager.subcloud_detail.\
return_value = [SUBCLOUD]
actual_call = self.call(subcloud_cmd.ShowSubcloud, app_args=[ID])
self.assertEqual((ID, NAME,
DESCRIPTION,
LOCATION,
SOFTWARE_VERSION,
MANAGEMENT_STATE,
AVAILABILITY_STATUS,
MANAGEMENT_SUBNET,
MANAGEMENT_START_IP,
MANAGEMENT_END_IP,
MANAGEMENT_GATEWAY_IP,
SYSTEMCONTROLLER_GATEWAY_IP,
TIME_NOW, TIME_NOW),
actual_call[1])
def test_show_subcloud_negative(self):
self.client.subcloud_manager.subcloud_detail.return_value = []
actual_call = self.call(subcloud_cmd.ShowSubcloud, app_args=[ID])
self.assertEqual((('<none>', '<none>', '<none>', '<none>',
'<none>', '<none>', '<none>', '<none>',
'<none>', '<none>', '<none>', '<none>',
'<none>', '<none>'),),
actual_call[1])
def test_add_subcloud(self):
self.client.subcloud_manager.add_subcloud.\
return_value = [SUBCLOUD]
actual_call = self.call(
subcloud_cmd.AddSubcloud, app_args=[
'--name', NAME,
'--description', DESCRIPTION,
'--location', LOCATION,
'--management-subnet', MANAGEMENT_SUBNET,
'--management-start-ip', MANAGEMENT_START_IP,
'--management-end-ip', MANAGEMENT_END_IP,
'--management-gateway-ip', MANAGEMENT_GATEWAY_IP,
'--systemcontroller-gateway-ip', SYSTEMCONTROLLER_GATEWAY_IP])
self.assertEqual((ID, NAME, DESCRIPTION, LOCATION, SOFTWARE_VERSION,
MANAGEMENT_STATE, AVAILABILITY_STATUS,
MANAGEMENT_SUBNET, MANAGEMENT_START_IP,
MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP,
SYSTEMCONTROLLER_GATEWAY_IP,
TIME_NOW, TIME_NOW), actual_call[1])
def test_add_subcloud_no_optional_parameters(self):
subcloud = copy.copy(SUBCLOUD)
subcloud.description = ''
subcloud.location = ''
self.client.subcloud_manager.add_subcloud.\
return_value = [subcloud]
actual_call = self.call(
subcloud_cmd.AddSubcloud, app_args=[
'--name', NAME,
'--management-subnet', MANAGEMENT_SUBNET,
'--management-start-ip', MANAGEMENT_START_IP,
'--management-end-ip', MANAGEMENT_END_IP,
'--management-gateway-ip', MANAGEMENT_GATEWAY_IP,
'--systemcontroller-gateway-ip', SYSTEMCONTROLLER_GATEWAY_IP])
self.assertEqual((ID, NAME, '', '', SOFTWARE_VERSION,
MANAGEMENT_STATE, AVAILABILITY_STATUS,
MANAGEMENT_SUBNET, MANAGEMENT_START_IP,
MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP,
SYSTEMCONTROLLER_GATEWAY_IP,
TIME_NOW, TIME_NOW), actual_call[1])
def test_add_subcloud_without_name(self):
self.client.subcloud_manager.add_subcloud.\
return_value = [SUBCLOUD]
self.assertRaises(
SystemExit, self.call, subcloud_cmd.AddSubcloud, app_args=[
'--description', DESCRIPTION,
'--location', LOCATION,
'--management-subnet', MANAGEMENT_SUBNET,
'--management-start-ip', MANAGEMENT_START_IP,
'--management-end-ip', MANAGEMENT_END_IP,
'--management-gateway-ip', MANAGEMENT_GATEWAY_IP,
'--systemcontroller-gateway-ip', SYSTEMCONTROLLER_GATEWAY_IP])
def test_unmanage_subcloud(self):
self.client.subcloud_manager.update_subcloud.\
return_value = [SUBCLOUD]
actual_call = self.call(
subcloud_cmd.UnmanageSubcloud, app_args=[ID])
self.assertEqual((ID, NAME,
DESCRIPTION, LOCATION,
SOFTWARE_VERSION, MANAGEMENT_STATE,
AVAILABILITY_STATUS,
MANAGEMENT_SUBNET, MANAGEMENT_START_IP,
MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP,
SYSTEMCONTROLLER_GATEWAY_IP,
TIME_NOW, TIME_NOW), actual_call[1])
def test_unmanage_subcloud_without_subcloud_id(self):
self.assertRaises(SystemExit, self.call,
subcloud_cmd.UnmanageSubcloud, app_args=[])
def test_manage_subcloud(self):
self.client.subcloud_manager.update_subcloud.\
return_value = [SUBCLOUD]
actual_call = self.call(
subcloud_cmd.ManageSubcloud, app_args=[ID])
self.assertEqual((ID, NAME,
DESCRIPTION, LOCATION,
SOFTWARE_VERSION, MANAGEMENT_STATE,
AVAILABILITY_STATUS,
MANAGEMENT_SUBNET, MANAGEMENT_START_IP,
MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP,
SYSTEMCONTROLLER_GATEWAY_IP,
TIME_NOW, TIME_NOW), actual_call[1])
def test_manage_subcloud_without_subcloud_id(self):
self.assertRaises(SystemExit, self.call,
subcloud_cmd.ManageSubcloud, app_args=[])
def test_update_subcloud(self):
self.client.subcloud_manager.update_subcloud.\
return_value = [SUBCLOUD]
actual_call = self.call(
subcloud_cmd.UpdateSubcloud,
app_args=[ID,
'--description', 'subcloud description',
'--location', 'subcloud location'])
self.assertEqual((ID, NAME,
DESCRIPTION, LOCATION,
SOFTWARE_VERSION, MANAGEMENT_STATE,
AVAILABILITY_STATUS,
MANAGEMENT_SUBNET, MANAGEMENT_START_IP,
MANAGEMENT_END_IP, MANAGEMENT_GATEWAY_IP,
SYSTEMCONTROLLER_GATEWAY_IP,
TIME_NOW, TIME_NOW), actual_call[1])
def test_generate_config_subcloud(self):
FAKE_CONFIG = "This is a fake config file."
self.client.subcloud_manager.generate_config_subcloud.\
return_value = FAKE_CONFIG
actual_call = self.call(
subcloud_cmd.GenerateConfigSubcloud, app_args=[ID])
self.assertEqual(FAKE_CONFIG, actual_call)
def test_generate_config_subcloud_without_subcloud_id(self):
self.assertRaises(SystemExit, self.call,
subcloud_cmd.GenerateConfigSubcloud, app_args=[])

89
dcmanagerclient/utils.py Normal file
View File

@ -0,0 +1,89 @@
# Copyright 2016 - Ericsson AB
# Copyright 2015 - Huawei Technologies Co. Ltd
# Copyright 2015 - StackStorm, 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.
#
# Copyright (c) 2017 Wind River Systems, Inc.
#
# The right to copy, distribute, modify, or otherwise make use
# of this software may be licensed only pursuant to the terms
# of an applicable Wind River license agreement.
#
import json
import os
import yaml
from six.moves.urllib import parse
from six.moves.urllib import request
from dcmanagerclient import exceptions
def do_action_on_many(action, resources, success_msg, error_msg):
"""Helper to run an action on many resources."""
failure_flag = False
for resource in resources:
try:
action(resource)
print(success_msg % resource)
except Exception as e:
failure_flag = True
print(e)
if failure_flag:
raise exceptions.DCManagerClientException(error_msg)
def load_content(content):
if content is None or content == '':
return dict()
try:
data = yaml.safe_load(content)
except Exception:
data = json.loads(content)
return data
def load_file(path):
with open(path, 'r') as f:
return load_content(f.read())
def get_contents_if_file(contents_or_file_name):
"""Get the contents of a file.
If the value passed in is a file name or file URI, return the
contents. If not, or there is an error reading the file contents,
return the value passed in as the contents.
For example, a workflow definition will be returned if either the
workflow definition file name, or file URI are passed in, or the
actual workflow definition itself is passed in.
"""
try:
if parse.urlparse(contents_or_file_name).scheme:
definition_url = contents_or_file_name
else:
path = os.path.abspath(contents_or_file_name)
definition_url = parse.urljoin(
'file:',
request.pathname2url(path)
)
return request.urlopen(definition_url).read().decode('utf8')
except Exception:
return contents_or_file_name

12
requirements.txt Normal file
View File

@ -0,0 +1,12 @@
# 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.
cliff>=2.3.0 # Apache-2.0
osc-lib>=1.2.0 # Apache-2.0
osprofiler>=1.4.0 # Apache-2.0
pbr>=2.0.0 # Apache-2.0
python-keystoneclient>=3.8.0 # Apache-2.0
PyYAML>=3.10.0 # MIT
requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0
six>=1.9.0 # MIT
beautifulsoup4

33
setup.cfg Normal file
View File

@ -0,0 +1,33 @@
[metadata]
name = distributedcloud-client
summary = Python client for Distributed Cloud
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = http://www.openstack.org/
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.3
Programming Language :: Python :: 3.4
[files]
packages =
dcmanagerclient
[entry_points]
console_scripts =
dcmanager = dcmanagerclient.shell:main
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = dcmanagerclient/locale/dcmanagerclient.pot

30
setup.py Normal file
View File

@ -0,0 +1,30 @@
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
import multiprocessing # noqa
except ImportError:
pass
# Danger - pbr requirement >= 2.0.0 not satisfied...
setuptools.setup(
setup_requires=['pbr>=1.8.0'],
pbr=True)

13
test-requirements.txt Normal file
View File

@ -0,0 +1,13 @@
# 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.
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
pylint==1.4.5 # GPLv2
python-openstackclient>=3.3.0 # Apache-2.0
sphinx>=1.5.1 # BSD
unittest2 # BSD
fixtures>=3.0.0 # Apache-2.0/BSD
mock>=2.0 # BSD
nose # LGPL
tempest>=14.0.0 # Apache-2.0
testtools>=1.4.0 # MIT

35
tox.ini Normal file
View File

@ -0,0 +1,35 @@
[tox]
minversion = 2.0
envlist = py27,pep8
# Tox does not work if the path to the workdir is too long, so move it to /tmp
toxworkdir = /tmp/{env:USER}_distributedcloud-client
skipsdist = True
[testenv]
usedevelop = True
install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
DISCOVER_DIRECTORY=dcmanagerclient/tests
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = python setup.py testr --slowest --testr-args='{posargs}'
[testenv:pep8]
commands = flake8 {posargs}
[testenv:venv]
commands = {posargs}
[testenv:cover]
commands = python setup.py test --coverage --testr-args='{posargs}'
[testenv:debug]
commands = oslo_debug_helper {posargs}
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
ignore = E123,E125
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*openstack/common*,*egg,build