From 67e0fae211ec11f88704d87f77d9064f790592a8 Mon Sep 17 00:00:00 2001 From: Terry Howe Date: Thu, 3 Jul 2014 14:48:34 -0600 Subject: [PATCH] Add find method to resource Add a find method to the resource to search by name or id. Change-Id: I993e0ab2fffd3cd10407406316c483f8051dca1b --- openstack/exceptions.py | 10 ++++ openstack/resource.py | 24 ++++++++++ openstack/tests/test_resource.py | 80 ++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) diff --git a/openstack/exceptions.py b/openstack/exceptions.py index 2a4e4c227..a560040f0 100644 --- a/openstack/exceptions.py +++ b/openstack/exceptions.py @@ -66,3 +66,13 @@ class HttpException(SDKException): class MethodNotSupported(SDKException): """The resource does not support this operation type.""" pass + + +class ResourceNotFound(SDKException): + """The requested resource was not found.""" + pass + + +class DuplicateResource(SDKException): + """More than one resource exists with that name.""" + pass diff --git a/openstack/resource.py b/openstack/resource.py index edb336e9c..a255823c3 100644 --- a/openstack/resource.py +++ b/openstack/resource.py @@ -101,6 +101,9 @@ class Resource(collections.MutableMapping): resource_key = None resources_key = None + # The attribute associated with the name + name_attribute = 'name' + # the base part of the url for this resource base_path = '' @@ -350,3 +353,24 @@ class Resource(collections.MutableMapping): resp = resp[cls.resources_key] return [cls.existing(**data) for data in resp] + + @classmethod + def find(cls, session, name_or_id): + try: + info = cls.list(session, id=name_or_id, fields='id') + if len(info) == 1: + return info[0] + except exceptions.HttpException: + pass + if cls.name_attribute: + params = {cls.name_attribute: name_or_id, 'fields': 'id'} + info = cls.list(session, **params) + if len(info) == 1: + return info[0] + if len(info) > 1: + msg = "More than one %s exists with the name '%s'." + msg = (msg % (cls.resource_key, name_or_id)) + raise exceptions.DuplicateResource(msg) + msg = ("No %s with a name or ID of '%s' exists." % + (cls.resource_key, name_or_id)) + raise exceptions.ResourceNotFound(msg) diff --git a/openstack/tests/test_resource.py b/openstack/tests/test_resource.py index 7677b4620..f72e1062c 100644 --- a/openstack/tests/test_resource.py +++ b/openstack/tests/test_resource.py @@ -11,7 +11,9 @@ # under the License. import httpretty +import mock +from openstack import exceptions from openstack import resource from openstack import session from openstack.tests import base @@ -206,3 +208,81 @@ class ResourceTests(base.TestTransportBase): pass else: self.fail("Didn't raise attribute error") + + +class FakeResponse: + def __init__(self, response): + self.body = response + + +class TestFind(base.TestCase): + NAME = 'matrix' + ID = 'Fishburne' + + def setUp(self): + super(TestFind, self).setUp() + self.mock_session = mock.Mock() + self.mock_get = mock.Mock() + self.mock_session.get = self.mock_get + self.matrix = {'id': self.ID} + + def test_name(self): + self.mock_get.side_effect = [ + FakeResponse({FakeResource.resources_key: []}), + FakeResponse({FakeResource.resources_key: [self.matrix]}) + ] + + result = FakeResource.find(self.mock_session, self.NAME) + + self.assertEqual(self.ID, result.id) + p = {'fields': 'id', 'name': self.NAME} + self.mock_get.assert_called_with('/fakes', params=p, service=None) + + def test_id(self): + resp = FakeResponse({FakeResource.resources_key: [self.matrix]}) + self.mock_get.return_value = resp + + result = FakeResource.find(self.mock_session, self.ID) + + self.assertEqual(self.ID, result.id) + p = {'fields': 'id', 'id': self.ID} + self.mock_get.assert_called_with('/fakes', params=p, service=None) + + def test_nameo(self): + self.mock_get.side_effect = [ + FakeResponse({FakeResource.resources_key: []}), + FakeResponse({FakeResource.resources_key: [self.matrix]}) + ] + FakeResource.name_attribute = 'nameo' + + result = FakeResource.find(self.mock_session, self.NAME) + + FakeResource.name_attribute = 'name' + self.assertEqual(self.ID, result.id) + p = {'fields': 'id', 'nameo': self.NAME} + self.mock_get.assert_called_with('/fakes', params=p, service=None) + + def test_dups(self): + dup = {'id': 'Larry'} + resp = FakeResponse({FakeResource.resources_key: [self.matrix, dup]}) + self.mock_get.return_value = resp + + self.assertRaises(exceptions.DuplicateResource, FakeResource.find, + self.mock_session, self.NAME) + + def test_nada(self): + resp = FakeResponse({FakeResource.resources_key: []}) + self.mock_get.return_value = resp + + self.assertRaises(exceptions.ResourceNotFound, FakeResource.find, + self.mock_session, self.NAME) + + def test_no_name(self): + self.mock_get.side_effect = [ + FakeResponse({FakeResource.resources_key: []}), + FakeResponse({FakeResource.resources_key: [self.matrix]}) + ] + FakeResource.name_attribute = None + + self.assertRaises(exceptions.ResourceNotFound, FakeResource.find, + self.mock_session, self.NAME)