diff --git a/designate/central/rpcapi.py b/designate/central/rpcapi.py index f287f959..7d17ea4a 100644 --- a/designate/central/rpcapi.py +++ b/designate/central/rpcapi.py @@ -42,14 +42,15 @@ class CentralAPI(object): 3.3 - Add methods for blacklisted domains 4.0 - Create methods now accept designate objects 4.1 - Add methods for server pools + 4.2 - Add methods for pool manager integration """ - RPC_API_VERSION = '4.1' + RPC_API_VERSION = '4.2' def __init__(self, topic=None): topic = topic if topic else cfg.CONF.central_topic target = messaging.Target(topic=topic, version=self.RPC_API_VERSION) - self.client = rpc.get_client(target, version_cap='4.1') + self.client = rpc.get_client(target, version_cap='4.2') # Misc Methods def get_absolute_limits(self, context): @@ -376,8 +377,7 @@ class CentralAPI(object): def create_pool(self, context, pool): LOG.info(_LI("create_pool: Calling central's create_pool.")) cctxt = self.client.prepare(version='4.1') - return cctxt.call(context, 'create_pool', - pool=pool) + return cctxt.call(context, 'create_pool', pool=pool) def find_pools(self, context, criterion=None, marker=None, limit=None, sort_key=None, sort_dir=None): @@ -405,4 +405,11 @@ class CentralAPI(object): def delete_pool(self, context, pool_id): LOG.info(_LI("delete_pool: Calling central's delete_pool.")) cctxt = self.client.prepare(version='4.1') - return cctxt.call(context, 'delete_pool', pool_id=pool_id) \ No newline at end of file + return cctxt.call(context, 'delete_pool', pool_id=pool_id) + + # Pool Manager Integration Methods + def update_status(self, context, domain_id, status, serial): + LOG.info(_LI("update_status: Calling central's update_status.")) + cctxt = self.client.prepare(version='4.2') + return cctxt.call(context, 'update_status', domain_id=domain_id, + status=status, serial=serial) diff --git a/designate/central/service.py b/designate/central/service.py index e92a986c..88ad7d52 100644 --- a/designate/central/service.py +++ b/designate/central/service.py @@ -70,7 +70,7 @@ def transaction(f): class Service(service.RPCService): - RPC_API_VERSION = '4.1' + RPC_API_VERSION = '4.2' target = messaging.Target(version=RPC_API_VERSION) @@ -391,6 +391,7 @@ class Service(service.RPCService): 'type': "NS"}) # Add new record to recordset ns_record = objects.Record(data=server.name) + new_record = self.create_record(context, zone['id'], ns['id'], ns_record, increment_serial=False) @@ -741,6 +742,12 @@ class Service(service.RPCService): 'Please create at least one server')) raise exceptions.NoServersConfigured() + # TODO(Ron): remove this when integrated with pool manager. + # The default status is 'PENDING' for pool manager. + # Setting status to 'ACTIVE' for backward compatibility. + if cfg.CONF['service:central'].backend_driver != 'pool_manager_proxy': + domain.status = 'ACTIVE' + # Set the serial number domain.serial = utils.increment_serial() @@ -1146,6 +1153,12 @@ class Service(service.RPCService): # Ensure the tenant has enough quota to continue self._enforce_record_quota(context, domain, recordset) + # TODO(Ron): remove this when integrated with pool_manager. + # The default status is 'PENDING' for pool manager. + # Setting status to 'ACTIVE' for backward compatibility. + if cfg.CONF['service:central'].backend_driver != 'pool_manager_proxy': + record.status = 'ACTIVE' + created_record = self.storage.create_record(context, domain_id, recordset_id, record) @@ -1796,3 +1809,51 @@ class Service(service.RPCService): pool = self.storage.delete_pool(context, pool_id) self.notifier.info(context, 'dns.pool.delete', pool) + + # Pool Manager Integration + def update_status(self, context, domain_id, status, serial): + """ + :param context: Security context information. + :param domain_id: The ID of the designate domain. + :param status: The status, 'SUCCESS' or 'ERROR'. + :param serial: The consensus serial number for the domain. + :return: None + """ + domain = self.storage.get_domain(context, domain_id) + criterion = { + 'domain_id': domain_id + } + records = self.storage.find_records(context, criterion=criterion) + + if status == 'SUCCESS': + if domain.action in ['CREATE', 'UPDATE'] \ + and domain.status in ['PENDING', 'ERROR']: + domain.action = 'NONE' + domain.status = 'ACTIVE' + elif domain.action == 'DELETE' \ + and domain.status in ['PENDING', 'ERROR']: + domain.action = 'NONE' + domain.status = 'DELETED' + for record in records: + if record.action in ['CREATE', 'UPDATE'] \ + and record.status in ['PENDING', 'ERROR'] \ + and serial >= record.serial: + record.action = 'NONE' + record.status = 'ACTIVE' + elif record.action == 'DELETE' \ + and record.status in ['PENDING', 'ERROR'] \ + and serial >= record.serial: + record.action = 'NONE' + record.status = 'DELETED' + self.storage.update_record(context, record) + + elif status == 'ERROR': + if domain.status == 'PENDING': + domain.status = 'ERROR' + for record in records: + if record.status == 'PENDING' \ + and serial >= record.serial: + record.status = 'ERROR' + self.storage.update_record(context, record) + + self.storage.update_domain(context, domain) diff --git a/designate/objects/domain.py b/designate/objects/domain.py index 15ff58bd..9bae0bdb 100644 --- a/designate/objects/domain.py +++ b/designate/objects/domain.py @@ -29,7 +29,8 @@ class Domain(base.DictObjectMixin, base.SoftDeleteObjectMixin, 'parent_domain_id': {}, 'serial': {}, 'description': {}, - 'status': {} + 'status': {}, + 'action': {} } diff --git a/designate/objects/record.py b/designate/objects/record.py index 7cdb4b1a..45319338 100644 --- a/designate/objects/record.py +++ b/designate/objects/record.py @@ -34,7 +34,9 @@ class Record(base.DictObjectMixin, base.PersistentObjectMixin, 'recordset_id': {}, 'managed_tenant_id': {}, 'managed_resource_region': {}, - 'managed_extra': {} + 'managed_extra': {}, + 'action': {}, + 'serial': {} } diff --git a/designate/storage/impl_sqlalchemy/migrate_repo/versions/043_modify_domains_and_records.py b/designate/storage/impl_sqlalchemy/migrate_repo/versions/043_modify_domains_and_records.py new file mode 100644 index 00000000..11f06f9d --- /dev/null +++ b/designate/storage/impl_sqlalchemy/migrate_repo/versions/043_modify_domains_and_records.py @@ -0,0 +1,100 @@ +# Copyright (c) 2014 eBay Inc. +# +# Author: Ron Rickard +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 sqlalchemy import MetaData, Table, Enum, Column, Integer +from migrate.changeset.constraint import UniqueConstraint + +meta = MetaData() + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + + RESOURCE_STATUSES = ['ACTIVE', 'PENDING', 'DELETED', 'ERROR'] + ACTIONS = ['CREATE', 'DELETE', 'UPDATE', 'NONE'] + + # Get associated database tables + domains_table = Table('domains', meta, autoload=True) + records_table = Table('records', meta, autoload=True) + + # Upgrade the domains table. + domains_table.c.status.alter( + type=Enum(name='resource_statuses', *RESOURCE_STATUSES), + default='PENDING', server_default='PENDING') + action_column = Column('action', Enum(name='actions', *ACTIONS), + default='CREATE', server_default='CREATE', + nullable=False) + action_column.create(domains_table) + + # Re-add constraint for sqlite. + dialect = migrate_engine.url.get_dialect().name + if dialect.startswith('sqlite'): + constraint = UniqueConstraint( + 'name', 'deleted', name='unique_domain_name', table=domains_table) + constraint.create() + + # Upgrade the records table. + records_table.c.status.alter( + type=Enum(name='resource_statuses', *RESOURCE_STATUSES), + default='PENDING', server_default='PENDING') + action_column = Column('action', Enum(name='actions', *ACTIONS), + default='CREATE', server_default='CREATE', + nullable=False) + action_column.create(records_table) + serial_column = Column('serial', Integer(), server_default='1', + nullable=False) + serial_column.create(records_table) + + # Re-add constraint for sqlite. + if dialect.startswith('sqlite'): + constraint = UniqueConstraint( + 'hash', name='unique_record', table=records_table) + constraint.create() + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + RESOURCE_STATUSES = ['ACTIVE', 'PENDING', 'DELETED'] + + # Get associated database tables + domains_table = Table('domains', meta, autoload=True) + records_table = Table('records', meta, autoload=True) + + # Downgrade the domains table. + domains_table.c.status.alter( + type=Enum(name='resource_statuses', *RESOURCE_STATUSES), + default='ACTIVE', server_default='ACTIVE') + domains_table.c.action.drop() + + # Re-add constraint for sqlite. + dialect = migrate_engine.url.get_dialect().name + if dialect.startswith('sqlite'): + constraint = UniqueConstraint( + 'name', 'deleted', name='unique_domain_name', table=domains_table) + constraint.create() + + # Downgrade the records table. + records_table.c.status.alter( + type=Enum(name='resource_statuses', *RESOURCE_STATUSES), + default='ACTIVE', server_default='ACTIVE') + records_table.c.action.drop() + records_table.c.serial.drop() + + # Re-add constraint for sqlite. + if dialect.startswith('sqlite'): + constraint = UniqueConstraint( + 'hash', name='unique_record', table=records_table) + constraint.create() diff --git a/designate/storage/impl_sqlalchemy/tables.py b/designate/storage/impl_sqlalchemy/tables.py index c2d27bb3..9da6890a 100644 --- a/designate/storage/impl_sqlalchemy/tables.py +++ b/designate/storage/impl_sqlalchemy/tables.py @@ -26,13 +26,13 @@ from designate.sqlalchemy.types import UUID CONF = cfg.CONF -RESOURCE_STATUSES = ['ACTIVE', 'PENDING', 'DELETED'] +RESOURCE_STATUSES = ['ACTIVE', 'PENDING', 'DELETED', 'ERROR'] RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT', 'SPF', 'NS', 'PTR', 'SSHFP'] TSIG_ALGORITHMS = ['hmac-md5', 'hmac-sha1', 'hmac-sha224', 'hmac-sha256', 'hmac-sha384', 'hmac-sha512'] - POOL_PROVISIONERS = ['UNMANAGED'] +ACTIONS = ['ADD', 'DELETE', 'UPDATE', 'NONE'] metadata = MetaData() @@ -98,8 +98,10 @@ domains = Table('domains', metadata, Column('minimum', Integer, default=CONF.default_soa_minimum, nullable=False), Column('status', Enum(name='resource_statuses', *RESOURCE_STATUSES), - nullable=False, server_default='ACTIVE', default='ACTIVE'), + nullable=False, server_default='PENDING', default='PENDING'), Column('parent_domain_id', UUID, default=None, nullable=True), + Column('action', Enum(name='actions', *ACTIONS), + default='CREATE', server_default='CREATE', nullable=False), UniqueConstraint('name', 'deleted', name='unique_domain_name'), ForeignKeyConstraint(['parent_domain_id'], @@ -152,8 +154,12 @@ records = Table('records', metadata, Column('managed_resource_id', UUID, default=None, nullable=True), Column('managed_tenant_id', Unicode(36), default=None, nullable=True), Column('status', Enum(name='resource_statuses', *RESOURCE_STATUSES), - nullable=False, server_default='ACTIVE', default='ACTIVE'), + server_default='PENDING', default='PENDING', nullable=False), + Column('action', Enum(name='actions', *ACTIONS), + default='CREATE', server_default='CREATE', nullable=False), + Column('serial', Integer(), server_default='1', nullable=False), + UniqueConstraint('hash', name='unique_record'), ForeignKeyConstraint(['domain_id'], ['domains.id'], ondelete='CASCADE'), ForeignKeyConstraint(['recordset_id'], ['recordsets.id'], ondelete='CASCADE'),