Commit 274c2cb8 authored by Nguyen Van Trung's avatar Nguyen Van Trung Committed by Graham Hayes

Migrate object to OVO (4)

This commit will migrate:
- Pool and PoolList
- Related objects
Co-authored-By: default avatarDai Dang Van <daidv@vn.fujitsu.com>

Change-Id: I59da0628a9dc23efe8d8665bbf3359e652eaa8c9
Implements: blueprint designate-rolling-upgrade
parent aac3812c
......@@ -51,16 +51,16 @@ class YAMLAdapter(base.DesignateAdapter):
obj_key = key
# Check if this item is a relation (another DesignateObject that
# will need to be converted itself
if object.FIELDS.get(obj_key, {}).get('relation'):
if hasattr(object.FIELDS.get(obj_key, {}), 'objname'):
# Get a adapter for the nested object
# Get the class the object is and get its adapter, then set
# the item in the dict to the output
r_obj[key] = cls.get_object_adapter(
cls.ADAPTER_FORMAT,
object.FIELDS[obj_key].get('relation_cls')).render(
cls.ADAPTER_FORMAT, obj, *args, **kwargs)
elif object.FIELDS.get(
obj_key, {}).get('schema', {}).get('type') == 'integer':
object.FIELDS[obj_key].objname).render(
cls.ADAPTER_FORMAT, obj, *args, **kwargs)
elif all(hasattr(object.FIELDS.get(obj_key, {}), attr)
for attr in ['min', 'max']):
r_obj[key] = int(obj)
elif obj is not None:
# Just attach the damn item if there is no weird edge cases
......
......@@ -288,3 +288,20 @@ class BaseObject(ovoo_fields.FieldType):
class BaseObjectField(ovoo_fields.AutoTypedField):
AUTO_TYPE = BaseObject()
class IPOrHost(IPV4AndV6AddressField):
def __init__(self, nullable=False, read_only=False,
default=ovoo_fields.UnspecifiedDefault):
super(IPOrHost, self).__init__(nullable=nullable,
default=default, read_only=read_only)
def coerce(self, obj, attr, value):
try:
value = super(IPOrHost, self).coerce(obj, attr, value)
except ValueError:
if not re.match(StringFields.RE_ZONENAME, value):
raise ValueError("%s is not IP address or host name" % value)
# we use this field as a string, not need a netaddr.IPAdress
# as oslo.versionedobjects is using
return str(value)
......@@ -22,6 +22,8 @@ from oslo_versionedobjects.base import VersionedObjectDictCompat as DictObjectMi
from designate.i18n import _
from designate.i18n import _LE
from designate.objects import fields
from designate.objects.validation_error import ValidationError
from designate.objects.validation_error import ValidationErrorList
from designate import exceptions
LOG = logging.getLogger(__name__)
......@@ -209,20 +211,31 @@ class DesignateObject(base.VersionedObject):
def validate(self):
self.fields = self.FIELDS
try:
for name in self.fields:
field = self.fields[name]
if self.obj_attr_is_set(name):
value = getattr(self, name) # Check relation
field.coerce(self, name, value) # Check value
if isinstance(value, base.ObjectListBase):
for obj in value:
obj.validate()
elif not field.nullable:
# Check required is True ~ nullable is False
raise exceptions.InvalidObject
except Exception:
raise exceptions.InvalidObject
for name in self.fields:
field = self.fields[name]
if self.obj_attr_is_set(name):
value = getattr(self, name) # Check relation
if isinstance(value, ListObjectMixin):
for obj in value.objects:
obj.validate()
else:
try:
field.coerce(self, name, value) # Check value
except Exception as e:
raise exceptions.InvalidObject(
"{} is invalid".format(name))
elif not field.nullable:
# Check required is True ~ nullable is False
errors = ValidationErrorList()
e = ValidationError()
e.path = ['records', 0]
e.validator = 'required'
e.validator_value = [name]
e.message = "'%s' is a required property" % name
errors.append(e)
raise exceptions.InvalidObject(
"Provided object does not match "
"schema", errors=errors, object=self)
class ListObjectMixin(base.ObjectListBase):
......
......@@ -14,64 +14,25 @@
# License for the specific language governing permissions and limitations
# under the License.
from designate import utils
from designate.objects import base
from designate.objects import ovo_base as base
from designate.objects import fields
@base.DesignateRegistry.register
class Pool(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = {
'name': {
'schema': {
'type': 'string',
'description': 'Pool name',
'maxLength': 50,
},
'immutable': True,
'required': True
},
'description': {
'schema': {
'type': ['string', 'null'],
'description': 'Description for the pool',
'maxLength': 160
}
},
'tenant_id': {
'schema': {
'type': ['string', 'null'],
'description': 'Project identifier',
'maxLength': 36,
},
'immutable': True
},
'provisioner': {
'schema': {
'type': ['string', 'null'],
'description': 'Provisioner used for this pool',
'maxLength': 160
}
},
'attributes': {
'relation': True,
'relation_cls': 'PoolAttributeList',
},
'ns_records': {
'relation': True,
'relation_cls': 'PoolNsRecordList',
'required': True
},
'nameservers': {
'relation': True,
'relation_cls': 'PoolNameserverList'
},
'targets': {
'relation': True,
'relation_cls': 'PoolTargetList'
},
'also_notifies': {
'relation': True,
'relation_cls': 'PoolAlsoNotifyList'
},
fields = {
'name': fields.StringFields(maxLength=50),
'description': fields.StringFields(nullable=True, maxLength=160),
'tenant_id': fields.StringFields(maxLength=36, nullable=True),
'provisioner': fields.StringFields(nullable=True, maxLength=160),
'attributes': fields.ObjectFields('PoolAttributeList', nullable=True),
'ns_records': fields.ObjectFields('PoolNsRecordList', nullable=True),
'nameservers': fields.ObjectFields('PoolNameserverList',
nullable=True),
'targets': fields.ObjectFields('PoolTargetList', nullable=True),
'also_notifies': fields.ObjectFields('PoolAlsoNotifyList',
nullable=True),
}
@classmethod
......@@ -144,9 +105,14 @@ class Pool(base.DictObjectMixin, base.PersistentObjectMixin,
]
@base.DesignateRegistry.register
class PoolList(base.ListObjectMixin, base.DesignateObject):
LIST_ITEM_TYPE = Pool
fields = {
'objects': fields.ListOfObjectsField('Pool'),
}
def __contains__(self, pool):
for p in self.objects:
if p.id == pool.id:
......
......@@ -12,34 +12,17 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from designate.objects import base
from designate.objects import ovo_base as base
from designate.objects import fields
@base.DesignateRegistry.register
class PoolAlsoNotify(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = {
'pool_id': {
'schema': {
'type': 'string',
'description': 'Pool identifier',
'format': 'uuid',
},
},
'host': {
'schema': {
'type': 'string',
'format': 'ip-or-host',
'required': True,
},
},
'port': {
'schema': {
'type': 'integer',
'minimum': 1,
'maximum': 65535,
'required': True,
},
}
fields = {
'pool_id': fields.UUIDFields(nullable=True),
'host': fields.IPOrHost(),
'port': fields.IntegerFields(minimum=1, maximum=65535),
}
STRING_KEYS = [
......@@ -47,5 +30,9 @@ class PoolAlsoNotify(base.DictObjectMixin, base.PersistentObjectMixin,
]
@base.DesignateRegistry.register
class PoolAlsoNotifyList(base.ListObjectMixin, base.DesignateObject):
LIST_ITEM_TYPE = PoolAlsoNotify
fields = {
'objects': fields.ListOfObjectsField('PoolAlsoNotify'),
}
......@@ -12,33 +12,18 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from designate.objects import base
from designate.objects import ovo_base as base
from designate.objects import fields
@base.DesignateRegistry.register
class PoolAttribute(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = {
'pool_id': {
'schema': {
'type': 'string',
'description': 'Pool identifier',
'format': 'uuid',
},
},
'key': {
'schema': {
'type': 'string',
'maxLength': 50,
},
'required': True,
},
'value': {
'schema': {
'type': 'string',
'maxLength': 50,
},
'required': True
}
fields = {
'pool_id': fields.UUIDFields(nullable=True),
'key': fields.StringFields(maxLength=50),
'value': fields.StringFields(maxLength=50)
}
STRING_KEYS = [
......@@ -46,5 +31,9 @@ class PoolAttribute(base.DictObjectMixin, base.PersistentObjectMixin,
]
@base.DesignateRegistry.register
class PoolAttributeList(base.AttributeListObjectMixin, base.DesignateObject):
LIST_ITEM_TYPE = PoolAttribute
fields = {
'objects': fields.ListOfObjectsField('PoolAttribute'),
}
......@@ -13,44 +13,21 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from designate.objects import base
from designate.objects import ovo_base as base
from designate.objects import fields
@base.DesignateRegistry.register
class PoolManagerStatus(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = {
'nameserver_id': {
'schema': {
'type': 'string',
'format': 'uuid',
},
'required': True
},
'zone_id': {
'schema': {
'type': 'string',
'format': 'uuid',
},
'required': True},
'status': {
'schema': {
'type': ['string', 'null'],
'enum': ['ACTIVE', 'PENDING', 'ERROR'],
},
},
'serial_number': {
'schema': {
'type': 'integer',
'minimum': 0,
'maximum': 4294967295,
},
},
'action': {
'schema': {
'type': 'string',
'enum': ['CREATE', 'DELETE', 'UPDATE', 'NONE'],
},
}
fields = {
'nameserver_id': fields.UUIDFields(),
'zone_id': fields.UUIDFields(),
'status': fields.EnumField(['ACTIVE', 'PENDING', 'ERROR',
'SUCCESS', 'COMPLETE'], nullable=True),
'serial_number': fields.IntegerFields(minimum=0, maximum=4294967295),
'action': fields.EnumField(['CREATE', 'DELETE',
'UPDATE', 'NONE'], nullable=True),
}
STRING_KEYS = [
......@@ -58,5 +35,9 @@ class PoolManagerStatus(base.DictObjectMixin, base.PersistentObjectMixin,
]
@base.DesignateRegistry.register
class PoolManagerStatusList(base.ListObjectMixin, base.DesignateObject):
LIST_ITEM_TYPE = PoolManagerStatus
fields = {
'objects': fields.ListOfObjectsField('PoolManagerStatus'),
}
......@@ -12,34 +12,17 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from designate.objects import base
from designate.objects import ovo_base as base
from designate.objects import fields
@base.DesignateRegistry.register
class PoolNameserver(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = {
'pool_id': {
'schema': {
'type': 'string',
'description': 'Pool identifier',
'format': 'uuid',
},
},
'host': {
'schema': {
'type': 'string',
'format': 'ip-or-host',
'required': True,
},
},
'port': {
'schema': {
'type': 'integer',
'minimum': 1,
'maximum': 65535,
'required': True,
},
}
fields = {
'pool_id': fields.UUIDFields(nullable=True),
'host': fields.IPOrHost(),
'port': fields.IntegerFields(minimum=1, maximum=65535),
}
STRING_KEYS = [
......@@ -47,5 +30,9 @@ class PoolNameserver(base.DictObjectMixin, base.PersistentObjectMixin,
]
@base.DesignateRegistry.register
class PoolNameserverList(base.ListObjectMixin, base.DesignateObject):
LIST_ITEM_TYPE = PoolNameserver
fields = {
'objects': fields.ListOfObjectsField('PoolNameserver'),
}
......@@ -12,38 +12,18 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from designate.objects import base
from designate.objects import ovo_base as base
from designate.objects import fields
@base.DesignateRegistry.register
class PoolNsRecord(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = {
'pool_id': {
'schema': {
'type': 'string',
'description': 'Pool identifier',
'format': 'uuid',
},
},
'priority': {
'schema': {
'type': 'integer',
'description': 'NS Record Priority Order',
'minimum': 1,
'maximum': 10000
},
'required': True
},
'hostname': {
'schema': {
'type': 'string',
'description': 'NS Record Hostname',
'format': 'domainname',
'maxLength': 255,
},
'immutable': True,
'required': True
}
fields = {
'pool_id': fields.UUIDFields(nullable=True),
'priority': fields.IntegerFields(minimum=1, maximum=10000),
'hostname': fields.DomainField(maxLength=255),
}
STRING_KEYS = [
......@@ -51,5 +31,9 @@ class PoolNsRecord(base.DictObjectMixin, base.PersistentObjectMixin,
]
@base.DesignateRegistry.register
class PoolNsRecordList(base.ListObjectMixin, base.DesignateObject):
LIST_ITEM_TYPE = PoolNsRecord
fields = {
'objects': fields.ListOfObjectsField('PoolNsRecord'),
}
......@@ -12,43 +12,21 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from designate.objects import base
from designate.objects import ovo_base as base
from designate.objects import fields
@base.DesignateRegistry.register
class PoolTarget(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = {
'pool_id': {
'schema': {
'type': 'string',
'description': 'Pool identifier',
'format': 'uuid',
},
},
'type': {},
'tsigkey_id': {
'schema': {
'type': ['string', 'null'],
'description': 'TSIG identifier',
'format': 'uuid',
},
},
'description': {
'schema': {
'type': ['string', 'null'],
'description': 'Description for the pool',
'maxLength': 160
}
},
'masters': {
'relation': True,
'relation_cls': 'PoolTargetMasterList'
},
'options': {
'relation': True,
'relation_cls': 'PoolTargetOptionList'
},
'backend': {}
fields = {
'pool_id': fields.UUIDFields(nullable=True),
'type': fields.AnyField(nullable=True),
'tsigkey_id': fields.UUIDFields(nullable=True),
'description': fields.StringFields(maxLength=160, nullable=True),
'masters': fields.ObjectFields('PoolTargetMasterList'),
'options': fields.ObjectFields('PoolTargetOptionList'),
'backend': fields.AnyField(nullable=True),
}
STRING_KEYS = [
......@@ -56,5 +34,9 @@ class PoolTarget(base.DictObjectMixin, base.PersistentObjectMixin,
]
@base.DesignateRegistry.register
class PoolTargetList(base.ListObjectMixin, base.DesignateObject):
LIST_ITEM_TYPE = PoolTarget
fields = {
'objects': fields.ListOfObjectsField('PoolTarget'),
}
......@@ -12,34 +12,17 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from designate.objects import base
from designate.objects import ovo_base as base
from designate.objects import fields
@base.DesignateRegistry.register
class PoolTargetMaster(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = {
'pool_target_id': {
'schema': {
'type': 'string',
'description': 'Pool Target identifier',
'format': 'uuid',
},
},
'host': {
'schema': {
'type': 'string',
'format': 'ip-or-host',
'required': True,
},
},
'port': {
'schema': {
'type': 'integer',
'minimum': 1,
'maximum': 65535,
'required': True,
},
}
fields = {
'pool_target_id': fields.UUIDFields(nullable=True),
'host': fields.IPOrHost(),
'port': fields.IntegerFields(minimum=1, maximum=65535)
}
STRING_KEYS = [
......@@ -47,5 +30,9 @@ class PoolTargetMaster(base.DictObjectMixin, base.PersistentObjectMixin,
]
@base.DesignateRegistry.register
class PoolTargetMasterList(base.ListObjectMixin, base.DesignateObject):
LIST_ITEM_TYPE = PoolTargetMaster
fields = {
'objects': fields.ListOfObjectsField('PoolTargetMaster'),
}
......@@ -12,33 +12,17 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from designate.objects import base
from designate.objects import ovo_base as base
from designate.objects import fields
@base.DesignateRegistry.register
class PoolTargetOption(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
FIELDS = {
'pool_target_id': {
'schema': {
'type': 'string',
'description': 'Pool Target identifier',
'format': 'uuid',
},
},
'key': {
'schema': {
'type': 'string',
'maxLength': 255,
},
'required': True,
},
'value': {
'schema': {
'type': 'string',
'maxLength': 255,
},
'required': True
}
fields = {
'pool_target_id': fields.UUIDFields(nullable=True),
'key': fields.StringFields(maxLength=255),
'value': fields.AnyField(),
}
STRING_KEYS = [
......@@ -46,6 +30,10 @@ class PoolTargetOption(base.DictObjectMixin, base.PersistentObjectMixin,
]
@base.DesignateRegistry.register
class PoolTargetOptionList(base.AttributeListObjectMixin,
base.DesignateObject):
LIST_ITEM_TYPE = PoolTargetOption
fields = {
'objects': fields.ListOfObjectsField('PoolTargetOption'),
}
......@@ -21,11 +21,11 @@ from designate.objects import fields
class ZoneExport(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
fields = {
'status': fields.EnumField(
'status': fields.EnumField(nullable=True,
valid_values=["ACTIVE", "PENDING",
"DELETED", "ERROR", "COMPLETE"]
),
'task_type': fields.EnumField(
'task_type': fields.EnumField(nullable=True,
valid_values=["EXPORT"]
),
'tenant_id': fields.StringFields(nullable=True),
......
......@@ -21,11 +21,11 @@ from designate.objects import fields
class ZoneImport(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
fields = {
'status': fields.EnumField(
'status': fields.EnumField(nullable=True,
valid_values=["ACTIVE", "PENDING",
"DELETED", "ERROR", "COMPLETE"]
),
'task_type': fields.EnumField(
'task_type': fields.EnumField(nullable=True,
valid_values=["IMPORT"]
),
'tenant_id': fields.StringFields(nullable=True),
......
......@@ -519,9 +519,14 @@ class ApiV2ZonesTest(ApiV2TestCase):
def test_update_secondary(self):
# Create a zone
fixture = self.get_zone_fixture('SECONDARY', 0)
zone = objects.Zone(**fixture)
zone = objects.Zone(
name='example.com.',
type='SECONDARY',
masters=objects.ZoneMasterList.from_list([
{'host': '1.0.0.0', 'port': 69},
{'host': '2.0.0.0', 'port': 69}
])
)
zone.email = cfg.CONF['service:central'].managed_resource_email
# Create a zone
......
......@@ -350,7 +350,9 @@ class PoolManagerServiceNoopTest(PoolManagerTestCase):
zone = self._build_zone('example.org.', 'UPDATE', 'PENDING')
self.service._update_zone_on_target = Mock(return_value=True)
self.service._update_zone_on_also_notify = Mock()
self.service.pool.also_notifies = ['bogus']
self.service.pool.also_notifies = objects.PoolAlsoNotifyList(
objects=[objects.PoolAlsoNotify(host='1.0.0.0', port=1)]
)
self.service._exceed_or_meet_threshold = Mock(return_value=True)
# cache.retrieve will throw exceptions.PoolManagerStatusNotFound
......
......@@ -50,11 +50,11 @@ mock_conf = RoObject(**{
also_notifies=['1.0.0.0:1', '2.0.0.0:2']
),
'pool_nameserver:169ca3fc-5924-4a44-8c1f-7efbe52fbd59': RoObject(
host='pool_host_1',
host='pool_host_1.example.',
port=123
),
'pool_nameserver:269ca3fc-5924-4a44-8c1f-7efbe52fbd59': RoObject(
host='pool_host_2',
host='pool_host_2.example.',
port=456
),
'pool_target:1588652b-50e7-46b9-b688-a9bad40a873e': RoObject(
......@@ -91,10 +91,10 @@ class poolTest(oslotest.base.BaseTestCase):
[('host', '2.0.0.0'), ('port', 2)]]),
('description', 'Pool built from configuration on foohost'), # noqa