region.description is optional and can be null
region.description is documented to be optional and should be treated as optional. A null description provided when creating indicates that the user doesn't want a description stored. This is equivalent to not sending a description property at all. A null description provided when updating indicates that the user doesn't want a description stored and any existing description should be removed. In both cases we treat this as an empty string because our database schema requires a non-null value for description. Closes-Bug: #1398165 Change-Id: I4a890414927ab5278861341b71a3e7fb324946a4
This commit is contained in:
parent
f5332c99be
commit
c6e96e4cb2
|
@ -103,10 +103,12 @@ class Manager(manager.Manager):
|
||||||
msg = _('Duplicate ID, %s.') % region_ref['id']
|
msg = _('Duplicate ID, %s.') % region_ref['id']
|
||||||
raise exception.Conflict(type='region', details=msg)
|
raise exception.Conflict(type='region', details=msg)
|
||||||
|
|
||||||
# NOTE(lbragstad): The description column of the region database
|
# NOTE(lbragstad,dstanek): The description column of the region
|
||||||
# can not be null. So if the user doesn't pass in a description then
|
# database cannot be null. So if the user doesn't pass in a
|
||||||
# set it to an empty string.
|
# description or passes in a null description then set it to an
|
||||||
region_ref.setdefault('description', '')
|
# empty string.
|
||||||
|
if region_ref.get('description') is None:
|
||||||
|
region_ref['description'] = ''
|
||||||
try:
|
try:
|
||||||
ret = self.driver.create_region(region_ref)
|
ret = self.driver.create_region(region_ref)
|
||||||
except exception.NotFound:
|
except exception.NotFound:
|
||||||
|
@ -124,6 +126,11 @@ class Manager(manager.Manager):
|
||||||
raise exception.RegionNotFound(region_id=region_id)
|
raise exception.RegionNotFound(region_id=region_id)
|
||||||
|
|
||||||
def update_region(self, region_id, region_ref, initiator=None):
|
def update_region(self, region_id, region_ref, initiator=None):
|
||||||
|
# NOTE(lbragstad,dstanek): The description column of the region
|
||||||
|
# database cannot be null. So if the user passes in a null
|
||||||
|
# description set it to an empty string.
|
||||||
|
if 'description' in region_ref and region_ref['description'] is None:
|
||||||
|
region_ref['description'] = ''
|
||||||
ref = self.driver.update_region(region_id, region_ref)
|
ref = self.driver.update_region(region_id, region_ref)
|
||||||
notifications.Audit.updated(self._REGION, region_id, initiator)
|
notifications.Audit.updated(self._REGION, region_id, initiator)
|
||||||
self.get_region.invalidate(self, region_id)
|
self.get_region.invalidate(self, region_id)
|
||||||
|
|
|
@ -14,7 +14,9 @@ from keystone.common.validation import parameter_types
|
||||||
|
|
||||||
|
|
||||||
_region_properties = {
|
_region_properties = {
|
||||||
'description': parameter_types.description,
|
'description': {
|
||||||
|
'type': ['string', 'null'],
|
||||||
|
},
|
||||||
# NOTE(lbragstad): Regions use ID differently. The user can specify the ID
|
# NOTE(lbragstad): Regions use ID differently. The user can specify the ID
|
||||||
# or it will be generated automatically.
|
# or it will be generated automatically.
|
||||||
'id': {
|
'id': {
|
||||||
|
|
|
@ -154,7 +154,7 @@ class CatalogTestCase(test_v3.RestfulTestCase):
|
||||||
ref2 = self.new_region_ref()
|
ref2 = self.new_region_ref()
|
||||||
|
|
||||||
del ref1['description']
|
del ref1['description']
|
||||||
del ref2['description']
|
ref2['description'] = None
|
||||||
|
|
||||||
resp1 = self.post(
|
resp1 = self.post(
|
||||||
'/regions',
|
'/regions',
|
||||||
|
@ -224,6 +224,39 @@ class CatalogTestCase(test_v3.RestfulTestCase):
|
||||||
body={'region': region})
|
body={'region': region})
|
||||||
self.assertValidRegionResponse(r, region)
|
self.assertValidRegionResponse(r, region)
|
||||||
|
|
||||||
|
def test_update_region_without_description_keeps_original(self):
|
||||||
|
"""Call ``PATCH /regions/{region_id}``."""
|
||||||
|
region_ref = self.new_region_ref()
|
||||||
|
|
||||||
|
resp = self.post('/regions', body={'region': region_ref},
|
||||||
|
expected_status=201)
|
||||||
|
|
||||||
|
region_updates = {
|
||||||
|
# update with something that's not the description
|
||||||
|
'parent_region_id': self.region_id,
|
||||||
|
}
|
||||||
|
resp = self.patch('/regions/%s' % region_ref['id'],
|
||||||
|
body={'region': region_updates},
|
||||||
|
expected_status=200)
|
||||||
|
|
||||||
|
# NOTE(dstanek): Keystone should keep the original description.
|
||||||
|
self.assertEqual(region_ref['description'],
|
||||||
|
resp.result['region']['description'])
|
||||||
|
|
||||||
|
def test_update_region_with_null_description(self):
|
||||||
|
"""Call ``PATCH /regions/{region_id}``."""
|
||||||
|
region = self.new_region_ref()
|
||||||
|
del region['id']
|
||||||
|
region['description'] = None
|
||||||
|
r = self.patch('/regions/%(region_id)s' % {
|
||||||
|
'region_id': self.region_id},
|
||||||
|
body={'region': region})
|
||||||
|
|
||||||
|
# NOTE(dstanek): Keystone should turn the provided None value into
|
||||||
|
# an empty string before storing in the backend.
|
||||||
|
region['description'] = ''
|
||||||
|
self.assertValidRegionResponse(r, region)
|
||||||
|
|
||||||
def test_delete_region(self):
|
def test_delete_region(self):
|
||||||
"""Call ``DELETE /regions/{region_id}``."""
|
"""Call ``DELETE /regions/{region_id}``."""
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue