Add initial solum client code
This includes: 1. A client based on oslo apiclient. 2. Platform manager for platform resource. blueprint solum-cli Change-Id: I4e40b7b86a498b24ec9ea1eadaf770eda611aaca
This commit is contained in:
@@ -2,3 +2,6 @@ pbr>=0.5.21,<1.0
|
|||||||
Babel>=1.3
|
Babel>=1.3
|
||||||
oslo.config>=1.2.0
|
oslo.config>=1.2.0
|
||||||
iso8601>=0.1.8
|
iso8601>=0.1.8
|
||||||
|
requests>=1.1
|
||||||
|
python-keystoneclient>=0.4.2
|
||||||
|
stevedore>=0.12
|
||||||
|
34
solumclient/client.py
Normal file
34
solumclient/client.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Copyright 2013 - Noorul Islam K M
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from solumclient.common import auth
|
||||||
|
from solumclient.openstack.common.apiclient import client
|
||||||
|
|
||||||
|
API_NAME = 'solum'
|
||||||
|
VERSION_MAP = {
|
||||||
|
'1': 'solumclient.v1.client.Client',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def Client(version, **kwargs):
|
||||||
|
client_class = client.BaseClient.get_class(API_NAME, version, VERSION_MAP)
|
||||||
|
keystone_auth = auth.KeystoneAuthPlugin(
|
||||||
|
username=kwargs.get('username'),
|
||||||
|
password=kwargs.get('password'),
|
||||||
|
tenant_name=kwargs.get('tenant_name'),
|
||||||
|
token=kwargs.get('token'),
|
||||||
|
auth_url=kwargs.get('auth_url'),
|
||||||
|
solum_url=kwargs.get('solum_url'))
|
||||||
|
http_client = client.HTTPClient(keystone_auth)
|
||||||
|
return client_class(http_client)
|
0
solumclient/common/__init__.py
Normal file
0
solumclient/common/__init__.py
Normal file
74
solumclient/common/auth.py
Normal file
74
solumclient/common/auth.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Copyright 2013 - Noorul Islam K M
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from keystoneclient.v2_0 import client as ksclient
|
||||||
|
|
||||||
|
from solumclient.openstack.common.apiclient import auth
|
||||||
|
from solumclient.openstack.common.apiclient import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
class KeystoneAuthPlugin(auth.BaseAuthPlugin):
|
||||||
|
|
||||||
|
opt_names = [
|
||||||
|
"username",
|
||||||
|
"password",
|
||||||
|
"tenant_name",
|
||||||
|
"token",
|
||||||
|
"auth_url",
|
||||||
|
"solum_url"
|
||||||
|
]
|
||||||
|
|
||||||
|
def _do_authenticate(self, http_client):
|
||||||
|
if self.opts.get('token') is None:
|
||||||
|
ks_kwargs = {
|
||||||
|
'username': self.opts.get('username'),
|
||||||
|
'password': self.opts.get('password'),
|
||||||
|
'tenant_name': self.opts.get('tenant_name'),
|
||||||
|
'auth_url': self.opts.get('auth_url'),
|
||||||
|
}
|
||||||
|
|
||||||
|
self._ksclient = ksclient.Client(**ks_kwargs)
|
||||||
|
|
||||||
|
def token_and_endpoint(self, endpoint_type, service_type):
|
||||||
|
token = endpoint = None
|
||||||
|
|
||||||
|
if self.opts.get('token') and self.opts.get('solum_url'):
|
||||||
|
token = self.opts.get('token')
|
||||||
|
endpoint = self.opts.get('solum_url')
|
||||||
|
|
||||||
|
elif hasattr(self, '_ksclient'):
|
||||||
|
token = self._ksclient.auth_token
|
||||||
|
endpoint = (self.opts.get('solum_url') or
|
||||||
|
self._ksclient.service_catalog.url_for(
|
||||||
|
service_type=service_type,
|
||||||
|
endpoint_type=endpoint_type))
|
||||||
|
|
||||||
|
return (token, endpoint)
|
||||||
|
|
||||||
|
def sufficient_options(self):
|
||||||
|
"""Check if all required options are present.
|
||||||
|
|
||||||
|
:raises: AuthPluginOptionsMissing
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.opts.get('token'):
|
||||||
|
lookup_table = ["token", "solum_url"]
|
||||||
|
else:
|
||||||
|
lookup_table = ["username", "password", "tenant_name", "auth_url"]
|
||||||
|
|
||||||
|
missing = [opt
|
||||||
|
for opt in lookup_table
|
||||||
|
if not self.opts.get(opt)]
|
||||||
|
if missing:
|
||||||
|
raise exceptions.AuthPluginOptionsMissing(missing)
|
33
solumclient/common/base.py
Normal file
33
solumclient/common/base.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Copyright 2013 - Noorul Islam K M
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from solumclient.openstack.common.apiclient import base
|
||||||
|
|
||||||
|
|
||||||
|
class BaseManager(base.BaseManager):
|
||||||
|
def _get(self, url, response_key=None):
|
||||||
|
"""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()
|
||||||
|
|
||||||
|
if response_key is None:
|
||||||
|
data = body
|
||||||
|
else:
|
||||||
|
data = body[response_key]
|
||||||
|
|
||||||
|
return self.resource_class(self, data, loaded=True)
|
0
solumclient/tests/common/__init__.py
Normal file
0
solumclient/tests/common/__init__.py
Normal file
69
solumclient/tests/common/test_auth.py
Normal file
69
solumclient/tests/common/test_auth.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Copyright 2013 - Noorul Islam K M
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from keystoneclient.v2_0 import client as ksclient
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from solumclient.common import auth
|
||||||
|
from solumclient.openstack.common.apiclient import client
|
||||||
|
from solumclient.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(ksclient, 'Client')
|
||||||
|
class KeystoneAuthPluginTest(base.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(KeystoneAuthPluginTest, self).setUp()
|
||||||
|
plugin = auth.KeystoneAuthPlugin(
|
||||||
|
username="fake-username",
|
||||||
|
password="fake-password",
|
||||||
|
tenant_name="fake-tenant-name",
|
||||||
|
auth_url="http://auth",
|
||||||
|
solum_url="http://solum")
|
||||||
|
self.cs = client.HTTPClient(auth_plugin=plugin)
|
||||||
|
|
||||||
|
def test_authenticate(self, mock_ksclient):
|
||||||
|
self.cs.authenticate()
|
||||||
|
mock_ksclient.assert_called_with(
|
||||||
|
username="fake-username",
|
||||||
|
password="fake-password",
|
||||||
|
tenant_name="fake-tenant-name",
|
||||||
|
auth_url="http://auth")
|
||||||
|
|
||||||
|
def test_token_and_endpoint(self, mock_ksclient):
|
||||||
|
self.cs.authenticate()
|
||||||
|
(token, endpoint) = self.cs.auth_plugin.token_and_endpoint(
|
||||||
|
"fake-endpoint-type", "fake-service-type")
|
||||||
|
self.assertIsInstance(token, mock.MagicMock)
|
||||||
|
self.assertEqual(endpoint, "http://solum")
|
||||||
|
|
||||||
|
def test_token_and_endpoint_before_auth(self, mock_ksclient):
|
||||||
|
(token, endpoint) = self.cs.auth_plugin.token_and_endpoint(
|
||||||
|
"fake-endpoint-type", "fake-service-type")
|
||||||
|
self.assertIsNone(token, None)
|
||||||
|
self.assertIsNone(endpoint, None)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(ksclient, 'Client')
|
||||||
|
class KeystoneAuthPluginTokenTest(base.TestCase):
|
||||||
|
def test_token_and_endpoint(self, mock_ksclient):
|
||||||
|
plugin = auth.KeystoneAuthPlugin(
|
||||||
|
token="fake-token",
|
||||||
|
solum_url="http://solum")
|
||||||
|
cs = client.HTTPClient(auth_plugin=plugin)
|
||||||
|
|
||||||
|
cs.authenticate()
|
||||||
|
(token, endpoint) = cs.auth_plugin.token_and_endpoint(
|
||||||
|
"fake-endpoint-type", "fake-service-type")
|
||||||
|
self.assertEqual(token, 'fake-token')
|
||||||
|
self.assertEqual(endpoint, 'http://solum')
|
76
solumclient/tests/common/test_base.py
Normal file
76
solumclient/tests/common/test_base.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Copyright 2013 - Noorul Islam K M
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from solumclient.common import base as solum_base
|
||||||
|
from solumclient.openstack.common.apiclient import base as apiclient_base
|
||||||
|
from solumclient.openstack.common.apiclient import client
|
||||||
|
from solumclient.openstack.common.apiclient import fake_client
|
||||||
|
from solumclient.tests import base as test_base
|
||||||
|
|
||||||
|
|
||||||
|
fixture1 = {
|
||||||
|
'/foo_resource': {
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'id': 1, 'name': 'foo'}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fixture2 = {
|
||||||
|
'/foo_resource': {
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'foo_resource': {'id': 1, 'name': 'foo'}}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FooResource(apiclient_base.Resource):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FooResourceManager(solum_base.BaseManager):
|
||||||
|
resource_class = FooResource
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
return self._get("/foo_resource")
|
||||||
|
|
||||||
|
def get_with_response_key(self):
|
||||||
|
return self._get("/foo_resource", "foo_resource")
|
||||||
|
|
||||||
|
|
||||||
|
class TestClient(client.BaseClient):
|
||||||
|
|
||||||
|
service_type = "test"
|
||||||
|
|
||||||
|
def __init__(self, http_client, extensions=None):
|
||||||
|
super(TestClient, self).__init__(
|
||||||
|
http_client, extensions=extensions)
|
||||||
|
|
||||||
|
self.foo_resource = FooResourceManager(self)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseManagerTest(test_base.TestCase):
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
http_client = fake_client.FakeHTTPClient(fixtures=fixture1)
|
||||||
|
tc = TestClient(http_client)
|
||||||
|
tc.foo_resource.get()
|
||||||
|
|
||||||
|
def test_get_with_response_key(self):
|
||||||
|
http_client = fake_client.FakeHTTPClient(fixtures=fixture2)
|
||||||
|
tc = TestClient(http_client)
|
||||||
|
tc.foo_resource.get_with_response_key()
|
31
solumclient/tests/test_client.py
Normal file
31
solumclient/tests/test_client.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Copyright 2013 - Noorul Islam K M
|
||||||
|
#
|
||||||
|
# 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 solumclient import client
|
||||||
|
from solumclient.common import auth
|
||||||
|
from solumclient.openstack.common.apiclient import exceptions
|
||||||
|
from solumclient.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
class ClientTest(base.TestCase):
|
||||||
|
|
||||||
|
def test_client_unsupported_version(self):
|
||||||
|
self.assertRaises(exceptions.UnsupportedVersion,
|
||||||
|
client.Client, '111.11', **{})
|
||||||
|
|
||||||
|
def test_client(self):
|
||||||
|
with mock.patch.object(auth, 'KeystoneAuthPlugin'):
|
||||||
|
client.Client('1', **{})
|
0
solumclient/tests/v1/__init__.py
Normal file
0
solumclient/tests/v1/__init__.py
Normal file
62
solumclient/tests/v1/test_platform.py
Normal file
62
solumclient/tests/v1/test_platform.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Copyright 2013 - Noorul Islam K M
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from solumclient.openstack.common.apiclient import fake_client
|
||||||
|
from solumclient.tests import base
|
||||||
|
from solumclient.v1 import client as solumclient
|
||||||
|
from solumclient.v1 import platform
|
||||||
|
|
||||||
|
|
||||||
|
fixtures = {
|
||||||
|
'/v1': {
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
'uri': 'http://example.com/v1',
|
||||||
|
'name': 'solum',
|
||||||
|
'type': 'platform',
|
||||||
|
'tags': ['solid'],
|
||||||
|
'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94',
|
||||||
|
'user_id': '55f41cf46df74320b9486a35f5d28a11',
|
||||||
|
'description': 'solum native implementation',
|
||||||
|
'implementation_version': '2014.1.1',
|
||||||
|
'assemblies_uri': 'http://example.com:9777/v1/assemblies',
|
||||||
|
'services_uri': 'http://example.com:9777/v1/services',
|
||||||
|
'components_uri': 'http://example.com:9777/v1/components',
|
||||||
|
'extenstions_uri': 'http://example.com:9777/v1/extenstions'
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PlatformManagerTest(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(PlatformManagerTest, self).setUp()
|
||||||
|
fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures)
|
||||||
|
api_client = solumclient.Client(fake_http_client)
|
||||||
|
self.mgr = platform.PlatformManager(api_client)
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
platform = self.mgr.get()
|
||||||
|
self.assertIn('Platform', repr(platform))
|
||||||
|
self.assertEqual(platform.uri,
|
||||||
|
'http://example.com/v1')
|
||||||
|
self.assertEqual(platform.type,
|
||||||
|
'platform')
|
||||||
|
self.assertEqual(platform.project_id,
|
||||||
|
'1dae5a09ef2b4d8cbf3594b0eb4f6b94')
|
||||||
|
self.assertEqual(platform.user_id,
|
||||||
|
'55f41cf46df74320b9486a35f5d28a11')
|
0
solumclient/v1/__init__.py
Normal file
0
solumclient/v1/__init__.py
Normal file
25
solumclient/v1/client.py
Normal file
25
solumclient/v1/client.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Copyright 2013 - Noorul Islam K M
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from solumclient.openstack.common.apiclient import client
|
||||||
|
from solumclient.v1 import platform
|
||||||
|
|
||||||
|
|
||||||
|
class Client(client.BaseClient):
|
||||||
|
"""Client for the Solum v1 API."""
|
||||||
|
|
||||||
|
def __init__(self, http_client, extensions=None):
|
||||||
|
"""Initialize a new client for the Solum v1 API."""
|
||||||
|
super(Client, self).__init__(http_client, extensions)
|
||||||
|
self.platform = platform.PlatformManager(self)
|
28
solumclient/v1/platform.py
Normal file
28
solumclient/v1/platform.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Copyright 2013 - Noorul Islam K M
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from solumclient.common import base as solum_base
|
||||||
|
from solumclient.openstack.common.apiclient import base as apiclient_base
|
||||||
|
|
||||||
|
|
||||||
|
class Platform(apiclient_base.Resource):
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Platform %s>" % self._info
|
||||||
|
|
||||||
|
|
||||||
|
class PlatformManager(solum_base.BaseManager):
|
||||||
|
resource_class = Platform
|
||||||
|
|
||||||
|
def get(self, **kwargs):
|
||||||
|
return self._get('/v1')
|
@@ -9,3 +9,4 @@ oslo.sphinx
|
|||||||
testrepository>=0.0.17
|
testrepository>=0.0.17
|
||||||
testscenarios>=0.4
|
testscenarios>=0.4
|
||||||
testtools>=0.9.32
|
testtools>=0.9.32
|
||||||
|
mock>=1.0
|
||||||
|
Reference in New Issue
Block a user