Add 'explain' command to v2 that describes schemas
At its core, this is adding the ability to finx a schema through published links and convert it to a usable object. Related to bp glance-client-v2 Change-Id: I7b38ad091c6b0ad80197eb789503cf73989893e5
This commit is contained in:
parent
2822748bc1
commit
7f48506781
|
@ -22,6 +22,11 @@ class EndpointNotFound(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class SchemaNotFound(KeyError):
|
||||
"""Could not find schema"""
|
||||
pass
|
||||
|
||||
|
||||
class ClientException(Exception):
|
||||
"""
|
||||
The base exception class for all exceptions this library raises.
|
||||
|
|
|
@ -17,6 +17,7 @@ import logging
|
|||
|
||||
from glanceclient.common import http
|
||||
from glanceclient.v2 import images
|
||||
from glanceclient.v2 import schemas
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -32,7 +33,8 @@ class Client(object):
|
|||
http requests. (optional)
|
||||
"""
|
||||
|
||||
def __init__(self, endpoint, token=None, timeout=600):
|
||||
def __init__(self, endpoint, token=None, timeout=600, **kwargs):
|
||||
self.http_client = http.HTTPClient(
|
||||
endpoint, token=token, timeout=timeout)
|
||||
self.images = images.Controller(self.http_client)
|
||||
self.schemas = schemas.Controller(self.http_client)
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
# Copyright 2012 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from glanceclient.common import exceptions
|
||||
|
||||
|
||||
class SchemaProperty(object):
|
||||
def __init__(self, name, **kwargs):
|
||||
self.name = name
|
||||
self.description = kwargs.get('description')
|
||||
|
||||
|
||||
def translate_schema_properties(schema_properties):
|
||||
"""Parse the properties dictionary of a schema document
|
||||
|
||||
:returns list of SchemaProperty objects
|
||||
"""
|
||||
properties = []
|
||||
for (name, prop) in schema_properties.items():
|
||||
properties.append(SchemaProperty(name, **prop))
|
||||
return properties
|
||||
|
||||
|
||||
class Schema(object):
|
||||
def __init__(self, raw_schema):
|
||||
self._raw_schema = raw_schema
|
||||
self.name = raw_schema['name']
|
||||
raw_properties = raw_schema['properties']
|
||||
self.properties = translate_schema_properties(raw_properties)
|
||||
|
||||
|
||||
class Controller(object):
|
||||
def __init__(self, http_client):
|
||||
self.http_client = http_client
|
||||
|
||||
def get(self, schema_name):
|
||||
uri = self._find_schema_uri(schema_name)
|
||||
_, raw_schema = self.http_client.json_request('GET', uri)
|
||||
return Schema(raw_schema)
|
||||
|
||||
def _find_schema_uri(self, schema_name):
|
||||
_, schema_index = self.http_client.json_request('GET', '/v2/schemas')
|
||||
for link in schema_index['links']:
|
||||
if link['rel'] == schema_name:
|
||||
return link['href']
|
||||
raise exceptions.SchemaNotFound(schema_name)
|
|
@ -21,3 +21,11 @@ def do_image_list(gc, args):
|
|||
images = gc.images.list()
|
||||
columns = ['ID', 'Name']
|
||||
utils.print_list(images, columns)
|
||||
|
||||
|
||||
@utils.arg('name', metavar='<NAME>', help='Name of model to describe.')
|
||||
def do_explain(gc, args):
|
||||
"""Describe a specific model."""
|
||||
schema = gc.schemas.get(args.name)
|
||||
columns = ['Name', 'Description']
|
||||
utils.print_list(schema.properties, columns)
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# Copyright 2012 OpenStack LLC.
|
||||
# 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.
|
||||
|
||||
|
||||
class FakeAPI(object):
|
||||
def __init__(self, fixtures):
|
||||
self.fixtures = fixtures
|
||||
self.calls = []
|
||||
|
||||
def _request(self, method, url, headers=None, body=None):
|
||||
call = (method, url, headers or {}, body)
|
||||
self.calls.append(call)
|
||||
# drop any query params
|
||||
url = url.split('?', 1)[0]
|
||||
return self.fixtures[url][method]
|
||||
|
||||
def raw_request(self, *args, **kwargs):
|
||||
return self._request(*args, **kwargs)
|
||||
|
||||
def json_request(self, *args, **kwargs):
|
||||
return self._request(*args, **kwargs)
|
|
@ -1,16 +1,61 @@
|
|||
# Copyright 2012 OpenStack LLC.
|
||||
# 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.
|
||||
|
||||
import unittest
|
||||
|
||||
from tests.v1 import utils
|
||||
|
||||
import glanceclient.v1.images
|
||||
import glanceclient.v1.image_members
|
||||
from tests import utils
|
||||
|
||||
|
||||
fixtures = {
|
||||
'/v1/images/1/members': {
|
||||
'GET': (
|
||||
{},
|
||||
{'members': [
|
||||
{'member_id': '1', 'can_share': False},
|
||||
]},
|
||||
),
|
||||
'PUT': ({}, None),
|
||||
},
|
||||
'/v1/images/1/members/1': {
|
||||
'GET': (
|
||||
{},
|
||||
{'member': {
|
||||
'member_id': '1',
|
||||
'can_share': False,
|
||||
}},
|
||||
),
|
||||
'PUT': ({}, None),
|
||||
'DELETE': ({}, None),
|
||||
},
|
||||
'/v1/shared-images/1': {
|
||||
'GET': (
|
||||
{},
|
||||
{'shared_images': [
|
||||
{'image_id': '1', 'can_share': False},
|
||||
]},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ImageMemberManagerTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.api = utils.FakeAPI()
|
||||
self.api = utils.FakeAPI(fixtures)
|
||||
self.mgr = glanceclient.v1.image_members.ImageMemberManager(self.api)
|
||||
self.image = glanceclient.v1.images.Image(self.api, {'id': '1'}, True)
|
||||
|
||||
|
|
|
@ -1,16 +1,93 @@
|
|||
# Copyright 2012 OpenStack LLC.
|
||||
# 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.
|
||||
|
||||
import json
|
||||
import StringIO
|
||||
import unittest
|
||||
|
||||
from tests.v1 import utils
|
||||
|
||||
import glanceclient.v1.images
|
||||
from tests import utils
|
||||
|
||||
|
||||
fixtures = {
|
||||
'/v1/images': {
|
||||
'POST': (
|
||||
{
|
||||
'location': '/v1/images/1',
|
||||
},
|
||||
json.dumps(
|
||||
{'image': {
|
||||
'id': '1',
|
||||
'name': 'image-1',
|
||||
'container_format': 'ovf',
|
||||
'disk_format': 'vhd',
|
||||
'owner': 'asdf',
|
||||
'size': '1024',
|
||||
'min_ram': '512',
|
||||
'min_disk': '10',
|
||||
'properties': {'a': 'b', 'c': 'd'},
|
||||
}},
|
||||
),
|
||||
),
|
||||
},
|
||||
'/v1/images/detail': {
|
||||
'GET': (
|
||||
{},
|
||||
{'images': [
|
||||
{
|
||||
'id': '1',
|
||||
'name': 'image-1',
|
||||
'properties': {'arch': 'x86_64'},
|
||||
},
|
||||
]},
|
||||
),
|
||||
},
|
||||
'/v1/images/1': {
|
||||
'HEAD': (
|
||||
{
|
||||
'x-image-meta-id': '1',
|
||||
'x-image-meta-name': 'image-1',
|
||||
'x-image-meta-property-arch': 'x86_64',
|
||||
},
|
||||
None),
|
||||
'PUT': (
|
||||
{},
|
||||
json.dumps(
|
||||
{'image': {
|
||||
'id': '1',
|
||||
'name': 'image-2',
|
||||
'container_format': 'ovf',
|
||||
'disk_format': 'vhd',
|
||||
'owner': 'asdf',
|
||||
'size': '1024',
|
||||
'min_ram': '512',
|
||||
'min_disk': '10',
|
||||
'properties': {'a': 'b', 'c': 'd'},
|
||||
}},
|
||||
),
|
||||
),
|
||||
'DELETE': ({}, None),
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
||||
class ImageManagerTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.api = utils.FakeAPI()
|
||||
self.api = utils.FakeAPI(fixtures)
|
||||
self.mgr = glanceclient.v1.images.ImageManager(self.api)
|
||||
|
||||
def test_list(self):
|
||||
|
@ -135,7 +212,7 @@ class ImageManagerTest(unittest.TestCase):
|
|||
|
||||
class ImageTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.api = utils.FakeAPI()
|
||||
self.api = utils.FakeAPI(fixtures)
|
||||
self.mgr = glanceclient.v1.images.ImageManager(self.api)
|
||||
|
||||
def test_delete(self):
|
||||
|
|
|
@ -1,125 +0,0 @@
|
|||
# Copyright 2012 OpenStack LLC.
|
||||
# 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.
|
||||
|
||||
import json
|
||||
|
||||
|
||||
fixtures = {
|
||||
'/v1/images': {
|
||||
'POST': (
|
||||
{
|
||||
'location': '/v1/images/1',
|
||||
},
|
||||
json.dumps(
|
||||
{'image': {
|
||||
'id': '1',
|
||||
'name': 'image-1',
|
||||
'container_format': 'ovf',
|
||||
'disk_format': 'vhd',
|
||||
'owner': 'asdf',
|
||||
'size': '1024',
|
||||
'min_ram': '512',
|
||||
'min_disk': '10',
|
||||
'properties': {'a': 'b', 'c': 'd'},
|
||||
}},
|
||||
),
|
||||
),
|
||||
},
|
||||
'/v1/images/detail': {
|
||||
'GET': (
|
||||
{},
|
||||
{'images': [
|
||||
{
|
||||
'id': '1',
|
||||
'name': 'image-1',
|
||||
'properties': {'arch': 'x86_64'},
|
||||
},
|
||||
]},
|
||||
),
|
||||
},
|
||||
'/v1/images/1': {
|
||||
'HEAD': (
|
||||
{
|
||||
'x-image-meta-id': '1',
|
||||
'x-image-meta-name': 'image-1',
|
||||
'x-image-meta-property-arch': 'x86_64',
|
||||
},
|
||||
None),
|
||||
'PUT': (
|
||||
{},
|
||||
json.dumps(
|
||||
{'image': {
|
||||
'id': '1',
|
||||
'name': 'image-2',
|
||||
'container_format': 'ovf',
|
||||
'disk_format': 'vhd',
|
||||
'owner': 'asdf',
|
||||
'size': '1024',
|
||||
'min_ram': '512',
|
||||
'min_disk': '10',
|
||||
'properties': {'a': 'b', 'c': 'd'},
|
||||
}},
|
||||
),
|
||||
),
|
||||
'DELETE': ({}, None),
|
||||
},
|
||||
'/v1/images/1/members': {
|
||||
'GET': (
|
||||
{},
|
||||
{'members': [
|
||||
{'member_id': '1', 'can_share': False},
|
||||
]},
|
||||
),
|
||||
'PUT': ({}, None),
|
||||
},
|
||||
'/v1/images/1/members/1': {
|
||||
'GET': (
|
||||
{},
|
||||
{'member': {
|
||||
'member_id': '1',
|
||||
'can_share': False,
|
||||
}},
|
||||
),
|
||||
'PUT': ({}, None),
|
||||
'DELETE': ({}, None),
|
||||
},
|
||||
'/v1/shared-images/1': {
|
||||
'GET': (
|
||||
{},
|
||||
{'shared_images': [
|
||||
{'image_id': '1', 'can_share': False},
|
||||
]},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class FakeAPI(object):
|
||||
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
|
||||
def _request(self, method, url, headers=None, body=None):
|
||||
call = (method, url, headers or {}, body)
|
||||
self.calls.append(call)
|
||||
# drop any query params
|
||||
url = url.split('?', 1)[0]
|
||||
return fixtures[url][method]
|
||||
|
||||
def raw_request(self, *args, **kwargs):
|
||||
return self._request(*args, **kwargs)
|
||||
|
||||
def json_request(self, *args, **kwargs):
|
||||
return self._request(*args, **kwargs)
|
|
@ -0,0 +1,80 @@
|
|||
# Copyright 2012 OpenStack LLC.
|
||||
# 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.
|
||||
|
||||
import unittest
|
||||
|
||||
from glanceclient.v2 import schemas
|
||||
from tests import utils
|
||||
|
||||
|
||||
fixtures = {
|
||||
'/v2/schemas': {
|
||||
'GET': (
|
||||
{},
|
||||
{'links': [
|
||||
{'rel': 'image', 'href': '/v2/schemas/image'},
|
||||
{'rel': 'access', 'href': '/v2/schemas/image/access'},
|
||||
]},
|
||||
),
|
||||
},
|
||||
'/v2/schemas/image': {
|
||||
'GET': (
|
||||
{},
|
||||
{
|
||||
'name': 'image',
|
||||
'properties': {
|
||||
'name': {'type': 'string', 'description': 'Name of image'},
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class TestSchemaProperty(unittest.TestCase):
|
||||
def test_property_minimum(self):
|
||||
prop = schemas.SchemaProperty('size')
|
||||
self.assertEqual(prop.name, 'size')
|
||||
|
||||
def test_property_description(self):
|
||||
prop = schemas.SchemaProperty('size', description='some quantity')
|
||||
self.assertEqual(prop.name, 'size')
|
||||
self.assertEqual(prop.description, 'some quantity')
|
||||
|
||||
|
||||
class TestSchema(unittest.TestCase):
|
||||
def test_schema_minimum(self):
|
||||
raw_schema = {'name': 'Country', 'properties': {}}
|
||||
schema = schemas.Schema(raw_schema)
|
||||
self.assertEqual(schema.name, 'Country')
|
||||
self.assertEqual(schema.properties, [])
|
||||
|
||||
def test_schema_with_property(self):
|
||||
raw_schema = {'name': 'Country', 'properties': {'size': {}}}
|
||||
schema = schemas.Schema(raw_schema)
|
||||
self.assertEqual(schema.name, 'Country')
|
||||
self.assertEqual([p.name for p in schema.properties], ['size'])
|
||||
|
||||
|
||||
class TestController(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(TestController, self).setUp()
|
||||
self.api = utils.FakeAPI(fixtures)
|
||||
self.controller = schemas.Controller(self.api)
|
||||
|
||||
def test_get_schema(self):
|
||||
schema = self.controller.get('image')
|
||||
self.assertEqual(schema.name, 'image')
|
||||
self.assertEqual([p.name for p in schema.properties], ['name'])
|
Loading…
Reference in New Issue