summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Fedosin <mfedosin@mirantis.com>2015-06-16 18:36:14 +0300
committerMike Fedosin <mfedosin@mirantis.com>2015-08-26 20:02:24 +0300
commitc71a87241e15d819b08dd182b7cc295bf6b3ab62 (patch)
tree8e4ff4a08737437f3f00365be47f8046af1af412
parent5d847b92cdf62596dc0448adf85a797f82b51d0d (diff)
downloadpython-glanceclient-c71a87241e15d819b08dd182b7cc295bf6b3ab62.tar.gz
Glance v3 client basic methods
This commit adds standard lifecycle methods and related tests to the client. Currently implemented methods: 'create', 'update', 'get', 'delete' FastTrack Implements-blueprint: artifact-repository Change-Id: I03f8be8917cf1de527989c441dde24160854cf59
-rw-r--r--glanceclient/tests/unit/v3/test_artifacts.py276
-rw-r--r--glanceclient/v3/__init__.py2
-rw-r--r--glanceclient/v3/artifacts.py87
3 files changed, 360 insertions, 5 deletions
diff --git a/glanceclient/tests/unit/v3/test_artifacts.py b/glanceclient/tests/unit/v3/test_artifacts.py
index 0d09ec9..91597e0 100644
--- a/glanceclient/tests/unit/v3/test_artifacts.py
+++ b/glanceclient/tests/unit/v3/test_artifacts.py
@@ -14,11 +14,117 @@
import testtools
+from glanceclient import exc
+from glanceclient.tests.unit.v3 import get_artifact_fixture
from glanceclient.tests import utils
from glanceclient.v3 import artifacts
-data_fixtures = {}
+type_fixture = {'type_name': 'adventure_time', 'type_version': '1.0.2'}
+
+data_fixtures = {
+ '/v3/artifacts/ice_kingdom/v1.0.1/drafts': {
+ 'POST': (
+ {},
+ get_artifact_fixture(id='3a4560a1-e585-443e-9b39-553b46ec92d1',
+ name='LSP',
+ version='0.0.1')
+ ),
+ },
+ '/v3/artifacts/ice_kingdom/v1.0.1/87b634c1-f893-33c9-28a9-e5673c99239a': {
+ 'DELETE': (
+ {},
+ {
+ 'id': '87b634c1-f893-33c9-28a9-e5673c99239a',
+ },
+ ),
+ 'GET': (
+ {},
+ get_artifact_fixture(id='87b634c1-f893-33c9-28a9-e5673c99239a'),
+ ),
+ },
+ '/v3/artifacts/ice_kingdom/v1.0.1/'
+ '87b634c1-f893-33c9-28a9-e5673c99239a?show_level=none': {
+ 'GET': (
+ {},
+ get_artifact_fixture(id='87b634c1-f893-33c9-28a9-e5673c99239a',
+ version='3123.0'),
+ ),
+ },
+ '/v3/artifacts/adventure_time/v1.0.2/'
+ '3a4560a1-e585-443e-9b39-553b46ec92d1': {
+ 'GET': (
+ {},
+ get_artifact_fixture(id='3a4560a1-e585-443e-9b39-553b46ec92d1',
+ finn=None, **type_fixture),
+ ),
+ 'PATCH': (
+ {},
+ get_artifact_fixture(id='3a4560a1-e585-443e-9b39-553b46ec92d1',
+ finn='human', **type_fixture)
+ ),
+ },
+ '/v3/artifacts/adventure_time/v1.0.2/'
+ '4cd6da69-1f08-45f0-af22-a06f3106588f': {
+ 'GET': (
+ {},
+ get_artifact_fixture(id='4cd6da69-1f08-45f0-af22-a06f3106588f',
+ finn='dog', **type_fixture),
+ ),
+ 'PATCH': (
+ {},
+ get_artifact_fixture(id='4cd6da69-1f08-45f0-af22-a06f3106588f',
+ finn='human', **type_fixture)
+ ),
+ },
+ '/v3/artifacts/adventure_time/v1.0.2/'
+ '55f2bcf0-f34d-4c06-bb67-fa43b439ab20': {
+ 'GET': (
+ {},
+ get_artifact_fixture(id='55f2bcf0-f34d-4c06-bb67-fa43b439ab20',
+ finn='human', **type_fixture),
+ ),
+ 'PATCH': (
+ {},
+ get_artifact_fixture(id='55f2bcf0-f34d-4c06-bb67-fa43b439ab20',
+ **type_fixture)
+ ),
+ },
+ '/v3/artifacts/adventure_time/v1.0.2/'
+ '73a0ebdd-6b32-4536-a529-c8301f2af2c6': {
+ 'GET': (
+ {},
+ get_artifact_fixture(id='73a0ebdd-6b32-4536-a529-c8301f2af2c6',
+ **type_fixture),
+ ),
+ 'PATCH': (
+ {},
+ get_artifact_fixture(id='73a0ebdd-6b32-4536-a529-c8301f2af2c6',
+ name='Marceline', **type_fixture)
+ ),
+ },
+ '/v3/artifacts/adventure_time/v1.0.2/'
+ 'c859cae3-d924-45b7-a0da-fcc30ad9c6ab': {
+ 'GET': (
+ {},
+ get_artifact_fixture(id='c859cae3-d924-45b7-a0da-fcc30ad9c6ab',
+ **type_fixture)
+ ),
+ },
+ '/v3/artifacts/adventure_time/v1.0.2/'
+ '9dcbafea-ebea-40a8-8f4b-5c54d6d58e1f': {
+ 'GET': (
+ {},
+ get_artifact_fixture(id='9dcbafea-ebea-40a8-8f4b-5c54d6d58e1f',
+ finn='dog', **type_fixture),
+ ),
+ 'PATCH': (
+ {},
+ get_artifact_fixture(id='9dcbafea-ebea-40a8-8f4b-5c54d6d58e1f',
+ finn='human', **type_fixture)
+ ),
+ },
+}
class TestController(testtools.TestCase):
@@ -26,3 +132,171 @@ class TestController(testtools.TestCase):
super(TestController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.controller = artifacts.Controller(self.api)
+
+ def test_create_artifact(self):
+ artifact_fixture = get_artifact_fixture(name='LSP',
+ version='0.0.1')
+ artifact = self.controller.create(**artifact_fixture)
+ self.assertEqual('3a4560a1-e585-443e-9b39-553b46ec92d1',
+ artifact.id)
+ self.assertEqual('LSP', artifact.name)
+ self.assertEqual('0.0.1', artifact.version)
+
+ def test_create_artifact_no_type_version(self):
+ artifact_fixture = get_artifact_fixture(name='artifact-1',
+ version='0.0.1')
+ del artifact_fixture['type_version']
+ self.assertRaises(exc.HTTPBadRequest, self.controller.create,
+ **artifact_fixture)
+
+ def test_create_artifact_no_type_name(self):
+ artifact_fixture = get_artifact_fixture(name='artifact-1',
+ version='0.0.1')
+ del artifact_fixture['type_name']
+ self.assertRaises(exc.HTTPBadRequest, self.controller.create,
+ **artifact_fixture)
+
+ def test_delete_artifact(self):
+ artifact_id = '87b634c1-f893-33c9-28a9-e5673c99239a'
+
+ self.controller.delete(artifact_id, 'ice_kingdom', '1.0.1')
+ expect = [
+ ('DELETE',
+ '/v3/artifacts/ice_kingdom/v1.0.1/%s' % artifact_id,
+ {},
+ None)]
+ self.assertEqual(expect, self.api.calls)
+
+ def test_get_artifact(self):
+ artifact_id = '87b634c1-f893-33c9-28a9-e5673c99239a'
+
+ artifact = self.controller.get(artifact_id, 'ice_kingdom', '1.0.1')
+ self.assertEqual('Gunter The Penguin', artifact.name)
+ self.assertEqual('11.2', artifact.version)
+
+ def test_get_artifact_show_level(self):
+ artifact_id = '87b634c1-f893-33c9-28a9-e5673c99239a'
+ show_level = 'none'
+ artifact = self.controller.get(artifact_id, 'ice_kingdom', '1.0.1',
+ show_level)
+ self.assertEqual('Gunter The Penguin', artifact.name)
+ self.assertEqual('3123.0', artifact.version)
+
+ def test_get_artifact_invalid_show_level(self):
+ artifact_id = '87b634c1-f893-33c9-28a9-e5673c99239a'
+ show_level = 'invalid'
+ self.assertRaises(exc.HTTPBadRequest, self.controller.get,
+ artifact_id, 'ice_kingdom', '1.0.1', show_level)
+
+ def test_update_add_custom_prop(self):
+ artifact_id = '3a4560a1-e585-443e-9b39-553b46ec92d1'
+
+ artifact = self.controller.update(artifact_id, finn='human',
+ **type_fixture)
+ expect_hdrs = {
+ 'Content-Type': 'application/openstack-images-v2.1-json-patch',
+ }
+ expect_body = [{'op': 'add', 'path': '/finn', 'value': 'human'}]
+ expect = [
+ ('GET', '/v3/artifacts/adventure_time/v1.0.2/'
+ '%s' % artifact_id, {}, None),
+ ('PATCH', '/v3/artifacts/adventure_time/v1.0.2/'
+ '%s' % artifact_id, expect_hdrs, expect_body),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual(artifact_id, artifact.id)
+ self.assertEqual('Gunter The Penguin', artifact.name)
+ self.assertEqual('human', artifact.type_specific_properties['finn'])
+
+ def test_update_replace_custom_prop(self):
+ artifact_id = '4cd6da69-1f08-45f0-af22-a06f3106588f'
+
+ artifact = self.controller.update(artifact_id, finn='human',
+ **type_fixture)
+ expect_hdrs = {
+ 'Content-Type': 'application/openstack-images-v2.1-json-patch',
+ }
+ expect_body = [{'op': 'replace', 'path': '/finn', 'value': 'human'}]
+ expect = [
+ ('GET', '/v3/artifacts/adventure_time/v1.0.2/'
+ '%s' % artifact_id, {}, None),
+ ('PATCH', '/v3/artifacts/adventure_time/v1.0.2/'
+ '%s' % artifact_id, expect_hdrs, expect_body),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual(artifact_id, artifact.id)
+ self.assertEqual('Gunter The Penguin', artifact.name)
+ self.assertEqual('human', artifact.type_specific_properties['finn'])
+
+ def test_update_delete_custom_prop(self):
+ artifact_id = '55f2bcf0-f34d-4c06-bb67-fa43b439ab20'
+
+ artifact = self.controller.update(artifact_id, remove_props=['finn'],
+ **type_fixture)
+ expect_hdrs = {
+ 'Content-Type': 'application/openstack-images-v2.1-json-patch',
+ }
+ expect_body = [{'op': 'remove', 'path': '/finn'}]
+ expect = [
+ ('GET', '/v3/artifacts/adventure_time/v1.0.2/'
+ '%s' % artifact_id, {}, None),
+ ('PATCH', '/v3/artifacts/adventure_time/v1.0.2/'
+ '%s' % artifact_id, expect_hdrs, expect_body),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual(artifact_id, artifact.id)
+ self.assertEqual('Gunter The Penguin', artifact.name)
+ self.assertNotIn('finn', artifact.type_specific_properties)
+
+ def test_update_replace_base_prop(self):
+ artifact_id = '73a0ebdd-6b32-4536-a529-c8301f2af2c6'
+
+ artifact = self.controller.update(artifact_id, name='Marceline',
+ **type_fixture)
+ expect_hdrs = {
+ 'Content-Type': 'application/openstack-images-v2.1-json-patch',
+ }
+ expect_body = [{'op': 'replace', 'path': '/name',
+ 'value': 'Marceline'}]
+ expect = [
+ ('GET', '/v3/artifacts/adventure_time/v1.0.2/'
+ '%s' % artifact_id, {}, None),
+ ('PATCH', '/v3/artifacts/adventure_time/v1.0.2/'
+ '%s' % artifact_id, expect_hdrs, expect_body),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual(artifact_id, artifact.id)
+ self.assertEqual('Marceline', artifact.name)
+
+ def test_update_remove_base_prop(self):
+ artifact_id = '73a0ebdd-6b32-4536-a529-c8301f2af2c6'
+
+ self.assertRaises(exc.HTTPBadRequest, self.controller.update,
+ artifact_id, remove_props=['name'], **type_fixture)
+
+ def test_update_add_nonexiting_property(self):
+ artifact_id = 'c859cae3-d924-45b7-a0da-fcc30ad9c6ab'
+
+ self.assertRaises(exc.HTTPBadRequest, self.controller.update,
+ artifact_id, finn='human', **type_fixture)
+
+ def test_remove_and_replace_same_property(self):
+ artifact_id = '9dcbafea-ebea-40a8-8f4b-5c54d6d58e1f'
+
+ artifact = self.controller.update(artifact_id, finn='human',
+ remove_props=['finn'],
+ **type_fixture)
+ expect_hdrs = {
+ 'Content-Type': 'application/openstack-images-v2.1-json-patch',
+ }
+ expect_body = [{'op': 'replace', 'path': '/finn', 'value': 'human'}]
+ expect = [
+ ('GET', '/v3/artifacts/adventure_time/v1.0.2/'
+ '%s' % artifact_id, {}, None),
+ ('PATCH', '/v3/artifacts/adventure_time/v1.0.2/'
+ '%s' % artifact_id, expect_hdrs, expect_body),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual(artifact_id, artifact.id)
+ self.assertEqual('Gunter The Penguin', artifact.name)
+ self.assertEqual('human', artifact.type_specific_properties['finn'])
diff --git a/glanceclient/v3/__init__.py b/glanceclient/v3/__init__.py
index 27d99b5..937309b 100644
--- a/glanceclient/v3/__init__.py
+++ b/glanceclient/v3/__init__.py
@@ -22,6 +22,8 @@ class ArtifactType(object):
'version', 'visibility', 'description', 'tags',
'published_at', 'deleted_at')
+ supported_show_levels = ('none', 'basic', 'direct', 'transitive')
+
def __init__(self, **kwargs):
try:
for prop in self.generic_properties:
diff --git a/glanceclient/v3/artifacts.py b/glanceclient/v3/artifacts.py
index 0ddd308..2440a36 100644
--- a/glanceclient/v3/artifacts.py
+++ b/glanceclient/v3/artifacts.py
@@ -11,7 +11,9 @@
# 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 import exc
+from glanceclient.v3 import ArtifactType
class Controller(object):
@@ -37,15 +39,84 @@ class Controller(object):
def create(self, name, version, type_name=None, type_version=None,
**kwargs):
- raise NotImplementedError()
+ """Create an artifact of given type and version.
+
+ :param name: name of creating artifact.
+ :param version: semver string describing an artifact version
+ """
+ type_name, type_version = self._check_type_params(type_name,
+ type_version)
+ kwargs.update({'name': name, 'version': version})
+ url = '/v3/artifacts/%s/v%s/drafts' % (type_name, type_version)
+ resp, body = self.http_client.post(url, data=kwargs)
+ return ArtifactType(**body)
def update(self, artifact_id, type_name=None, type_version=None,
remove_props=None, **kwargs):
- raise NotImplementedError()
+ """Update attributes of an artifact.
+
+ :param artifact_id: ID of the artifact to modify.
+ :param remove_props: List of property names to remove
+ :param \*\*kwargs: Artifact attribute names and their new values.
+ """
+ type_name, type_version = self._check_type_params(type_name,
+ type_version)
+ url = '/v3/artifacts/%s/v%s/%s' % (type_name, type_version,
+ artifact_id)
+ hdrs = {
+ 'Content-Type': 'application/openstack-images-v2.1-json-patch'}
+
+ artifact_obj = self.get(artifact_id, type_name, type_version)
+
+ changes = []
+ if remove_props:
+ for prop in remove_props:
+ if prop in ArtifactType.generic_properties:
+ msg = "Generic properties cannot be removed"
+ raise exc.HTTPBadRequest(msg)
+ if prop not in kwargs:
+ changes.append({'op': 'remove',
+ 'path': '/' + prop})
+
+ for prop in kwargs:
+ if prop in artifact_obj.generic_properties:
+ op = 'add' if getattr(artifact_obj,
+ prop) is None else 'replace'
+ elif prop in artifact_obj.type_specific_properties:
+ if artifact_obj.type_specific_properties[prop] is None:
+ op = 'add'
+ else:
+ op = 'replace'
+ else:
+ msg = ("Property '%s' doesn't exist in type '%s' with version"
+ " '%s'" % (prop, type_name, type_version))
+ raise exc.HTTPBadRequest(msg)
+ changes.append({'op': op, 'path': '/' + prop,
+ 'value': kwargs[prop]})
+
+ resp, body = self.http_client.patch(url, headers=hdrs, data=changes)
+ return ArtifactType(**body)
def get(self, artifact_id, type_name=None, type_version=None,
show_level=None):
- raise NotImplementedError()
+ """Get information about an artifact.
+
+ :param artifact_id: ID of the artifact to get.
+ :param show_level: value of datalization. Possible values:
+ "none", "basic", "direct", "transitive"
+ """
+ type_name, type_version = self._check_type_params(type_name,
+ type_version)
+
+ url = '/v3/artifacts/%s/v%s/%s' % (type_name, type_version,
+ artifact_id)
+ if show_level:
+ if show_level not in ArtifactType.supported_show_levels:
+ msg = "Invalid show level: %s" % show_level
+ raise exc.HTTPBadRequest(msg)
+ url += '?show_level=%s' % show_level
+ resp, body = self.http_client.get(url)
+ return ArtifactType(**body)
def list(self, type_name=None, type_version=None, **kwargs):
raise NotImplementedError()
@@ -57,7 +128,15 @@ class Controller(object):
raise NotImplementedError()
def delete(self, artifact_id, type_name=None, type_version=None):
- raise NotImplementedError()
+ """Delete an artifact and all its data.
+
+ :param artifact_id: ID of the artifact to delete.
+ """
+ type_name, type_version = self._check_type_params(type_name,
+ type_version)
+ url = '/v3/artifacts/%s/v%s/%s' % (type_name, type_version,
+ artifact_id)
+ self.http_client.delete(url)
def upload_blob(self, artifact_id, blob_property, data, position=None,
type_name=None, type_version=None):