Add OpenstackClient plugin for cluster profile show
This change implements the "openstack cluster profile show" command Based on the existing senlin command: senlin profile-show Change-Id: Ib4658b6fea9c097ecd043c394522dac88b97fb94 Blueprint: senlin-support-python-openstackclient
This commit is contained in:
parent
466f4b2e73
commit
993081ab10
@ -4,12 +4,14 @@
|
||||
|
||||
Babel>=1.3 # BSD
|
||||
pbr>=1.6 # Apache-2.0
|
||||
cliff!=1.16.0,>=1.15.0 # Apache-2.0
|
||||
PrettyTable<0.8,>=0.7 # BSD
|
||||
openstacksdk # Apache-2.0
|
||||
oslo.i18n>=2.1.0 # Apache-2.0
|
||||
oslo.serialization>=1.10.0 # Apache-2.0
|
||||
oslo.utils>=3.4.0 # Apache-2.0
|
||||
python-heatclient>=0.6.0 # Apache-2.0
|
||||
python-openstackclient>=2.0.0 # Apache-2.0
|
||||
PyYAML>=3.1.0 # MIT
|
||||
requests!=2.9.0,>=2.8.1 # Apache-2.0
|
||||
six>=1.9.0 # MIT
|
||||
|
0
senlinclient/osc/__init__.py
Normal file
0
senlinclient/osc/__init__.py
Normal file
46
senlinclient/osc/plugin.py
Normal file
46
senlinclient/osc/plugin.py
Normal file
@ -0,0 +1,46 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from openstack import connection
|
||||
from openstackclient.common import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_CLUSTERING_API_VERSION = '1'
|
||||
API_VERSION_OPTION = 'os_clustering_api_version'
|
||||
API_NAME = 'clustering'
|
||||
|
||||
|
||||
def make_client(instance):
|
||||
"""Returns a clustering proxy"""
|
||||
|
||||
conn = connection.Connection(authenticator=instance.session.auth)
|
||||
LOG.debug('Connection: %s', conn)
|
||||
LOG.debug('Clustering client initialized using OpenStackSDK: %s',
|
||||
conn.cluster)
|
||||
return conn.cluster
|
||||
|
||||
|
||||
def build_option_parser(parser):
|
||||
"""Hook to add global options"""
|
||||
parser.add_argument(
|
||||
'--os-clustering-api-version',
|
||||
metavar='<clustering-api-version>',
|
||||
default=utils.env(
|
||||
'OS_CLUSTERING_API_VERSION',
|
||||
default=DEFAULT_CLUSTERING_API_VERSION),
|
||||
help='Clustering API version, default=' +
|
||||
DEFAULT_CLUSTERING_API_VERSION +
|
||||
' (Env: OS_CLUSTERING_API_VERSION)')
|
||||
return parser
|
0
senlinclient/osc/v1/__init__.py
Normal file
0
senlinclient/osc/v1/__init__.py
Normal file
72
senlinclient/osc/v1/profile.py
Normal file
72
senlinclient/osc/v1/profile.py
Normal file
@ -0,0 +1,72 @@
|
||||
# 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.
|
||||
|
||||
"""Clustering v1 profile action implementations"""
|
||||
|
||||
import logging
|
||||
|
||||
from cliff import show
|
||||
from openstack import exceptions as sdk_exc
|
||||
from openstackclient.common import exceptions as exc
|
||||
from openstackclient.common import utils
|
||||
|
||||
from senlinclient.common import utils as senlin_utils
|
||||
|
||||
|
||||
class ShowProfile(show.ShowOne):
|
||||
"""Show profile details."""
|
||||
|
||||
log = logging.getLogger(__name__ + ".ShowProfile")
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super(ShowProfile, self).get_parser(prog_name)
|
||||
parser.add_argument(
|
||||
'profile',
|
||||
metavar='<PROFILE>',
|
||||
help='Name or ID of profile to show',
|
||||
)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.log.debug("take_action(%s)", parsed_args)
|
||||
|
||||
senlin_client = self.app.client_manager.clustering
|
||||
return _show_profile(senlin_client, profile_id=parsed_args.profile)
|
||||
|
||||
|
||||
def _show_profile(senlin_client, profile_id):
|
||||
try:
|
||||
data = senlin_client.get_profile(profile_id)
|
||||
except sdk_exc.ResourceNotFound:
|
||||
raise exc.CommandError('Profile not found: %s' % profile_id)
|
||||
else:
|
||||
formatters = {}
|
||||
formatters['metadata'] = senlin_utils.json_formatter
|
||||
formatters['spec'] = senlin_utils.nested_dict_formatter(
|
||||
['type', 'version', 'properties'],
|
||||
['property', 'value'])
|
||||
|
||||
columns = [
|
||||
'created_at',
|
||||
'domain',
|
||||
'id',
|
||||
'metadata',
|
||||
'name',
|
||||
'permission',
|
||||
'project',
|
||||
'spec',
|
||||
'type',
|
||||
'updated_at',
|
||||
'user'
|
||||
]
|
||||
return columns, utils.get_dict_properties(data.to_dict(), columns,
|
||||
formatters=formatters)
|
0
senlinclient/tests/unit/osc/__init__.py
Normal file
0
senlinclient/tests/unit/osc/__init__.py
Normal file
160
senlinclient/tests/unit/osc/fakes.py
Normal file
160
senlinclient/tests/unit/osc/fakes.py
Normal file
@ -0,0 +1,160 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import requests
|
||||
import six
|
||||
import sys
|
||||
|
||||
|
||||
AUTH_TOKEN = "foobar"
|
||||
AUTH_URL = "http://0.0.0.0"
|
||||
USERNAME = "itchy"
|
||||
PASSWORD = "scratchy"
|
||||
|
||||
TEST_RESPONSE_DICT_V3 = {
|
||||
"token": {
|
||||
"audit_ids": [
|
||||
"a"
|
||||
],
|
||||
"catalog": [
|
||||
],
|
||||
"expires_at": "2034-09-29T18:27:15.978064Z",
|
||||
"extras": {},
|
||||
"issued_at": "2014-09-29T17:27:15.978097Z",
|
||||
"methods": [
|
||||
"password"
|
||||
],
|
||||
"project": {
|
||||
"domain": {
|
||||
"id": "default",
|
||||
"name": "Default"
|
||||
},
|
||||
"id": "bbb",
|
||||
"name": "project"
|
||||
},
|
||||
"roles": [
|
||||
],
|
||||
"user": {
|
||||
"domain": {
|
||||
"id": "default",
|
||||
"name": "Default"
|
||||
},
|
||||
"id": "aaa",
|
||||
"name": USERNAME
|
||||
}
|
||||
}
|
||||
}
|
||||
TEST_VERSIONS = {
|
||||
"versions": {
|
||||
"values": [
|
||||
{
|
||||
"id": "v3.0",
|
||||
"links": [
|
||||
{
|
||||
"href": AUTH_URL,
|
||||
"rel": "self"
|
||||
}
|
||||
],
|
||||
"media-types": [
|
||||
{
|
||||
"base": "application/json",
|
||||
"type": "application/vnd.openstack.identity-v3+json"
|
||||
},
|
||||
{
|
||||
"base": "application/xml",
|
||||
"type": "application/vnd.openstack.identity-v3+xml"
|
||||
}
|
||||
],
|
||||
"status": "stable",
|
||||
"updated": "2013-03-06T00:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FakeStdout(object):
|
||||
def __init__(self):
|
||||
self.content = []
|
||||
|
||||
def write(self, text):
|
||||
self.content.append(text)
|
||||
|
||||
def make_string(self):
|
||||
result = ''
|
||||
for line in self.content:
|
||||
result = result + line
|
||||
return result
|
||||
|
||||
|
||||
class FakeApp(object):
|
||||
def __init__(self, _stdout):
|
||||
self.stdout = _stdout
|
||||
self.client_manager = None
|
||||
self.stdin = sys.stdin
|
||||
self.stdout = _stdout or sys.stdout
|
||||
self.stderr = sys.stderr
|
||||
|
||||
|
||||
class FakeClient(object):
|
||||
def __init__(self, **kwargs):
|
||||
self.auth_url = kwargs['auth_url']
|
||||
self.token = kwargs['token']
|
||||
|
||||
|
||||
class FakeClientManager(object):
|
||||
def __init__(self):
|
||||
self.compute = None
|
||||
self.identity = None
|
||||
self.image = None
|
||||
self.object_store = None
|
||||
self.volume = None
|
||||
self.network = None
|
||||
self.session = None
|
||||
self.auth_ref = None
|
||||
|
||||
|
||||
class FakeModule(object):
|
||||
def __init__(self, name, version):
|
||||
self.name = name
|
||||
self.__version__ = version
|
||||
|
||||
|
||||
class FakeResource(object):
|
||||
def __init__(self, manager, info, loaded=False):
|
||||
self.manager = manager
|
||||
self._info = info
|
||||
self._add_details(info)
|
||||
self._loaded = loaded
|
||||
|
||||
def _add_details(self, info):
|
||||
for (k, v) in six.iteritems(info):
|
||||
setattr(self, k, v)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class FakeResponse(requests.Response):
|
||||
def __init__(self, headers={}, status_code=200, data=None, encoding=None):
|
||||
super(FakeResponse, self).__init__()
|
||||
|
||||
self.status_code = status_code
|
||||
|
||||
self.headers.update(headers)
|
||||
self._content = json.dumps(data)
|
||||
if not isinstance(self._content, six.binary_type):
|
||||
self._content = self._content.encode()
|
0
senlinclient/tests/unit/osc/v1/__init__.py
Normal file
0
senlinclient/tests/unit/osc/v1/__init__.py
Normal file
33
senlinclient/tests/unit/osc/v1/fakes.py
Normal file
33
senlinclient/tests/unit/osc/v1/fakes.py
Normal file
@ -0,0 +1,33 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from openstackclient.tests import utils
|
||||
from senlinclient.tests.unit.osc import fakes
|
||||
|
||||
|
||||
class FakeClusteringv1Client(object):
|
||||
def __init__(self, **kwargs):
|
||||
self.http_client = mock.Mock()
|
||||
self.http_client.auth_token = kwargs['token']
|
||||
self.profiles = fakes.FakeResource(None, {})
|
||||
|
||||
|
||||
class TestClusteringv1(utils.TestCommand):
|
||||
def setUp(self):
|
||||
super(TestClusteringv1, self).setUp()
|
||||
|
||||
self.app.client_manager.clustering = FakeClusteringv1Client(
|
||||
token=fakes.AUTH_TOKEN,
|
||||
auth_url=fakes.AUTH_URL
|
||||
)
|
109
senlinclient/tests/unit/osc/v1/test_profile.py
Normal file
109
senlinclient/tests/unit/osc/v1/test_profile.py
Normal file
@ -0,0 +1,109 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
|
||||
from openstack.cluster.v1 import profile as sdk_profile
|
||||
from openstack import exceptions as sdk_exc
|
||||
from openstackclient.common import exceptions as exc
|
||||
from openstackclient.common import utils
|
||||
|
||||
from senlinclient.osc.v1 import profile as osc_profile
|
||||
from senlinclient.tests.unit.osc.v1 import fakes
|
||||
|
||||
|
||||
class TestProfile(fakes.TestClusteringv1):
|
||||
def setUp(self):
|
||||
super(TestProfile, self).setUp()
|
||||
self.mock_client = self.app.client_manager.clustering
|
||||
|
||||
|
||||
class TestProfileShow(TestProfile):
|
||||
get_response = {"profile": {
|
||||
"created_at": "2015-03-01T14:28:25",
|
||||
"domain": 'false',
|
||||
"id": "7fa885cd-fa39-4531-a42d-780af95c84a4",
|
||||
"metadata": {},
|
||||
"name": "test_prof1",
|
||||
"project": "42d9e9663331431f97b75e25136307ff",
|
||||
"spec": {
|
||||
"disable_rollback": 'false',
|
||||
"environment": {
|
||||
"resource_registry": {
|
||||
"os.heat.server": "OS::Heat::Server"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"file:///opt/stack/senlin/examples/profiles/test_script.sh":
|
||||
"#!/bin/bash\n\necho \"this is a test script file\"\n"
|
||||
},
|
||||
"parameters": {},
|
||||
"template": {
|
||||
"heat_template_version": "2014-10-16",
|
||||
"outputs": {
|
||||
"result": {
|
||||
"value": {
|
||||
"get_attr": [
|
||||
"random",
|
||||
"value"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"file": {
|
||||
"default": {
|
||||
"get_file": "file:///opt/stack/senlin/"
|
||||
"examples/profiles/test_script.sh"
|
||||
},
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": {
|
||||
"random": {
|
||||
"properties": {
|
||||
"length": 64
|
||||
},
|
||||
"type": "OS::Heat::RandomString"
|
||||
}
|
||||
},
|
||||
"timeout": 60
|
||||
},
|
||||
"type": "os.heat.stack",
|
||||
"version": "1.0"
|
||||
},
|
||||
"type": "os.heat.stack-1.0",
|
||||
"updated_at": 'null',
|
||||
"user": "5e5bf8027826429c96af157f68dc9072"
|
||||
}}
|
||||
|
||||
def setUp(self):
|
||||
super(TestProfileShow, self).setUp()
|
||||
self.cmd = osc_profile.ShowProfile(self.app, None)
|
||||
self.mock_client.get_profile = mock.Mock(
|
||||
return_value=sdk_profile.Profile(None, self.get_response))
|
||||
utils.get_dict_properties = mock.Mock(return_value='')
|
||||
|
||||
def test_profile_show(self):
|
||||
arglist = ['my_profile']
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
self.cmd.take_action(parsed_args)
|
||||
self.mock_client.get_profile.assert_called_with('my_profile')
|
||||
|
||||
def test_profile_show_not_found(self):
|
||||
arglist = ['my_profile']
|
||||
parsed_args = self.check_parser(self.cmd, arglist, [])
|
||||
self.mock_client.get_profile.side_effect = sdk_exc.ResourceNotFound()
|
||||
self.assertRaises(
|
||||
exc.CommandError,
|
||||
self.cmd.take_action,
|
||||
parsed_args)
|
Loading…
x
Reference in New Issue
Block a user