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:
Noorul Islam K M
2013-11-23 06:56:16 +05:30
parent a15645d6d7
commit 83d8a2e83f
15 changed files with 436 additions and 0 deletions

View File

@@ -2,3 +2,6 @@ pbr>=0.5.21,<1.0
Babel>=1.3
oslo.config>=1.2.0
iso8601>=0.1.8
requests>=1.1
python-keystoneclient>=0.4.2
stevedore>=0.12

34
solumclient/client.py Normal file
View 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)

View File

View 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)

View 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)

View File

View 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')

View 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()

View 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', **{})

View File

View 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')

View File

25
solumclient/v1/client.py Normal file
View 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)

View 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')

View File

@@ -9,3 +9,4 @@ oslo.sphinx
testrepository>=0.0.17
testscenarios>=0.4
testtools>=0.9.32
mock>=1.0