diff --git a/etc/heat/heat.conf.sample b/etc/heat/heat.conf.sample index 69371bafc60..6ecc493e7a9 100644 --- a/etc/heat/heat.conf.sample +++ b/etc/heat/heat.conf.sample @@ -10,11 +10,6 @@ # Driver to use for controlling instances (string value) #instance_driver=heat.engine.nova -# Engine identifier for multi-engine distributed lock. If -# this is set to "generate_uuid", a UUID will be generated. -# (string value) -#engine_id=generate_uuid - # List of directories to search for Plugins (list value) #plugin_dirs=/usr/lib64/heat,/usr/lib/heat diff --git a/heat/api/aws/exception.py b/heat/api/aws/exception.py index 61f4afb4aeb..2269df43bec 100644 --- a/heat/api/aws/exception.py +++ b/heat/api/aws/exception.py @@ -247,16 +247,6 @@ class HeatAPINotImplementedError(HeatAPIException): err_type = "Server" -class HeatActionInProgressError(HeatAPIException): - ''' - Cannot perform action on stack in its current state - ''' - code = 400 - title = 'InvalidAction' - explanation = ("Cannot perform action on stack while other actions are " + - "in progress") - - def map_remote_error(ex): """ Map rpc_common.RemoteError exceptions returned by the engine @@ -281,7 +271,6 @@ def map_remote_error(ex): ) denied_errors = ('Forbidden', 'NotAuthorized') already_exists_errors = ('StackExists') - invalid_action_errors = ('ActionInProgress',) ex_type = ex.__class__.__name__ @@ -294,8 +283,6 @@ def map_remote_error(ex): return HeatAccessDeniedError(detail=str(ex)) elif ex_type in already_exists_errors: return AlreadyExistsError(detail=str(ex)) - elif ex_type in invalid_action_errors: - return HeatActionInProgressError(detail=str(ex)) else: # Map everything else to internal server error for now return HeatInternalFailureError(detail=str(ex)) diff --git a/heat/api/middleware/fault.py b/heat/api/middleware/fault.py index 367444bfa32..730282a2582 100644 --- a/heat/api/middleware/fault.py +++ b/heat/api/middleware/fault.py @@ -58,7 +58,6 @@ class FaultWrapper(wsgi.Middleware): error_map = { 'AttributeError': webob.exc.HTTPBadRequest, - 'ActionInProgress': webob.exc.HTTPConflict, 'ValueError': webob.exc.HTTPBadRequest, 'StackNotFound': webob.exc.HTTPNotFound, 'ResourceNotFound': webob.exc.HTTPNotFound, diff --git a/heat/common/config.py b/heat/common/config.py index 916d072daef..a6dc4462e5e 100644 --- a/heat/common/config.py +++ b/heat/common/config.py @@ -75,11 +75,6 @@ cfg.StrOpt('instance_driver', default='heat.engine.nova', help='Driver to use for controlling instances'), - cfg.StrOpt('engine_id', - default="generate_uuid", - help=_('Engine identifier for multi-engine distributed lock.' - ' If this is set to "generate_uuid", a UUID will be' - ' generated.')), cfg.ListOpt('plugin_dirs', default=['/usr/lib64/heat', '/usr/lib/heat'], help='List of directories to search for Plugins'), diff --git a/heat/common/exception.py b/heat/common/exception.py index 24e8a4a37cc..54d3e121e0d 100644 --- a/heat/common/exception.py +++ b/heat/common/exception.py @@ -334,8 +334,3 @@ class RequestLimitExceeded(HeatException): class StackResourceLimitExceeded(HeatException): msg_fmt = _('Maximum resources per stack exceeded.') - - -class ActionInProgress(HeatException): - msg_fmt = _("Stack %(stack_name)s already has an action (%(action)s) " - "in progress.") diff --git a/heat/db/api.py b/heat/db/api.py index 77ed724aa8d..0fb3d4ec61a 100644 --- a/heat/db/api.py +++ b/heat/db/api.py @@ -139,22 +139,6 @@ def stack_delete(context, stack_id): return IMPL.stack_delete(context, stack_id) -def stack_lock_get(context, stack_id): - return IMPL.stack_lock_get(context, stack_id) - - -def stack_lock_create(context, stack_id, engine_id): - return IMPL.stack_lock_create(context, stack_id, engine_id) - - -def stack_lock_steal(context, stack_id, engine_id): - return IMPL.stack_lock_steal(context, stack_id, engine_id) - - -def stack_lock_release(context, stack_id): - return IMPL.stack_lock_release(context, stack_id) - - def user_creds_create(context): return IMPL.user_creds_create(context) @@ -231,7 +215,3 @@ def db_sync(version=None): def db_version(): """Display the current database version.""" return IMPL.db_version() - - -def current_timestamp(): - return IMPL.current_timestamp() diff --git a/heat/db/sqlalchemy/api.py b/heat/db/sqlalchemy/api.py index 235f809e368..8150b5ab90c 100644 --- a/heat/db/sqlalchemy/api.py +++ b/heat/db/sqlalchemy/api.py @@ -297,28 +297,6 @@ def stack_delete(context, stack_id): session.flush() -def stack_lock_get(context, stack_id): - return model_query(context, models.StackLock).get(stack_id) - - -def stack_lock_create(context, stack_id, engine_id): - stack_lock = models.StackLock() - stack_lock.update({"stack_id": stack_id, - "engine_id": engine_id}) - stack_lock.save(_session(context)) - - -def stack_lock_steal(context, stack_id, engine_id): - stack_lock = stack_lock_get(context, stack_id) - stack_lock.update({"engine_id": engine_id}) - stack_lock.save(_session(context)) - - -def stack_lock_release(context, stack_id): - stack_lock = stack_lock_get(context, stack_id) - stack_lock.delete() - - def user_creds_create(context): values = context.to_dict() user_creds_ref = models.UserCreds() @@ -543,11 +521,3 @@ def db_sync(version=None): def db_version(): """Display the current database version.""" return migration.db_version() - - -def current_timestamp(): - """Return a datetime object with the current database time.""" - session = get_session() - query = sqlalchemy.select([sqlalchemy.func.current_timestamp()]) - result = session.execute(query).fetchall() - return result[0][0] diff --git a/heat/db/sqlalchemy/migrate_repo/versions/029_stack_lock.py b/heat/db/sqlalchemy/migrate_repo/versions/029_stack_lock.py deleted file mode 100644 index 7c3eb80976c..00000000000 --- a/heat/db/sqlalchemy/migrate_repo/versions/029_stack_lock.py +++ /dev/null @@ -1,40 +0,0 @@ - -# 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. - -import sqlalchemy - - -def upgrade(migrate_engine): - meta = sqlalchemy.MetaData() - meta.bind = migrate_engine - - stack_lock = sqlalchemy.Table( - 'stack_lock', meta, - sqlalchemy.Column('stack_id', sqlalchemy.String(length=36), - sqlalchemy.ForeignKey('stack.id'), - primary_key=True, - nullable=False), - sqlalchemy.Column('created_at', sqlalchemy.DateTime), - sqlalchemy.Column('updated_at', sqlalchemy.DateTime), - sqlalchemy.Column('engine_id', sqlalchemy.String(length=64)) - ) - sqlalchemy.Table('stack', meta, autoload=True) - stack_lock.create() - - -def downgrade(migrate_engine): - meta = sqlalchemy.MetaData() - meta.bind = migrate_engine - - stack_lock = sqlalchemy.Table('stack_lock', meta, autoload=True) - stack_lock.drop() diff --git a/heat/db/sqlalchemy/models.py b/heat/db/sqlalchemy/models.py index 2ea8cae28d8..e7de94b985e 100644 --- a/heat/db/sqlalchemy/models.py +++ b/heat/db/sqlalchemy/models.py @@ -143,17 +143,6 @@ class Stack(BASE, HeatBase, SoftDelete): disable_rollback = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False) -class StackLock(BASE, HeatBase): - """Store stack locks for deployments with multiple-engines.""" - - __tablename__ = 'stack_lock' - - stack_id = sqlalchemy.Column(sqlalchemy.String, - sqlalchemy.ForeignKey('stack.id'), - primary_key=True) - engine_id = sqlalchemy.Column(sqlalchemy.String) - - class UserCreds(BASE, HeatBase): """ Represents user credentials and mirrors the 'context' diff --git a/heat/engine/stack_lock.py b/heat/engine/stack_lock.py deleted file mode 100644 index ed6a6093317..00000000000 --- a/heat/engine/stack_lock.py +++ /dev/null @@ -1,89 +0,0 @@ - -# 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 oslo.config import cfg - -cfg.CONF.import_opt('engine_id', 'heat.common.config') - -from heat.common import exception -from heat.db import api as db_api - -from heat.openstack.common import log as logging -from heat.openstack.common import uuidutils - -logger = logging.getLogger(__name__) - - -class StackLock(object): - def __init__(self, context, stack): - self.context = context - self.stack = stack - if cfg.CONF.engine_id == "generate_uuid": - self.engine_id = uuidutils.generate_uuid() - else: - self.engine_id = cfg.CONF.engine_id - - @staticmethod - def _lock_staleness(lock): - """Returns number of seconds since stack was created or updated.""" - if lock.updated_at: - changed_time = lock.updated_at - else: - changed_time = lock.created_at - current_time = db_api.current_timestamp() - current_epoch = float(current_time.strftime('%s')) - changed_epoch = float(changed_time.strftime('%s')) - return current_epoch - changed_epoch - - @property - def timeout(self): - """Returns the stack timeout in seconds.""" - try: - return self.stack.timeout * 60 - except AttributeError: - return self.stack.timeout_secs() - - def acquire(self): - """Acquire a lock on the stack.""" - existing_lock = db_api.stack_lock_get(self.context, self.stack.id) - if existing_lock: - if self._lock_staleness(existing_lock) > self.timeout: - logger.info("Lock expired. Engine %s is stealing the lock" - % existing_lock.engine_id) - db_api.stack_lock_steal(self.context, self.stack.id, - self.engine_id) - else: - logger.debug("Stack lock is owned by engine %s" - % existing_lock.engine_id) - raise exception.ActionInProgress(stack_name=self.stack.name, - action=self.stack.action) - else: - db_api.stack_lock_create(self.context, self.stack.id, - self.engine_id) - logger.debug("Acquired lock for engine: %s, stack: %s, action: %s" - % (self.engine_id, self.stack.id, self.stack.action)) - - def release(self): - """Release a stack lock.""" - logger.debug("Releasing lock for engine: %s, stack: %s, action: %s" - % (self.engine_id, self.stack.id, self.stack.action)) - db_api.stack_lock_release(self.context, self.stack.id) - - def _gt_callback_release(self, gt, *args, **kwargs): - """Callback function that will be passed to GreenThread.link().""" - # If gt.wait() isn't called here and a lock exists, then the - # pending _gt_callback_release() from the previous acquire() - # will be executed immediately upon the next call to - # acquire(). This leads to a pre-mature release of the lock. - gt.wait() - self.release() diff --git a/heat/tests/test_sqlalchemy_api.py b/heat/tests/test_sqlalchemy_api.py index c750f534ea8..21ddfc2335e 100644 --- a/heat/tests/test_sqlalchemy_api.py +++ b/heat/tests/test_sqlalchemy_api.py @@ -17,7 +17,6 @@ from json import loads from json import dumps import mox -from testtools import matchers from heat.db.sqlalchemy import api as db_api @@ -419,10 +418,6 @@ def create_resource_data(ctx, resource, **kwargs): return db_api.resource_data_set(resource, **values) -def create_stack_lock(ctx, stack_id, engine_id): - return db_api.stack_lock_create(ctx, stack_id, engine_id) - - def create_event(ctx, **kwargs): values = { 'stack_id': 'test_stack_id', @@ -805,34 +800,6 @@ def test_resource_get_all_by_stack(self): self.ctx, self.stack2.id) -class DBAPIStackLockTest(HeatTestCase): - def setUp(self): - super(DBAPIStackLockTest, self).setUp() - self.ctx = utils.dummy_context() - utils.setup_dummy_db() - utils.reset_dummy_db() - self.template = create_raw_template(self.ctx) - self.user_creds = create_user_creds(self.ctx) - self.stack = create_stack(self.ctx, self.template, self.user_creds) - - def test_stack_lock_create_get(self): - create_stack_lock(self.ctx, self.stack.id, UUID1) - lock = db_api.stack_lock_get(self.ctx, self.stack.id) - self.assertEqual(UUID1, lock['engine_id']) - - def test_stack_lock_steal(self): - create_stack_lock(self.ctx, self.stack.id, UUID1) - db_api.stack_lock_steal(self.ctx, self.stack.id, UUID2) - lock = db_api.stack_lock_get(self.ctx, self.stack.id) - self.assertEqual(UUID2, lock['engine_id']) - - def test_stack_lock_release(self): - create_stack_lock(self.ctx, self.stack.id, UUID1) - db_api.stack_lock_release(self.ctx, self.stack.id) - lock = db_api.stack_lock_get(self.ctx, self.stack.id) - self.assertIsNone(lock) - - class DBAPIResourceDataTest(HeatTestCase): def setUp(self): super(DBAPIResourceDataTest, self).setUp() @@ -1083,15 +1050,3 @@ def test_watch_data_get_all(self): data = [wd.data for wd in watch_data] [self.assertIn(val['data'], data) for val in values] - - -class DBAPIUtilTest(HeatTestCase): - def setUp(self): - super(DBAPIUtilTest, self).setUp() - self.ctx = utils.dummy_context() - utils.setup_dummy_db() - utils.reset_dummy_db() - - def test_current_timestamp(self): - current_timestamp = db_api.current_timestamp() - self.assertThat(current_timestamp, matchers.IsInstance(datetime)) diff --git a/heat/tests/test_stack_lock.py b/heat/tests/test_stack_lock.py deleted file mode 100644 index 12e98216d09..00000000000 --- a/heat/tests/test_stack_lock.py +++ /dev/null @@ -1,93 +0,0 @@ - -# 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. - -import datetime -import mox - -from heat.common import exception -from heat.db import api as db_api -from heat.engine import stack_lock -from heat.tests.common import HeatTestCase -from heat.tests import utils - - -class StackLockTest(HeatTestCase): - def setUp(self): - super(StackLockTest, self).setUp() - utils.setup_dummy_db() - self.context = utils.dummy_context() - self.stack = self.m.CreateMockAnything() - self.stack.id = "aae01f2d-52ae-47ac-8a0d-3fde3d220fea" - self.stack.name = "test_stack" - self.stack.action = "CREATE" - self.stack.timeout = 1 - - def test_successful_acquire_new_lock(self): - self.m.StubOutWithMock(db_api, "stack_lock_get") - db_api.stack_lock_get(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(None) - self.m.StubOutWithMock(db_api, "stack_lock_create") - db_api.stack_lock_create(mox.IgnoreArg(), mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(None) - self.m.ReplayAll() - slock = stack_lock.StackLock(self.context, self.stack) - slock.acquire() - self.m.VerifyAll() - - def test_successful_acquire_steal_lock_updated(self): - existing_lock = self.m.CreateMockAnything() - existing_lock.updated_at = datetime.datetime(2012, 10, 16, 18, 35, 18) - current_time = datetime.datetime(2012, 10, 16, 18, 36, 29) - self.m.StubOutWithMock(db_api, "current_timestamp") - db_api.current_timestamp().AndReturn(current_time) - self.m.StubOutWithMock(db_api, "stack_lock_get") - db_api.stack_lock_get(mox.IgnoreArg(), mox.IgnoreArg())\ - .AndReturn(existing_lock) - self.m.StubOutWithMock(db_api, "stack_lock_steal") - db_api.stack_lock_steal(mox.IgnoreArg(), mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(None) - self.m.ReplayAll() - slock = stack_lock.StackLock(self.context, self.stack) - slock.acquire() - self.m.VerifyAll() - - def test_successful_acquire_steal_lock_created(self): - existing_lock = self.m.CreateMockAnything() - existing_lock.updated_at = None - existing_lock.created_at = datetime.datetime(2012, 10, 16, 18, 35, 18) - current_time = datetime.datetime(2012, 10, 16, 18, 36, 29) - self.m.StubOutWithMock(db_api, "current_timestamp") - db_api.current_timestamp().AndReturn(current_time) - self.m.StubOutWithMock(db_api, "stack_lock_get") - db_api.stack_lock_get(mox.IgnoreArg(), mox.IgnoreArg())\ - .AndReturn(existing_lock) - self.m.StubOutWithMock(db_api, "stack_lock_steal") - db_api.stack_lock_steal(mox.IgnoreArg(), mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(None) - self.m.ReplayAll() - slock = stack_lock.StackLock(self.context, self.stack) - slock.acquire() - self.m.VerifyAll() - - def test_failed_acquire(self): - existing_lock = self.m.CreateMockAnything() - existing_lock.updated_at = datetime.datetime(2012, 10, 16, 18, 35, 18) - current_time = datetime.datetime(2012, 10, 16, 18, 35, 29) - self.m.StubOutWithMock(db_api, "current_timestamp") - db_api.current_timestamp().AndReturn(current_time) - self.m.StubOutWithMock(db_api, "stack_lock_get") - db_api.stack_lock_get(mox.IgnoreArg(), mox.IgnoreArg())\ - .AndReturn(existing_lock) - self.m.ReplayAll() - slock = stack_lock.StackLock(self.context, self.stack) - self.assertRaises(exception.ActionInProgress, slock.acquire) - self.m.VerifyAll()