class TsigKey(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): def __init__(self, *args, **kwargs): super(TsigKey, self).__init__(*args, **kwargs) fields = { 'name': fields.StringFields(nullable=False, maxLength=160), 'algorithm': fields.EnumField(nullable=False, valid_values=[ 'hmac-md5', 'hmac-sha1', 'hmac-sha224', 'hmac-sha256', 'hmac-sha384', 'hmac-sha512' ] ), 'secret': fields.StringFields(maxLength=160), 'scope': fields.EnumField(nullable=False, valid_values=['POOL', 'ZONE'] ), 'resource_id': fields.UUIDFields(nullable=False) } STRING_KEYS = [ 'id', 'name', 'algorithm', 'scope', 'resource_id' ]
class FloatingIP(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): fields = { "address": fields.IPV4AddressField(nullable=True), "description": fields.StringFields(nullable=True, maxLength=160), "ptrdname": fields.DomainField(nullable=True), "ttl": fields.IntegerFields(nullable=True, minimum=1, maximum=2147483647), "region": fields.StringFields(nullable=True), "action": fields.EnumField(['CREATE', 'DELETE', 'UPDATE', 'NONE'], nullable=True), "status": fields.EnumField(['ACTIVE', 'PENDING', 'ERROR'], nullable=True) } STRING_KEYS = ['key', 'address', 'ptrdname'] @property def key(self): return '%s:%s' % (self.region, self.id)
class ZoneTransferRequest( base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject, ): fields = { 'key': fields.StringFields(nullable=True, maxLength=160), 'zone_id': fields.UUIDFields(nullable=True), 'description': fields.StringFields(nullable=True, maxLength=160), 'tenant_id': fields.StringFields(nullable=True), 'target_tenant_id': fields.StringFields(nullable=True), 'status': fields.EnumField( nullable=True, valid_values=["ACTIVE", "PENDING", "DELETED", "ERROR", "COMPLETE"]), 'zone_name': fields.StringFields(nullable=True, maxLength=255), } STRING_KEYS = ['id', 'zone_id', 'zone_name', 'target_tenant_id']
class Blacklist(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): fields = { 'pattern': fields.StringFields(maxLength=255), 'description': fields.StringFields(maxLength=160, nullable=True), } STRING_KEYS = ['id', 'pattern']
class ZoneAttribute(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): fields = { 'zone_id': fields.UUIDFields(nullable=True), 'key': fields.StringFields(maxLength=50, nullable=False), 'value': fields.StringFields(maxLength=50, nullable=False) } STRING_KEYS = ['id', 'key', 'value', 'zone_id']
class PoolAttribute(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): fields = { 'pool_id': fields.UUIDFields(nullable=True), 'key': fields.StringFields(maxLength=50), 'value': fields.StringFields(maxLength=50) } STRING_KEYS = ['id', 'key', 'value', 'pool_id']
class Record(base.DesignateObject, base.PersistentObjectMixin, base.DictObjectMixin): def __init__(self, *args, **kwargs): super(Record, self).__init__(*args, **kwargs) fields = { 'shard': fields.IntegerFields(nullable=True, minimum=0, maximum=4095), 'data': fields.AnyField(nullable=True), 'zone_id': fields.UUIDFields(nullable=True), 'managed': fields.BooleanField(nullable=True), 'managed_resource_type': fields.StringFields(nullable=True, maxLength=160), 'managed_resource_id': fields.UUIDFields(nullable=True), 'managed_plugin_name': fields.StringFields(nullable=True, maxLength=160), 'managed_plugin_type': fields.StringFields(nullable=True, maxLength=160), 'hash': fields.StringFields(nullable=True, maxLength=32), 'description': fields.StringFields(nullable=True, maxLength=160), 'status': fields.EnumField( valid_values=['ACTIVE', 'PENDING', 'ERROR', 'DELETED'], nullable=True), 'tenant_id': fields.StringFields(nullable=True), 'recordset_id': fields.UUIDFields(nullable=True), 'managed_tenant_id': fields.StringFields(nullable=True), 'managed_resource_region': fields.StringFields(nullable=True, maxLength=160), 'managed_extra': fields.StringFields(nullable=True, maxLength=160), 'action': fields.EnumField(valid_values=['CREATE', 'DELETE', 'UPDATE', 'NONE'], nullable=True), 'serial': fields.IntegerFields(nullable=True, minimum=1, maximum=4294967295), } @classmethod def get_recordset_schema_changes(cls): # This is to allow record types to override the validation on a # recordset return {} STRING_KEYS = ['id', 'recordset_id', 'data'] def __str__(self): record = self.to_dict() record['data'] = record['data'][:35] return (self._make_obj_str(self.STRING_KEYS) % record)
class SPF(Record): """ SPF Resource Record Type Defined in: RFC4408 """ fields = {'txt_data': fields.StringFields()} def _to_string(self): return self.txt_data def _from_string(self, value): if (not value.startswith('"') and not value.endswith('"')): # value with spaces should be quoted as per RFC1035 5.1 for element in value: if element.isspace(): err = ("Empty spaces are not allowed in SPF record, " "unless wrapped in double quotes.") raise InvalidObject(err) else: # quotes within value should be escaped with backslash strip_value = value.strip('"') for index, char in enumerate(strip_value): if char == '"': if strip_value[index - 1] != "\\": err = ("Quotation marks should be escaped with " "backslash.") raise InvalidObject(err) self.txt_data = value # The record type is defined in the RFC. This will be used when the record # is sent by mini-dns. RECORD_TYPE = 99
class MX(Record): """ MX Resource Record Type Defined in: RFC1035 """ fields = { 'priority': fields.IntegerFields(minimum=0, maximum=65535), 'exchange': fields.StringFields(maxLength=255), } def _to_string(self): return '%(priority)s %(exchange)s' % self def _from_string(self, value): priority, exchange = value.split(' ') if repr(int(priority)) != priority: raise ValueError('Value is not an integer') self.priority = int(priority) self.exchange = exchange # The record type is defined in the RFC. This will be used when the record # is sent by mini-dns. RECORD_TYPE = 15
class RBACBaseObject(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): # not to confuse action field as all other Designate Objects # rbac_action is intended for allowed/denied actions # such as access_as_shared fields = { 'id': fields.UUIDFields(nullable=False), 'project_id': fields.UUIDFields(nullable=False), 'object_id': fields.UUIDFields(nullable=True), 'target_tenant': fields.UUIDFields(nullable=True), 'rbac_action': fields.StringFields(maxLength=255), } fields_no_update = ['id', 'project_id', 'object_id'] STRING_KEYS = [ 'id', 'project_id', 'object_id', 'target_tenant', 'rbac_action' ] @classmethod def get_projects(cls, context, object_id=None, action=None, target_tenant=None): ## TODO: update this method as it was done with: ## https://github.com/openstack/neutron/blob/master/neutron/objects/rbac.py#L44 return None
class ZoneImport(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): fields = { 'status': fields.EnumField( nullable=True, valid_values=["ACTIVE", "PENDING", "DELETED", "ERROR", "COMPLETE"]), 'task_type': fields.EnumField(nullable=True, valid_values=["IMPORT"]), 'tenant_id': fields.StringFields(nullable=True), 'message': fields.StringFields(nullable=True, maxLength=160), 'zone_id': fields.UUIDFields(nullable=True) }
class PoolTargetOption(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): fields = { 'pool_target_id': fields.UUIDFields(nullable=True), 'key': fields.StringFields(maxLength=255), 'value': fields.AnyField(), } STRING_KEYS = ['id', 'key', 'value', 'pool_target_id']
class SoftDeleteObjectMixin(object): """ Mixin class for Soft-Deleted objects. This adds the fields that we use in common for all soft-deleted objects. """ fields = { 'deleted': fields.StringFields(nullable=True), 'deleted_at': fields.DateTimeField(nullable=True), }
class ZoneTransferAccept(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): fields = { 'zone_transfer_request_id': fields.UUIDFields(nullable=True), 'tenant_id': fields.StringFields(nullable=True), 'status': fields.EnumField( nullable=True, valid_values=["ACTIVE", "PENDING", "DELETED", "ERROR", "COMPLETE"]), 'key': fields.StringFields(maxLength=160), 'zone_id': fields.UUIDFields(nullable=True), } STRING_KEYS = ['id', 'zone_id', 'tenant_id', 'zone_transfer_request_id']
class Tld(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): def __init__(self, *args, **kwargs): super(Tld, self).__init__(*args, **kwargs) fields = { 'name': fields.TldField(maxLength=255), 'description': fields.StringFields(nullable=True, maxLength=160) } STRING_KEYS = ['id', 'name']
class ServiceStatus(base.DesignateObject, base.DictObjectMixin, base.PersistentObjectMixin): def __init__(self, *args, **kwargs): super(ServiceStatus, self).__init__(*args, **kwargs) fields = { "service_name": fields.StringFields(), "hostname": fields.StringFields(nullable=True), "heartbeated_at": fields.DateTimeField(nullable=True), "status": fields.EnumField(nullable=True, valid_values=[ "UP", "DOWN", "WARNING" ]), "stats": fields.BaseObjectField(nullable=True), "capabilities": fields.BaseObjectField(nullable=True), } STRING_KEYS = [ 'service_name', 'hostname', 'status' ]
class PoolTarget(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): 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 = ['id', 'type', 'pool_id']
class SPF(Record): """ SPF Resource Record Type Defined in: RFC4408 """ fields = {'txt_data': fields.StringFields()} def _to_string(self): return self.txt_data def _from_string(self, value): self.txt_data = value # The record type is defined in the RFC. This will be used when the record # is sent by mini-dns. RECORD_TYPE = 99
class ZoneMaster(base.DesignateObject, base.DictObjectMixin, base.PersistentObjectMixin, base.SoftDeleteObjectMixin): def __init__(self, *args, **kwargs): super(ZoneMaster, self).__init__(*args, **kwargs) fields = { 'zone_id': fields.UUIDFields(nullable=True), 'host': fields.StringFields(), 'port': fields.IntegerFields(minimum=1, maximum=65535) } def to_data(self): return "{}:{}".format(self.host, self.port) @classmethod def from_data(cls, data): host, port = utils.split_host_port(data) dict_data = {"host": host, "port": port} return cls(**dict_data)
class RecordSet(base.DesignateObject, base.DictObjectMixin, base.PersistentObjectMixin): def __init__(self, *args, **kwargs): super(RecordSet, self).__init__(*args, **kwargs) @property def action(self): # Return action as UPDATE if present. CREATE and DELETE are returned # if they are the only ones. action = 'NONE' actions = {'CREATE': 0, 'DELETE': 0, 'UPDATE': 0, 'NONE': 0} for record in self.records: actions[record.action] += 1 if actions['CREATE'] != 0 and actions['UPDATE'] == 0 and \ actions['DELETE'] == 0 and actions['NONE'] == 0: # noqa action = 'CREATE' elif actions['DELETE'] != 0 and actions['UPDATE'] == 0 and \ actions['CREATE'] == 0 and actions['NONE'] == 0: # noqa action = 'DELETE' elif actions['UPDATE'] != 0 or actions['CREATE'] != 0 or \ actions['DELETE'] != 0: # noqa action = 'UPDATE' return action @property def managed(self): managed = False for record in self.records: if record.managed: return True return managed @property def status(self): # Return the worst status in order of ERROR, PENDING, ACTIVE, DELETED. status = None statuses = { 'ERROR': 0, 'PENDING': 1, 'ACTIVE': 2, 'DELETED': 3, } for record in self.records: if not status or statuses[record.status] < statuses[status]: status = record.status return status or 'ACTIVE' fields = { 'shard': fields.IntegerFields(nullable=True, minimum=0, maximum=4095), 'tenant_id': fields.StringFields(nullable=True, read_only=True), 'zone_id': fields.UUIDFields(nullable=True, read_only=True), 'zone_name': fields.DomainField(nullable=True, maxLength=255), 'name': fields.HostField(maxLength=255, nullable=True), 'type': fields.StringFields(nullable=True, read_only=True), 'ttl': fields.IntegerFields(nullable=True, minimum=1, maximum=2147483647), 'description': fields.StringFields(nullable=True, maxLength=160), 'records': fields.PolymorphicObjectField('RecordList', nullable=True), } def _validate_fail(self, errors, msg): e = ValidationError() e.path = ['recordset', 'type'] e.validator = 'value' e.validator_value = [self.type] e.message = msg # Add it to the list for later errors.append(e) raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=self) def validate(self): LOG.debug("Validating '%(name)s' object with values: %(values)r", { 'name': self.obj_name(), 'values': self.to_dict(), }) LOG.debug(list(self.records)) errors = ValidationErrorList() # Get the right classes (e.g. A for Recordsets with type: 'A') try: record_list_cls = self.obj_cls_from_name('%sList' % self.type) record_cls = self.obj_cls_from_name(self.type) except (KeyError, ovo_exc.UnsupportedObjectError) as e: err_msg = ("'%(type)s' is not a valid record type" % { 'type': self.type }) self._validate_fail(errors, err_msg) if self.type not in cfg.CONF.supported_record_type: err_msg = ("'%(type)s' is not a supported record type" % { 'type': self.type }) self._validate_fail(errors, err_msg) # Get any rules that the record type imposes on the record changes = record_cls.get_recordset_schema_changes() old_fields = {} if changes: LOG.debug("Record %s is overriding the RecordSet schema with: %s", record_cls.obj_name(), changes) old_fields = deepcopy(self.FIELDS) self.FIELDS = utils.deep_dict_merge(self.FIELDS, changes) error_indexes = [] # Copy these for safekeeping old_records = deepcopy(self.records) # Blank the records for this object with the right list type self.records = record_list_cls() i = 0 for record in old_records: record_obj = record_cls() try: record_obj._from_string(record.data) # The _from_string() method will throw a ValueError if there is not # enough data blobs except ValueError as e: # Something broke in the _from_string() method # Fake a correct looking ValidationError() object e = ValidationError() e.path = ['records', i] e.validator = 'format' e.validator_value = [self.type] e.message = ("'%(data)s' is not a '%(type)s' Record" % { 'data': record.data, 'type': self.type }) # Add it to the list for later errors.append(e) error_indexes.append(i) except TypeError as e: e = ValidationError() e.path = ['records', i] e.validator = 'format' e.validator_value = [self.type] e.message = ("'%(data)s' is not a '%(type)s' Record" % { 'data': record.data, 'type': self.type }) # Add it to the list for later errors.append(e) error_indexes.append(i) except AttributeError as e: e = ValidationError() e.path = ['records', i] e.validator = 'format' e.validator_value = [self.type] e.message = ("'%(data)s' is not a '%(type)s' Record" % { 'data': record.data, 'type': self.type }) # Add it to the list for later errors.append(e) error_indexes.append(i) except Exception as e: error_message = ('Provided object is not valid. Got a %s error' ' with message %s' % (type(e).__name__, six.text_type(e))) raise exceptions.InvalidObject(error_message) else: # Seems to have loaded right - add it to be validated by # JSONSchema self.records.append(record_obj) i += 1 try: # Run the actual validate code super(RecordSet, self).validate() except exceptions.InvalidObject as e: raise e else: # If JSONSchema passes, but we found parsing errors, # raise an exception if len(errors) > 0: LOG.debug( "Error Validating '%(name)s' object with values: " "%(values)r", { 'name': self.obj_name(), 'values': self.to_dict(), }) raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=self) finally: if old_fields: self.FIELDS = old_fields # Send in the traditional Record objects to central / storage self.records = old_records STRING_KEYS = ['id', 'type', 'name', 'zone_id']
class Zone(base.DesignateObject, base.DictObjectMixin, base.PersistentObjectMixin, base.SoftDeleteObjectMixin): def __init__(self, *args, **kwargs): super(Zone, self).__init__(*args, **kwargs) fields = { 'shard': fields.IntegerFields(nullable=True, minimum=0, maximum=4095), 'tenant_id': fields.StringFields(nullable=True, read_only=False), 'name': fields.DomainField(maxLength=255), 'email': fields.EmailField(maxLength=255, nullable=True), 'ttl': fields.IntegerFields(nullable=True, minimum=1, maximum=2147483647), 'refresh': fields.IntegerFields(nullable=True, minimum=0, maximum=2147483647, read_only=False), 'retry': fields.IntegerFields(nullable=True, minimum=0, maximum=2147483647, read_only=False), 'expire': fields.IntegerFields(nullable=True, minimum=0, maximum=2147483647, read_only=False), 'minimum': fields.IntegerFields(nullable=True, minimum=0, maximum=2147483647, read_only=False), 'parent_zone_id': fields.UUIDFields(nullable=True, read_only=False), 'serial': fields.IntegerFields(nullable=True, minimum=0, maximum=4294967295, read_only=False), 'description': fields.StringFields(nullable=True, maxLength=160), 'status': fields.EnumField(nullable=True, read_only=False, valid_values=[ 'ACTIVE', 'PENDING', 'ERROR', 'DELETED', 'SUCCESS', 'NO_ZONE'] ), 'action': fields.EnumField(nullable=True, valid_values=[ 'CREATE', 'DELETE', 'UPDATE', 'NONE'] ), 'pool_id': fields.UUIDFields(nullable=True, read_only=False), 'recordsets': fields.ObjectField('RecordSetList', nullable=True), 'attributes': fields.ObjectField('ZoneAttributeList', nullable=True), 'masters': fields.ObjectField('ZoneMasterList', nullable=True), 'type': fields.EnumField(nullable=True, valid_values=['SECONDARY', 'PRIMARY'], read_only=False ), 'transferred_at': fields.DateTimeField(nullable=True, read_only=False), 'delayed_notify': fields.BooleanField(nullable=True), } STRING_KEYS = [ 'id', 'type', 'name', 'pool_id', 'serial', 'action', 'status' ] def get_master_by_ip(self, host): """ Utility to get the master by it's ip for this zone. """ for srv in self.masters: srv_host, _ = utils.split_host_port(srv.to_data()) if host == srv_host: return srv return False def _raise(self, errors): if len(errors) != 0: raise exceptions.InvalidObject( "Provided object does not match " "schema", errors=errors, object=self) def __hash__(self): return hash(self.id) def validate(self): errors = ValidationErrorList() if self.type == 'PRIMARY': if self.obj_attr_is_set('masters') and len(self.masters) != 0: e = ValidationError() e.path = ['type'] e.validator = 'maxItems' e.validator_value = ['masters'] e.message = "'masters' has more items than allowed" errors.append(e) if self.email is None: e = ValidationError() e.path = ['type'] e.validator = 'required' e.validator_value = 'email' e.message = "'email' is a required property" errors.append(e) self._raise(errors) try: if self.type == 'SECONDARY': if self.masters is None or len(self.masters) == 0: e = ValidationError() e.path = ['type'] e.validator = 'required' e.validator_value = ['masters'] e.message = "'masters' is a required property" errors.append(e) for i in ['email', 'ttl']: if i in self.obj_what_changed(): e = ValidationError() e.path = ['type'] e.validator = 'not_allowed' e.validator_value = i e.message = "'%s' can't be specified when type is " \ "SECONDARY" % i errors.append(e) self._raise(errors) super(Zone, self).validate() except exceptions.RelationNotLoaded as ex: errors = ValidationErrorList() e = ValidationError() e.path = ['type'] e.validator = 'required' e.validator_value = [ex.relation] e.message = "'%s' is a required property" % ex.relation errors.append(e) self._raise(errors)
class Pool(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): 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 def from_config(cls, CONF, pool_id): pool_target_ids = CONF['pool:%s' % pool_id].targets pool_nameserver_ids = CONF['pool:%s' % pool_id].nameservers pool_also_notifies = CONF['pool:%s' % pool_id].also_notifies # Build Base Pool pool = { 'id': pool_id, 'description': 'Pool built from configuration on %s' % CONF.host, 'targets': [], 'nameservers': [], 'also_notifies': [], } # Build Pool Also Notifies for pool_also_notify in pool_also_notifies: host, port = utils.split_host_port(pool_also_notify) pool['also_notifies'].append({ 'host': host, 'port': port, }) # Build Pool Targets for pool_target_id in pool_target_ids: pool_target_group = 'pool_target:%s' % pool_target_id pool_target = { 'id': pool_target_id, 'type': CONF[pool_target_group].type, 'masters': [], 'options': [], } # Build Pool Target Masters for pool_target_master in CONF[pool_target_group].masters: host, port = utils.split_host_port(pool_target_master) pool_target['masters'].append({ 'host': host, 'port': port, }) # Build Pool Target Options for k, v in CONF[pool_target_group].options.items(): pool_target['options'].append({ 'key': k, 'value': v, }) pool['targets'].append(pool_target) # Build Pool Nameservers for pool_nameserver_id in pool_nameserver_ids: pool_nameserver_group = 'pool_nameserver:%s' % pool_nameserver_id pool_nameserver = { 'id': pool_nameserver_id, 'host': CONF[pool_nameserver_group].host, 'port': CONF[pool_nameserver_group].port, } pool['nameservers'].append(pool_nameserver) return cls.from_dict(pool) STRING_KEYS = [ 'id', 'name' ]