Skip to content

Commit 0d661c3

Browse files
Jenkinsopenstack-gerrit
Jenkins
authored andcommitted
Merge "Get mandatory patch attrs from WSME properties"
2 parents 03dd109 + d82d8b2 commit 0d661c3

File tree

10 files changed

+158
-139
lines changed

10 files changed

+158
-139
lines changed

magnum/api/controllers/v1/bay.py

+13-16
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,6 @@
4040
LOG = logging.getLogger(__name__)
4141

4242

43-
class BayPatchType(types.JsonPatchType):
44-
45-
@staticmethod
46-
def mandatory_attrs():
47-
return ['/baymodel_id']
48-
49-
@staticmethod
50-
def internal_attrs():
51-
internal_attrs = ['/api_address', '/node_addresses',
52-
'/master_addresses', '/stack_id',
53-
'/ca_cert_ref', '/magnum_cert_ref',
54-
'/trust_id', '/trustee_user_name',
55-
'/trustee_password', '/trustee_user_id']
56-
return types.JsonPatchType.internal_attrs() + internal_attrs
57-
58-
5943
class BayID(wtypes.Base):
6044
uuid = types.uuid
6145

@@ -185,6 +169,19 @@ def sample(cls, expand=True):
185169
return cls._convert_with_links(sample, 'http://localhost:9511', expand)
186170

187171

172+
class BayPatchType(types.JsonPatchType):
173+
_api_base = Bay
174+
175+
@staticmethod
176+
def internal_attrs():
177+
internal_attrs = ['/api_address', '/node_addresses',
178+
'/master_addresses', '/stack_id',
179+
'/ca_cert_ref', '/magnum_cert_ref',
180+
'/trust_id', '/trustee_user_name',
181+
'/trustee_password', '/trustee_user_id']
182+
return types.JsonPatchType.internal_attrs() + internal_attrs
183+
184+
188185
class BayCollection(collection.Collection):
189186
"""API representation of a collection of bays."""
190187

magnum/api/controllers/v1/baymodel.py

+8-9
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,6 @@
3333
from magnum.objects import fields
3434

3535

36-
class BayModelPatchType(types.JsonPatchType):
37-
38-
@staticmethod
39-
def mandatory_attrs():
40-
return ['/image_id', '/keypair_id', '/external_network_id', '/coe',
41-
'/tls_disabled', '/public', '/registry_enabled',
42-
'/server_type', '/cluster_distro', '/network_driver']
43-
44-
4536
class BayModel(base.APIBase):
4637
"""API representation of a baymodel.
4738
@@ -206,6 +197,14 @@ def sample(cls):
206197
return cls._convert_with_links(sample, 'http://localhost:9511')
207198

208199

200+
class BayModelPatchType(types.JsonPatchType):
201+
_api_base = BayModel
202+
_extra_non_removable_attrs = {'/network_driver', '/external_network_id',
203+
'/tls_disabled', '/public', '/server_type',
204+
'/coe', '/registry_enabled',
205+
'/cluster_distro'}
206+
207+
209208
class BayModelCollection(collection.Collection):
210209
"""API representation of a collection of baymodels."""
211210

magnum/api/controllers/v1/cluster.py

+13-15
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,6 @@
4040
LOG = logging.getLogger(__name__)
4141

4242

43-
class ClusterPatchType(types.JsonPatchType):
44-
@staticmethod
45-
def mandatory_attrs():
46-
return ['/cluster_template_id']
47-
48-
@staticmethod
49-
def internal_attrs():
50-
internal_attrs = ['/api_address', '/node_addresses',
51-
'/master_addresses', '/stack_id',
52-
'/ca_cert_ref', '/magnum_cert_ref',
53-
'/trust_id', '/trustee_user_name',
54-
'/trustee_password', '/trustee_user_id']
55-
return types.JsonPatchType.internal_attrs() + internal_attrs
56-
57-
5843
class ClusterID(wtypes.Base):
5944
"""API representation of a cluster ID
6045
@@ -237,6 +222,19 @@ def as_dict(self):
237222
return d
238223

239224

225+
class ClusterPatchType(types.JsonPatchType):
226+
_api_base = Cluster
227+
228+
@staticmethod
229+
def internal_attrs():
230+
internal_attrs = ['/api_address', '/node_addresses',
231+
'/master_addresses', '/stack_id',
232+
'/ca_cert_ref', '/magnum_cert_ref',
233+
'/trust_id', '/trustee_user_name',
234+
'/trustee_password', '/trustee_user_id']
235+
return types.JsonPatchType.internal_attrs() + internal_attrs
236+
237+
240238
class ClusterCollection(collection.Collection):
241239
"""API representation of a collection of clusters."""
242240

magnum/api/controllers/v1/cluster_template.py

+8-9
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,6 @@
3333
from magnum.objects import fields
3434

3535

36-
class ClusterTemplatePatchType(types.JsonPatchType):
37-
38-
@staticmethod
39-
def mandatory_attrs():
40-
return ['/image_id', '/keypair_id', '/external_network_id', '/coe',
41-
'/tls_disabled', '/public', '/registry_enabled',
42-
'/server_type', '/cluster_distro', '/network_driver']
43-
44-
4536
class ClusterTemplate(base.APIBase):
4637
"""API representation of a clustertemplate.
4738
@@ -209,6 +200,14 @@ def sample(cls):
209200
return cls._convert_with_links(sample, 'http://localhost:9511')
210201

211202

203+
class ClusterTemplatePatchType(types.JsonPatchType):
204+
_api_base = ClusterTemplate
205+
_extra_non_removable_attrs = {'/network_driver', '/external_network_id',
206+
'/tls_disabled', '/public', '/server_type',
207+
'/coe', '/registry_enabled',
208+
'/cluster_distro'}
209+
210+
212211
class ClusterTemplateCollection(collection.Collection):
213212
"""API representation of a collection of clustertemplates."""
214213

magnum/api/controllers/v1/types.py

+30-8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
# License for the specific language governing permissions and limitations
1414
# under the License.
1515

16+
import inspect
17+
1618
from oslo_utils import strutils
1719
from oslo_utils import uuidutils
1820
import wsme
@@ -143,6 +145,17 @@ class JsonPatchType(wtypes.Base):
143145
mandatory=True)
144146
value = MultiType(wtypes.text, int)
145147

148+
# The class of the objects being patched. Override this in subclasses.
149+
# Should probably be a subclass of magnum.api.controllers.base.APIBase.
150+
_api_base = None
151+
152+
# Attributes that are not required for construction, but which may not be
153+
# removed if set. Override in subclasses if needed.
154+
_extra_non_removable_attrs = set()
155+
156+
# Set of non-removable attributes, calculated lazily.
157+
_non_removable_attrs = None
158+
146159
@staticmethod
147160
def internal_attrs():
148161
"""Returns a list of internal attributes.
@@ -154,23 +167,32 @@ def internal_attrs():
154167
return ['/created_at', '/id', '/links', '/updated_at',
155168
'/uuid', '/project_id', '/user_id']
156169

157-
@staticmethod
158-
def mandatory_attrs():
159-
"""Retruns a list of mandatory attributes.
160-
161-
Mandatory attributes can't be removed from the document. This
162-
method should be overwritten by derived class.
170+
@classmethod
171+
def non_removable_attrs(self):
172+
"""Returns a set of names of attributes that may not be removed.
163173
174+
Attributes whose 'mandatory' property is True are automatically added
175+
to this set. To add additional attributes to the set, override the
176+
field _extra_non_removable_attrs in subclasses, with a set of the form
177+
{'/foo', '/bar'}.
164178
"""
165-
return []
179+
if self._non_removable_attrs is None:
180+
self._non_removable_attrs = self._extra_non_removable_attrs.copy()
181+
if self._api_base:
182+
fields = inspect.getmembers(self._api_base,
183+
lambda a: not inspect.isroutine(a))
184+
for name, field in fields:
185+
if getattr(field, 'mandatory', False):
186+
self._non_removable_attrs.add('/%s' % name)
187+
return self._non_removable_attrs
166188

167189
@staticmethod
168190
def validate(patch):
169191
if patch.path in patch.internal_attrs():
170192
msg = _("'%s' is an internal attribute and can not be updated")
171193
raise wsme.exc.ClientSideError(msg % patch.path)
172194

173-
if patch.path in patch.mandatory_attrs() and patch.op == 'remove':
195+
if patch.path in patch.non_removable_attrs() and patch.op == 'remove':
174196
msg = _("'%s' is a mandatory attribute and can not be removed")
175197
raise wsme.exc.ClientSideError(msg % patch.path)
176198

magnum/tests/unit/api/controllers/v1/test_bay.py

+9-15
Original file line numberDiff line numberDiff line change
@@ -384,21 +384,15 @@ def test_remove_ok(self):
384384
self.assertEqual(self.bay.name, response['name'])
385385
self.assertEqual(self.bay.master_count, response['master_count'])
386386

387-
def test_remove_uuid(self):
388-
response = self.patch_json('/bays/%s' % self.bay.uuid,
389-
[{'path': '/uuid', 'op': 'remove'}],
390-
expect_errors=True)
391-
self.assertEqual(400, response.status_int)
392-
self.assertEqual('application/json', response.content_type)
393-
self.assertTrue(response.json['errors'])
394-
395-
def test_remove_baymodel_id(self):
396-
response = self.patch_json('/bays/%s' % self.bay.uuid,
397-
[{'path': '/baymodel_id', 'op': 'remove'}],
398-
expect_errors=True)
399-
self.assertEqual(400, response.status_int)
400-
self.assertEqual('application/json', response.content_type)
401-
self.assertTrue(response.json['errors'])
387+
def test_remove_mandatory_property_fail(self):
388+
mandatory_properties = ('/uuid', '/baymodel_id')
389+
for p in mandatory_properties:
390+
response = self.patch_json('/bays/%s' % self.bay.uuid,
391+
[{'path': p, 'op': 'remove'}],
392+
expect_errors=True)
393+
self.assertEqual(400, response.status_int)
394+
self.assertEqual('application/json', response.content_type)
395+
self.assertTrue(response.json['errors'])
402396

403397
def test_remove_non_existent_property(self):
404398
response = self.patch_json(

magnum/tests/unit/api/controllers/v1/test_baymodel.py

+20-22
Original file line numberDiff line numberDiff line change
@@ -368,39 +368,37 @@ def test_create_baymodel_with_no_os_distro_image(self):
368368
self.assertTrue(response.json['errors'])
369369

370370
def test_remove_singular(self):
371-
baymodel = obj_utils.create_test_baymodel(
372-
self.context,
373-
uuid=uuidutils.generate_uuid())
374-
response = self.get_json('/baymodels/%s' % baymodel.uuid)
371+
response = self.get_json('/baymodels/%s' % self.baymodel.uuid)
375372
self.assertIsNotNone(response['dns_nameserver'])
376373

377-
response = self.patch_json('/baymodels/%s' % baymodel.uuid,
374+
response = self.patch_json('/baymodels/%s' % self.baymodel.uuid,
378375
[{'path': '/dns_nameserver',
379376
'op': 'remove'}])
380377
self.assertEqual('application/json', response.content_type)
381378
self.assertEqual(200, response.status_code)
382379

383-
response = self.get_json('/baymodels/%s' % baymodel.uuid)
380+
response = self.get_json('/baymodels/%s' % self.baymodel.uuid)
384381
self.assertIsNone(response['dns_nameserver'])
385382
# Assert nothing else was changed
386-
self.assertEqual(baymodel.uuid, response['uuid'])
387-
self.assertEqual(baymodel.name, response['name'])
388-
self.assertEqual(baymodel.apiserver_port, response['apiserver_port'])
389-
self.assertEqual(baymodel.image_id,
383+
self.assertEqual(self.baymodel.uuid, response['uuid'])
384+
self.assertEqual(self.baymodel.name, response['name'])
385+
self.assertEqual(self.baymodel.apiserver_port,
386+
response['apiserver_port'])
387+
self.assertEqual(self.baymodel.image_id,
390388
response['image_id'])
391-
self.assertEqual(baymodel.fixed_network,
389+
self.assertEqual(self.baymodel.fixed_network,
392390
response['fixed_network'])
393-
self.assertEqual(baymodel.network_driver,
391+
self.assertEqual(self.baymodel.network_driver,
394392
response['network_driver'])
395-
self.assertEqual(baymodel.volume_driver,
393+
self.assertEqual(self.baymodel.volume_driver,
396394
response['volume_driver'])
397-
self.assertEqual(baymodel.docker_volume_size,
395+
self.assertEqual(self.baymodel.docker_volume_size,
398396
response['docker_volume_size'])
399-
self.assertEqual(baymodel.coe, response['coe'])
400-
self.assertEqual(baymodel.http_proxy, response['http_proxy'])
401-
self.assertEqual(baymodel.https_proxy, response['https_proxy'])
402-
self.assertEqual(baymodel.no_proxy, response['no_proxy'])
403-
self.assertEqual(baymodel.labels, response['labels'])
397+
self.assertEqual(self.baymodel.coe, response['coe'])
398+
self.assertEqual(self.baymodel.http_proxy, response['http_proxy'])
399+
self.assertEqual(self.baymodel.https_proxy, response['https_proxy'])
400+
self.assertEqual(self.baymodel.no_proxy, response['no_proxy'])
401+
self.assertEqual(self.baymodel.labels, response['labels'])
404402

405403
def test_remove_non_existent_property_fail(self):
406404
response = self.patch_json('/baymodels/%s' % self.baymodel.uuid,
@@ -411,10 +409,10 @@ def test_remove_non_existent_property_fail(self):
411409
self.assertTrue(response.json['errors'])
412410

413411
def test_remove_mandatory_property_fail(self):
414-
mandatory_properties = ('/image_id', '/keypair_id',
415-
'/external_network_id', '/coe',
412+
mandatory_properties = ('/image_id', '/keypair_id', '/coe',
413+
'/external_network_id', '/server_type',
416414
'/tls_disabled', '/public',
417-
'/registry_enabled', '/server_type',
415+
'/registry_enabled',
418416
'/cluster_distro', '/network_driver')
419417
for p in mandatory_properties:
420418
response = self.patch_json('/baymodels/%s' % self.baymodel.uuid,

magnum/tests/unit/api/controllers/v1/test_cluster.py

+9-16
Original file line numberDiff line numberDiff line change
@@ -391,22 +391,15 @@ def test_remove_ok(self):
391391
self.assertEqual(self.cluster_obj.master_count,
392392
response['master_count'])
393393

394-
def test_remove_uuid(self):
395-
response = self.patch_json('/clusters/%s' % self.cluster_obj.uuid,
396-
[{'path': '/uuid', 'op': 'remove'}],
397-
expect_errors=True)
398-
self.assertEqual(400, response.status_int)
399-
self.assertEqual('application/json', response.content_type)
400-
self.assertTrue(response.json['errors'])
401-
402-
def test_remove_cluster_template_id(self):
403-
response = self.patch_json('/clusters/%s' % self.cluster_obj.uuid,
404-
[{'path': '/cluster_template_id',
405-
'op': 'remove'}],
406-
expect_errors=True)
407-
self.assertEqual(400, response.status_int)
408-
self.assertEqual('application/json', response.content_type)
409-
self.assertTrue(response.json['errors'])
394+
def test_remove_mandatory_property_fail(self):
395+
mandatory_properties = ('/uuid', '/cluster_template_id')
396+
for p in mandatory_properties:
397+
response = self.patch_json('/clusters/%s' % self.cluster_obj.uuid,
398+
[{'path': p, 'op': 'remove'}],
399+
expect_errors=True)
400+
self.assertEqual(400, response.status_int)
401+
self.assertEqual('application/json', response.content_type)
402+
self.assertTrue(response.json['errors'])
410403

411404
def test_remove_non_existent_property(self):
412405
response = self.patch_json(

0 commit comments

Comments
 (0)