summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.openstack.org>2019-01-09 13:17:20 +0000
committerGerrit Code Review <review@openstack.org>2019-01-09 13:17:20 +0000
commitfb2c2e7de1bc4fd4eead3632de9358d1a7037c9f (patch)
treea514c6bcbbaaa1252a104985fdc733aae564cb84
parentdbff6adcb73f8564d5f6c38c0ee15c3503c55517 (diff)
parentdeffb09871c9460497929c57bd82ca78d1c3177f (diff)
Merge "Make QuotaImageTagsProxy deep-copyable"
-rw-r--r--glance/quota/__init__.py17
-rw-r--r--glance/tests/unit/test_quota.py24
2 files changed, 26 insertions, 15 deletions
diff --git a/glance/quota/__init__.py b/glance/quota/__init__.py
index 4560307..32ba983 100644
--- a/glance/quota/__init__.py
+++ b/glance/quota/__init__.py
@@ -165,14 +165,15 @@ class QuotaImageTagsProxy(object):
165 return self.tags.__len__(*args, **kwargs) 165 return self.tags.__len__(*args, **kwargs)
166 166
167 def __getattr__(self, name): 167 def __getattr__(self, name):
168 # Use TypeError here, not AttributeError, as the latter is how we 168 # Protect against deepcopy, which calls getattr. __getattr__
169 # know a tag is not present. TypeError says "this object is not 169 # is only called when an attribute is not "normal", so when
170 # what it claims to be". 170 # self.tags is called, this is not.
171 try: 171 if name == 'tags':
172 tags = self.__getattribute__('tags') 172 try:
173 except AttributeError: 173 return self.__getattribute__('tags')
174 raise TypeError('QuotaImageTagsProxy has no tags.') 174 except AttributeError:
175 return getattr(tags, name) 175 return None
176 return getattr(self.tags, name)
176 177
177 178
178class ImageMemberFactoryProxy(glance.domain.proxy.ImageMembershipFactory): 179class ImageMemberFactoryProxy(glance.domain.proxy.ImageMembershipFactory):
diff --git a/glance/tests/unit/test_quota.py b/glance/tests/unit/test_quota.py
index fff5b1d..51d0295 100644
--- a/glance/tests/unit/test_quota.py
+++ b/glance/tests/unit/test_quota.py
@@ -12,6 +12,7 @@
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15import copy
15import uuid 16import uuid
16 17
17import mock 18import mock
@@ -596,15 +597,24 @@ class TestQuotaImageTagsProxy(test_utils.BaseTestCase):
596 items.remove(item) 597 items.remove(item)
597 self.assertEqual(0, len(items)) 598 self.assertEqual(0, len(items))
598 599
599 def test_tags_attr_exception(self): 600 def test_tags_attr_no_loop(self):
600 proxy = glance.quota.QuotaImageTagsProxy(None) 601 proxy = glance.quota.QuotaImageTagsProxy(None)
601 self.assertRaises(AttributeError, lambda: proxy.foo) 602 self.assertEqual(set([]), proxy.tags)
602 603
603 # Remove tags to cause the object to be broken. If tags 604 def test_tags_deepcopy(self):
604 # is not there and we weren't raising TypeError, we'd 605 proxy = glance.quota.QuotaImageTagsProxy(set(['a', 'b']))
605 # get an infinite loop when calling 'proxy.foo'. 606 proxy_copy = copy.deepcopy(proxy)
607 self.assertEqual(set(['a', 'b']), proxy_copy.tags)
608 self.assertIn('a', proxy_copy)
609 # remove is a found via __getattr__
610 proxy_copy.remove('a')
611 self.assertNotIn('a', proxy_copy)
612
613 def test_tags_delete(self):
614 proxy = glance.quota.QuotaImageTagsProxy(set(['a', 'b']))
615 self.assertEqual(set(['a', 'b']), proxy.tags)
606 del proxy.tags 616 del proxy.tags
607 self.assertRaises(TypeError, lambda: proxy.foo) 617 self.assertIsNone(proxy.tags)
608 618
609 619
610class TestImageMemberQuotas(test_utils.BaseTestCase): 620class TestImageMemberQuotas(test_utils.BaseTestCase):