diff --git a/glance/quota/__init__.py b/glance/quota/__init__.py index 4560307cd0..32ba9837a1 100644 --- a/glance/quota/__init__.py +++ b/glance/quota/__init__.py @@ -165,14 +165,15 @@ class QuotaImageTagsProxy(object): return self.tags.__len__(*args, **kwargs) def __getattr__(self, name): - # Use TypeError here, not AttributeError, as the latter is how we - # know a tag is not present. TypeError says "this object is not - # what it claims to be". - try: - tags = self.__getattribute__('tags') - except AttributeError: - raise TypeError('QuotaImageTagsProxy has no tags.') - return getattr(tags, name) + # Protect against deepcopy, which calls getattr. __getattr__ + # is only called when an attribute is not "normal", so when + # self.tags is called, this is not. + if name == 'tags': + try: + return self.__getattribute__('tags') + except AttributeError: + return None + return getattr(self.tags, name) class ImageMemberFactoryProxy(glance.domain.proxy.ImageMembershipFactory): diff --git a/glance/tests/unit/test_quota.py b/glance/tests/unit/test_quota.py index fff5b1daa7..51d0295e10 100644 --- a/glance/tests/unit/test_quota.py +++ b/glance/tests/unit/test_quota.py @@ -12,6 +12,7 @@ # 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 copy import uuid import mock @@ -596,15 +597,24 @@ class TestQuotaImageTagsProxy(test_utils.BaseTestCase): items.remove(item) self.assertEqual(0, len(items)) - def test_tags_attr_exception(self): + def test_tags_attr_no_loop(self): proxy = glance.quota.QuotaImageTagsProxy(None) - self.assertRaises(AttributeError, lambda: proxy.foo) + self.assertEqual(set([]), proxy.tags) - # Remove tags to cause the object to be broken. If tags - # is not there and we weren't raising TypeError, we'd - # get an infinite loop when calling 'proxy.foo'. + def test_tags_deepcopy(self): + proxy = glance.quota.QuotaImageTagsProxy(set(['a', 'b'])) + proxy_copy = copy.deepcopy(proxy) + self.assertEqual(set(['a', 'b']), proxy_copy.tags) + self.assertIn('a', proxy_copy) + # remove is a found via __getattr__ + proxy_copy.remove('a') + self.assertNotIn('a', proxy_copy) + + def test_tags_delete(self): + proxy = glance.quota.QuotaImageTagsProxy(set(['a', 'b'])) + self.assertEqual(set(['a', 'b']), proxy.tags) del proxy.tags - self.assertRaises(TypeError, lambda: proxy.foo) + self.assertIsNone(proxy.tags) class TestImageMemberQuotas(test_utils.BaseTestCase):