diff options
Diffstat (limited to 'heat')
28 files changed, 147 insertions, 152 deletions
diff --git a/heat/common/context.py b/heat/common/context.py index c2b48611a..0911982c3 100644 --- a/heat/common/context.py +++ b/heat/common/context.py @@ -12,7 +12,6 @@ # under the License. from keystoneauth1 import access -from keystoneauth1 import exceptions as ksa_exceptions from keystoneauth1.identity import access as access_plugin from keystoneauth1.identity import generic from keystoneauth1 import loading as ks_loading @@ -25,7 +24,6 @@ import oslo_messaging from oslo_middleware import request_id as oslo_request_id from oslo_utils import importutils import six -import tenacity from heat.common import config from heat.common import endpoint_utils @@ -38,6 +36,8 @@ from heat.engine import clients LOG = logging.getLogger(__name__) +cfg.CONF.import_opt('client_retry_limit', 'heat.common.config') + # Note, we yield the options via list_opts to enable generation of the # sample heat.conf, but we don't register these options directly via # cfg.CONF.register*, it's done via ks_loading.register_auth_conf_options @@ -53,15 +53,6 @@ TRUSTEE_CONF_GROUP = 'trustee' ks_loading.register_auth_conf_options(cfg.CONF, TRUSTEE_CONF_GROUP) -retry_on_connection_timeout = tenacity.retry( - stop=tenacity.stop_after_attempt(cfg.CONF.client_retry_limit+1), - wait=tenacity.wait_random(max=2), - retry=tenacity.retry_if_exception_type( - (ksa_exceptions.ConnectFailure, - ksa_exceptions.DiscoveryFailure)), - reraise=True) - - def list_opts(): trustee_opts = ks_loading.get_auth_common_conf_options() trustee_opts.extend(ks_loading.get_auth_plugin_conf_options( @@ -122,6 +113,7 @@ class RequestContext(context.RequestContext): self._session = None self._clients = None self._keystone_session = session.Session( + connect_retries=cfg.CONF.client_retry_limit, **config.get_ssl_options('keystone')) self.trust_id = trust_id self.trustor_user_id = trustor_user_id @@ -299,8 +291,6 @@ class RequestContext(context.RequestContext): class StoredContext(RequestContext): - - @retry_on_connection_timeout def _load_keystone_data(self): self._keystone_loaded = True auth_ref = self.auth_plugin.get_access(self.keystone_session) diff --git a/heat/db/sqlalchemy/api.py b/heat/db/sqlalchemy/api.py index c3f31ddfc..a33b39d12 100644 --- a/heat/db/sqlalchemy/api.py +++ b/heat/db/sqlalchemy/api.py @@ -164,7 +164,11 @@ def raw_template_update(context, template_id, values): def raw_template_delete(context, template_id): - raw_template = raw_template_get(context, template_id) + try: + raw_template = raw_template_get(context, template_id) + except exception.NotFound: + # Ignore not found + return raw_tmpl_files_id = raw_template.files_id session = context.session with session.begin(subtransactions=True): @@ -267,7 +271,7 @@ def resource_purge_deleted(context, stack_id): query = context.session.query(models.Resource) result = query.filter_by(**filters) attr_ids = [r.attr_data_id for r in result if r.attr_data_id is not None] - with context.session.begin(subtransactions=True): + with context.session.begin(): result.delete() if attr_ids: context.session.query(models.ResourcePropertiesData).filter( @@ -320,7 +324,7 @@ def resource_delete(context, resource_id): def resource_attr_id_set(context, resource_id, atomic_key, attr_id): session = context.session - with session.begin(subtransactions=True): + with session.begin(): values = {'attr_data_id': attr_id} _add_atomic_key_to_values(values, atomic_key) rows_updated = session.query(models.Resource).filter(and_( @@ -346,7 +350,7 @@ def resource_attr_id_set(context, resource_id, atomic_key, attr_id): def resource_attr_data_delete(context, resource_id, attr_id): session = context.session - with session.begin(subtransactions=True): + with session.begin(): resource = session.query(models.Resource).get(resource_id) attr_prop_data = session.query( models.ResourcePropertiesData).get(attr_id) @@ -468,7 +472,7 @@ def resource_exchange_stacks(context, resource_id1, resource_id2): def resource_data_delete(context, resource_id, key): result = resource_data_get_by_key(context, resource_id, key) session = context.session - with session.begin(subtransactions=True): + with session.begin(): session.delete(result) @@ -486,7 +490,7 @@ def resource_create_replacement(context, atomic_key, expected_engine_id=None): session = context.session try: - with session.begin(subtransactions=True): + with session.begin(): new_res = resource_create(context, new_res_values) update_data = {'replaced_by': new_res.id} update_data.update(existing_res_values) @@ -1267,7 +1271,7 @@ def software_deployment_update(context, deployment_id, values): def software_deployment_delete(context, deployment_id): deployment = software_deployment_get(context, deployment_id) session = context.session - with session.begin(subtransactions=True): + with session.begin(): session.delete(deployment) @@ -1631,7 +1635,7 @@ def _db_encrypt_or_decrypt_template_params( batch_size=batch_size) next_batch = list(itertools.islice(template_batches, batch_size)) while next_batch: - with session.begin(subtransactions=True): + with session.begin(): for raw_template in next_batch: try: if verbose: @@ -1714,7 +1718,7 @@ def _db_encrypt_or_decrypt_resource_prop_data_legacy( batch_size=batch_size) next_batch = list(itertools.islice(resource_batches, batch_size)) while next_batch: - with session.begin(subtransactions=True): + with session.begin(): for resource in next_batch: if not resource.properties_data: continue @@ -1761,7 +1765,7 @@ def _db_encrypt_or_decrypt_resource_prop_data( model=models.ResourcePropertiesData, batch_size=batch_size) next_batch = list(itertools.islice(rpd_batches, batch_size)) while next_batch: - with session.begin(subtransactions=True): + with session.begin(): for rpd in next_batch: if not rpd.data: continue diff --git a/heat/engine/check_resource.py b/heat/engine/check_resource.py index a2f6d842c..7d12ceeec 100644 --- a/heat/engine/check_resource.py +++ b/heat/engine/check_resource.py @@ -163,6 +163,8 @@ class CheckResource(object): return True except exception.UpdateInProgress: + LOG.debug('Waiting for existing update to unlock resource %s', + rsrc.id) if self._stale_resource_needs_retry(cnxt, rsrc, prev_template_id): rpc_data = sync_point.serialize_input_data(self.input_data) self._rpc_client.check_resource(cnxt, @@ -170,6 +172,8 @@ class CheckResource(object): current_traversal, rpc_data, is_update, adopt_stack_data) + else: + rsrc.handle_preempt() except exception.ResourceFailure as ex: action = ex.action or rsrc.action reason = 'Resource %s failed: %s' % (action, diff --git a/heat/engine/clients/client_plugin.py b/heat/engine/clients/client_plugin.py index 6f485d56a..ab737da6f 100644 --- a/heat/engine/clients/client_plugin.py +++ b/heat/engine/clients/client_plugin.py @@ -24,7 +24,6 @@ import requests import six from heat.common import config -from heat.common import context from heat.common import exception as heat_exception cfg.CONF.import_opt('client_retry_limit', 'heat.common.config') @@ -94,7 +93,6 @@ class ClientPlugin(object): def url_for(self, **kwargs): keystone_session = self.context.keystone_session - @context.retry_on_connection_timeout def get_endpoint(): return keystone_session.get_endpoint(**kwargs) diff --git a/heat/engine/clients/os/blazar.py b/heat/engine/clients/os/blazar.py index 24429d442..9d1d650bf 100644 --- a/heat/engine/clients/os/blazar.py +++ b/heat/engine/clients/os/blazar.py @@ -15,7 +15,9 @@ from blazarclient import client as blazar_client from blazarclient import exception as client_exception from oslo_config import cfg +from heat.common import exception from heat.engine.clients import client_plugin +from heat.engine import constraints CLIENT_NAME = 'blazar' @@ -58,3 +60,16 @@ class BlazarClientPlugin(client_plugin.ClientPlugin): def get_host(self, id): return self.client().host.get(id) + + +class BlazarBaseConstraint(constraints.BaseCustomConstraint): + + resource_client_name = CLIENT_NAME + + +class ReservationConstraint(BlazarBaseConstraint): + expected_exceptions = ( + exception.EntityNotFound, + client_exception.BlazarClientException,) + + resource_getter_name = 'get_lease' diff --git a/heat/engine/clients/os/designate.py b/heat/engine/clients/os/designate.py index be0f81ee6..9a2b25a2f 100644 --- a/heat/engine/clients/os/designate.py +++ b/heat/engine/clients/os/designate.py @@ -39,7 +39,7 @@ class DesignateClientPlugin(client_plugin.ClientPlugin): return isinstance(ex, exceptions.NotFound) def get_zone_id(self, zone_id_or_name): - client = self.client(version=self.V2) + client = self.client(version='2') try: zone_obj = client.zones.get(zone_id_or_name) return zone_obj['id'] diff --git a/heat/engine/clients/os/nova.py b/heat/engine/clients/os/nova.py index 95a9923b7..d8cd22a6f 100644 --- a/heat/engine/clients/os/nova.py +++ b/heat/engine/clients/os/nova.py @@ -82,11 +82,9 @@ class NovaClientPlugin(microversion_mixin.MicroversionMixin, def _get_args(self, version): endpoint_type = self._get_client_option(CLIENT_NAME, 'endpoint_type') - extensions = nc.discover_extensions(version) return { 'session': self.context.keystone_session, - 'extensions': extensions, 'endpoint_type': endpoint_type, 'service_type': self.COMPUTE, 'region_name': self._get_region_name(), @@ -448,10 +446,12 @@ echo -e '%s\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers payload = jsonutils.loads(userdata) encoded_metadata = urlparse.quote(jsonutils.dumps(metadata)) - cfn_init_data = { + path_list = ["/var/lib/heat-cfntools/cfn-init-data", + "/var/lib/cloud/data/cfn-init-data"] + ignition_format_metadata = { "filesystem": "root", "group": {"name": "root"}, - "path": "/var/lib/os-collect-config/local-data", + "path": "", "user": {"name": "root"}, "contents": { "source": "data:," + encoded_metadata, @@ -459,16 +459,19 @@ echo -e '%s\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers "mode": 0o640 } - storage = payload.setdefault('storage', {}) - try: - files = storage.setdefault('files', []) - except AttributeError: - raise ValueError('Ignition "storage" section must be a map') - else: + for path in path_list: + storage = payload.setdefault('storage', {}) try: - files.append(cfn_init_data) + files = storage.setdefault('files', []) except AttributeError: - raise ValueError('Ignition "files" section must be a list') + raise ValueError('Ignition "storage" section must be a map') + else: + try: + data = ignition_format_metadata.copy() + data["path"] = path + files.append(data) + except AttributeError: + raise ValueError('Ignition "files" section must be a list') return jsonutils.dumps(payload) @@ -824,15 +827,6 @@ echo -e '%s\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers return True return False - @os_client.MEMOIZE_EXTENSIONS - def _list_extensions(self): - extensions = self.client().list_extensions.show_all() - return set(extension.alias for extension in extensions) - - def has_extension(self, alias): - """Check if specific extension is present.""" - return alias in self._list_extensions() - class NovaBaseConstraint(constraints.BaseCustomConstraint): diff --git a/heat/engine/resource.py b/heat/engine/resource.py index c383f0abf..0d08d9569 100644 --- a/heat/engine/resource.py +++ b/heat/engine/resource.py @@ -1456,6 +1456,23 @@ class Resource(status.ResourceStatus): new_requires=new_requires) runner(timeout=timeout, progress_callback=progress_callback) + def handle_preempt(self): + """Pre-empt an in-progress update when a new update is available. + + This method is called when a previous convergence update is in + progress but a new update for the resource is available. By default + it does nothing, but subclasses may override it to cancel the + in-progress update if it is safe to do so. + + Note that this method does not run in the context of the in-progress + update and has no access to runtime information about it; nor is it + safe to make changes to the Resource in the database. If implemented, + this method should cause the existing update to complete by external + means. If this leaves the resource in a FAILED state, that should be + taken into account in needs_replace_failed(). + """ + return + def preview_update(self, after, before, after_props, before_props, prev_resource, check_init_complete=False): """Simulates update without actually updating the resource. diff --git a/heat/engine/resources/openstack/neutron/firewall.py b/heat/engine/resources/openstack/neutron/firewall.py index ef869cd40..93c865e76 100644 --- a/heat/engine/resources/openstack/neutron/firewall.py +++ b/heat/engine/resources/openstack/neutron/firewall.py @@ -243,8 +243,8 @@ class FirewallPolicy(neutron.NeutronResource): ), FIREWALL_RULES: properties.Schema( properties.Schema.LIST, - _('An ordered list of firewall rules to apply to the firewall.'), - required=True, + _('An ordered list of firewall rules to apply to the firewall. ' + '(Prior to version 14.0.0 this was a required property).'), update_allowed=True ), } diff --git a/heat/engine/resources/openstack/nova/flavor.py b/heat/engine/resources/openstack/nova/flavor.py index 28b1c5f64..afefcbe51 100644 --- a/heat/engine/resources/openstack/nova/flavor.py +++ b/heat/engine/resources/openstack/nova/flavor.py @@ -41,8 +41,6 @@ class NovaFlavor(resource.Resource): default_client_name = 'nova' - required_service_extension = 'os-flavor-manage' - entity = 'flavors' PROPERTIES = ( diff --git a/heat/engine/resources/openstack/nova/floatingip.py b/heat/engine/resources/openstack/nova/floatingip.py index 83e5e7fca..9a3d0a16a 100644 --- a/heat/engine/resources/openstack/nova/floatingip.py +++ b/heat/engine/resources/openstack/nova/floatingip.py @@ -49,8 +49,6 @@ class NovaFloatingIp(resource.Resource): ) ) - required_service_extension = 'os-floating-ips' - PROPERTIES = (POOL,) = ('pool',) ATTRIBUTES = ( diff --git a/heat/engine/resources/openstack/nova/host_aggregate.py b/heat/engine/resources/openstack/nova/host_aggregate.py index ee57fe27b..5264c8393 100644 --- a/heat/engine/resources/openstack/nova/host_aggregate.py +++ b/heat/engine/resources/openstack/nova/host_aggregate.py @@ -38,8 +38,6 @@ class HostAggregate(resource.Resource): entity = 'aggregates' - required_service_extension = 'os-aggregates' - PROPERTIES = ( NAME, AVAILABILITY_ZONE, HOSTS, METADATA ) = ( diff --git a/heat/engine/resources/openstack/nova/keypair.py b/heat/engine/resources/openstack/nova/keypair.py index bff7c7ba6..9104c8777 100644 --- a/heat/engine/resources/openstack/nova/keypair.py +++ b/heat/engine/resources/openstack/nova/keypair.py @@ -43,8 +43,6 @@ class KeyPair(resource.Resource): support_status = support.SupportStatus(version='2014.1') - required_service_extension = 'os-keypairs' - PROPERTIES = ( NAME, SAVE_PRIVATE_KEY, PUBLIC_KEY, KEY_TYPE, USER, ) = ( diff --git a/heat/engine/resources/openstack/nova/quota.py b/heat/engine/resources/openstack/nova/quota.py index 269fcdaa7..8761da97a 100644 --- a/heat/engine/resources/openstack/nova/quota.py +++ b/heat/engine/resources/openstack/nova/quota.py @@ -53,8 +53,6 @@ class NovaQuota(resource.Resource): entity = 'quotas' - required_service_extension = 'os-quota-sets' - PROPERTIES = ( PROJECT, CORES, FIXED_IPS, FLOATING_IPS, INSTANCES, INJECTED_FILES, INJECTED_FILE_CONTENT_BYTES, INJECTED_FILE_PATH_BYTES, @@ -117,6 +115,14 @@ class NovaQuota(resource.Resource): properties.Schema.INTEGER, _('Quota for the number of injected files. ' 'Setting the value to -1 removes the limit.'), + support_status=support.SupportStatus( + status=support.DEPRECATED, + version='14.0.0', + message=_('File injection is deprecated ' + 'from compute REST API ' + 'OS::Nova::Quota resource will not support ' + 'it in the future.') + ), constraints=[ constraints.Range(min=-1), ], @@ -126,6 +132,14 @@ class NovaQuota(resource.Resource): properties.Schema.INTEGER, _('Quota for the number of injected file content bytes. ' 'Setting the value to -1 removes the limit.'), + support_status=support.SupportStatus( + status=support.DEPRECATED, + version='14.0.0', + message=_('File injection is deprecated ' + 'from compute REST API ' + 'OS::Nova::Quota resource will not support ' + 'it in the future.') + ), constraints=[ constraints.Range(min=-1), ], @@ -135,6 +149,14 @@ class NovaQuota(resource.Resource): properties.Schema.INTEGER, _('Quota for the number of injected file path bytes. ' 'Setting the value to -1 removes the limit.'), + support_status=support.SupportStatus( + status=support.DEPRECATED, + version='14.0.0', + message=_('File injection is deprecated ' + 'from compute REST API ' + 'OS::Nova::Quota resource will not support ' + 'it in the future.') + ), constraints=[ constraints.Range(min=-1), ], diff --git a/heat/engine/resources/openstack/nova/server_group.py b/heat/engine/resources/openstack/nova/server_group.py index 5b5a4c3ff..abaa8c6b7 100644 --- a/heat/engine/resources/openstack/nova/server_group.py +++ b/heat/engine/resources/openstack/nova/server_group.py @@ -33,8 +33,6 @@ class ServerGroup(resource.Resource): entity = 'server_groups' - required_service_extension = 'os-server-groups' - PROPERTIES = ( NAME, POLICIES ) = ( diff --git a/heat/engine/resources/openstack/nova/server_network_mixin.py b/heat/engine/resources/openstack/nova/server_network_mixin.py index 6679710e5..c03514262 100644 --- a/heat/engine/resources/openstack/nova/server_network_mixin.py +++ b/heat/engine/resources/openstack/nova/server_network_mixin.py @@ -190,12 +190,6 @@ class ServerNetworkMixin(object): creating. We need to store information about that ports, so store their IDs to data with key `external_ports`. """ - # check if os-attach-interfaces extension is available on this cloud. - # If it's not, then novaclient's interface_list method cannot be used - # to get the list of interfaces. - if not self.client_plugin().has_extension('os-attach-interfaces'): - return - server = self.client().servers.get(self.resource_id) ifaces = server.interface_list() external_port_ids = set(iface.port_id for iface in ifaces) diff --git a/heat/engine/resources/stack_resource.py b/heat/engine/resources/stack_resource.py index 7def946c5..bb020c06b 100644 --- a/heat/engine/resources/stack_resource.py +++ b/heat/engine/resources/stack_resource.py @@ -561,9 +561,10 @@ class StackResource(resource.Resource): return self._check_status_complete(target_action, cookie=cookie) - def handle_update_cancel(self, cookie): + def _handle_cancel(self): stack_identity = self.nested_identifier() if stack_identity is not None: + LOG.debug('Cancelling %s of %s' % (self.action, self)) try: self.rpc_client().stack_cancel_update( self.context, @@ -573,6 +574,12 @@ class StackResource(resource.Resource): LOG.debug('Nested stack %s not in cancellable state', stack_identity.stack_name) + def handle_preempt(self): + self._handle_cancel() + + def handle_update_cancel(self, cookie): + self._handle_cancel() + def handle_create_cancel(self, cookie): return self.handle_update_cancel(cookie) diff --git a/heat/engine/service.py b/heat/engine/service.py index 62af50a95..5c02798d0 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -2320,18 +2320,12 @@ class EngineService(service.ServiceBase): try: for st in stacks: lock = stack_lock.StackLock(ctxt, st.id, self.engine_id) - lock.acquire() locks.append(lock) - sess = ctxt.session - sess.begin(subtransactions=True) - try: + lock.acquire() + with ctxt.session.begin(): for st in stacks: if not st.convergence: st.migrate_to_convergence() - sess.commit() - except Exception: - sess.rollback() - raise finally: for lock in locks: lock.release() diff --git a/heat/engine/worker.py b/heat/engine/worker.py index 274dd436f..ea8553d75 100644 --- a/heat/engine/worker.py +++ b/heat/engine/worker.py @@ -110,6 +110,9 @@ class WorkerService(object): Marks the stack as FAILED due to cancellation, but, allows all in_progress resources to complete normally; no worker is stopped abruptly. + + Any in-progress traversals are also stopped on all nested stacks that + are descendants of the one passed. """ _stop_traversal(stack) diff --git a/heat/tests/clients/test_nova_client.py b/heat/tests/clients/test_nova_client.py index 4af266f36..afd3caaf6 100644 --- a/heat/tests/clients/test_nova_client.py +++ b/heat/tests/clients/test_nova_client.py @@ -45,27 +45,21 @@ class NovaClientPluginTest(NovaClientPluginTestCase): def test_create(self): context = utils.dummy_context() - ext_mock = self.patchobject(nc, 'discover_extensions') plugin = context.clients.client_plugin('nova') plugin.max_microversion = '2.53' client = plugin.client() - ext_mock.assert_called_once_with('2.53') self.assertIsNotNone(client.servers) def test_v2_26_create(self): ctxt = utils.dummy_context() - ext_mock = self.patchobject(nc, 'discover_extensions') self.patchobject(nc, 'Client', return_value=mock.Mock()) plugin = ctxt.clients.client_plugin('nova') plugin.max_microversion = '2.53' plugin.client(version='2.26') - ext_mock.assert_called_once_with('2.26') - def test_v2_26_create_failed(self): ctxt = utils.dummy_context() - self.patchobject(nc, 'discover_extensions') plugin = ctxt.clients.client_plugin('nova') plugin.max_microversion = '2.23' client_stub = mock.Mock() @@ -402,8 +396,10 @@ class NovaClientPluginUserdataTest(NovaClientPluginTestCase): userdata=userdata, user_data_format=ud_format) ig = json.loads(data) - self.assertEqual("/var/lib/os-collect-config/local-data", + self.assertEqual("/var/lib/heat-cfntools/cfn-init-data", ig["storage"]["files"][0]["path"]) + self.assertEqual("/var/lib/cloud/data/cfn-init-data", + ig["storage"]["files"][1]["path"]) self.assertEqual("data:,%7B%22os-collect-config%22%3A%20%7B%22heat" "%22%3A%20%7B%22password%22%3A%20%22%2A%2A%2A%22" "%7D%7D%7D", @@ -652,30 +648,3 @@ class ConsoleUrlsTest(common.HeatTestCase): self.console_method.side_effect = exc("spam") self._test_get_console_url_tolerate_exception('spam') - - -class NovaClientPluginExtensionsTest(NovaClientPluginTestCase): - """Tests for extensions in novaclient.""" - - def test_has_no_extensions(self): - self.nova_client.list_extensions.show_all.return_value = [] - self.assertFalse(self.nova_plugin.has_extension( - "os-virtual-interfaces")) - - def test_has_no_interface_extensions(self): - mock_extension = mock.Mock() - p = mock.PropertyMock(return_value='os-xxxx') - type(mock_extension).alias = p - self.nova_client.list_extensions.show_all.return_value = [ - mock_extension] - self.assertFalse(self.nova_plugin.has_extension( - "os-virtual-interfaces")) - - def test_has_os_interface_extension(self): - mock_extension = mock.Mock() - p = mock.PropertyMock(return_value='os-virtual-interfaces') - type(mock_extension).alias = p - self.nova_client.list_extensions.show_all.return_value = [ - mock_extension] - self.assertTrue(self.nova_plugin.has_extension( - "os-virtual-interfaces")) diff --git a/heat/tests/engine/test_engine_worker.py b/heat/tests/engine/test_engine_worker.py index affb51186..bdde6c8be 100644 --- a/heat/tests/engine/test_engine_worker.py +++ b/heat/tests/engine/test_engine_worker.py @@ -197,6 +197,29 @@ class WorkerServiceTest(common.HeatTestCase): self.assertEqual('stack1', call_args1.name) self.assertEqual('stack2', call_args2.name) + @mock.patch.object(worker, '_stop_traversal') + def test_stop_nested_traversal_stops_deeply_nested_stack(self, mock_st): + mock_tgm = mock.Mock() + ctx = utils.dummy_context() + tmpl = templatem.Template.create_empty_template() + stack1 = parser.Stack(ctx, 'stack1', tmpl, + current_traversal='123') + stack1.store() + stack2 = parser.Stack(ctx, 'stack2', tmpl, + owner_id=stack1.id, current_traversal='456') + stack2.store() + stack3 = parser.Stack(ctx, 'stack3', tmpl, + owner_id=stack2.id, current_traversal='789') + stack3.store() + _worker = worker.WorkerService('host-1', 'topic-1', 'engine-001', + mock_tgm) + _worker.stop_traversal(stack2) + self.assertEqual(2, mock_st.call_count) + call1, call2 = mock_st.call_args_list + call_args1, call_args2 = call1[0][0], call2[0][0] + self.assertEqual('stack2', call_args1.name) + self.assertEqual('stack3', call_args2.name) + @mock.patch.object(worker, '_cancel_workers') @mock.patch.object(worker.WorkerService, 'stop_traversal') def test_stop_all_workers_when_stack_in_progress(self, mock_st, mock_cw): diff --git a/heat/tests/openstack/nova/fakes.py b/heat/tests/openstack/nova/fakes.py index 2651a96df..4404ea798 100644 --- a/heat/tests/openstack/nova/fakes.py +++ b/heat/tests/openstack/nova/fakes.py @@ -364,6 +364,19 @@ class FakeSessionClient(base_client.SessionClient): 'OS-FLV-EXT-DATA:ephemeral': 30}}) # + # Interfaces + # + + def get_servers_5678_os_interface(self, **kw): + return (200, {'interfaceAttachments': + [{"fixed_ips": + [{"ip_address": "10.0.0.1", + "subnet_id": "f8a6e8f8-c2ec-497c-9f23-da9616de54ef" + }], + "port_id": "ce531f90-199f-48c0-816c-13e38010b442" + }]}) + + # # Floating ips # diff --git a/heat/tests/openstack/nova/test_flavor.py b/heat/tests/openstack/nova/test_flavor.py index 7799fbdbb..1f6afcfdf 100644 --- a/heat/tests/openstack/nova/test_flavor.py +++ b/heat/tests/openstack/nova/test_flavor.py @@ -13,7 +13,6 @@ import mock -from heat.engine.clients.os import nova as novac from heat.engine import stack from heat.engine import template from heat.tests import common @@ -42,8 +41,6 @@ flavor_template = { class NovaFlavorTest(common.HeatTestCase): def setUp(self): super(NovaFlavorTest, self).setUp() - self.patchobject(novac.NovaClientPlugin, 'has_extension', - return_value=True) self.ctx = utils.dummy_context() def create_flavor(self, with_name_id=False, is_public=True): diff --git a/heat/tests/openstack/nova/test_host_aggregate.py b/heat/tests/openstack/nova/test_host_aggregate.py index 8b164e4e7..0c63fec18 100644 --- a/heat/tests/openstack/nova/test_host_aggregate.py +++ b/heat/tests/openstack/nova/test_host_aggregate.py @@ -39,9 +39,6 @@ AGGREGATE_TEMPLATE = { class NovaHostAggregateTest(common.HeatTestCase): def setUp(self): super(NovaHostAggregateTest, self).setUp() - self.patchobject(nova.NovaClientPlugin, - 'has_extension', - return_value=True) self.ctx = utils.dummy_context() self.stack = stack.Stack( diff --git a/heat/tests/openstack/nova/test_keypair.py b/heat/tests/openstack/nova/test_keypair.py index cc412bbd2..1244bc4f3 100644 --- a/heat/tests/openstack/nova/test_keypair.py +++ b/heat/tests/openstack/nova/test_keypair.py @@ -46,8 +46,6 @@ class NovaKeyPairTest(common.HeatTestCase): self.fake_nova = mock.MagicMock() self.fake_keypairs = mock.MagicMock() self.fake_nova.keypairs = self.fake_keypairs - self.patchobject(nova.NovaClientPlugin, 'has_extension', - return_value=True) self.cp_mock = self.patchobject(nova.NovaClientPlugin, 'client', return_value=self.fake_nova) diff --git a/heat/tests/openstack/nova/test_quota.py b/heat/tests/openstack/nova/test_quota.py index 3c813a56b..648db3895 100644 --- a/heat/tests/openstack/nova/test_quota.py +++ b/heat/tests/openstack/nova/test_quota.py @@ -16,7 +16,6 @@ import six from heat.common import exception from heat.common import template_format from heat.engine.clients.os import keystone as k_plugin -from heat.engine.clients.os import nova as n_plugin from heat.engine import rsrc_defn from heat.engine import stack as parser from heat.engine import template @@ -62,8 +61,6 @@ class NovaQuotaTest(common.HeatTestCase): super(NovaQuotaTest, self).setUp() self.ctx = utils.dummy_context() - self.patchobject(n_plugin.NovaClientPlugin, 'has_extension', - return_value=True) self.patchobject(k_plugin.KeystoneClientPlugin, 'get_project_id', return_value='some_project_id') tpl = template_format.parse(quota_template) diff --git a/heat/tests/openstack/nova/test_server.py b/heat/tests/openstack/nova/test_server.py index 0f6858d0c..8744466d7 100644 --- a/heat/tests/openstack/nova/test_server.py +++ b/heat/tests/openstack/nova/test_server.py @@ -1349,8 +1349,6 @@ class ServersTest(common.HeatTestCase): } } ''' - self.patchobject(nova.NovaClientPlugin, 'has_extension', - return_value=True) t = template_format.parse(nova_keypair_template) templ = template.Template(t) self.patchobject(nova.NovaClientPlugin, 'client', @@ -4947,7 +4945,6 @@ class ServerInternalPortTest(ServersTest): server.client = mock.Mock() server.client().servers.get.return_value = Fake() server.client_plugin = mock.Mock() - server.client_plugin().has_extension.return_value = True server._data = {"internal_ports": '[{"id": "1122"}]', "external_ports": '[{"id": "3344"},{"id": "5566"}]'} @@ -5220,28 +5217,3 @@ class ServerInternalPortTest(ServersTest): mock.call('prev_rsrc', 1122), mock.call('prev_rsrc', 3344), mock.call('prev_rsrc', 5566)]) - - def test_store_external_ports_os_interface_not_installed(self): - t, stack, server = self._return_template_stack_and_rsrc_defn( - 'test', tmpl_server_with_network_id) - - class Fake(object): - def interface_list(self): - return [iface('1122'), - iface('1122'), - iface('2233'), - iface('3344')] - - server.client = mock.Mock() - server.client().servers.get.return_value = Fake() - server.client_plugin = mock.Mock() - server.client_plugin().has_extension.return_value = False - - server._data = {"internal_ports": '[{"id": "1122"}]', - "external_ports": '[{"id": "3344"},{"id": "5566"}]'} - - iface = collections.namedtuple('iface', ['port_id']) - update_data = self.patchobject(server, '_data_update_ports') - - server.store_external_ports() - self.assertEqual(0, update_data.call_count) diff --git a/heat/tests/openstack/nova/test_server_group.py b/heat/tests/openstack/nova/test_server_group.py index a8129fa9e..6a2e8bed5 100644 --- a/heat/tests/openstack/nova/test_server_group.py +++ b/heat/tests/openstack/nova/test_server_group.py @@ -16,7 +16,6 @@ import json import mock from heat.common import template_format -from heat.engine.clients.os import nova from heat.engine import scheduler from heat.tests import common from heat.tests import utils @@ -44,8 +43,6 @@ class FakeGroup(object): class NovaServerGroupTest(common.HeatTestCase): def setUp(self): super(NovaServerGroupTest, self).setUp() - self.patchobject(nova.NovaClientPlugin, 'has_extension', - return_value=True) def _init_template(self, sg_template): template = template_format.parse(json.dumps(sg_template)) |