diff options
49 files changed, 757 insertions, 298 deletions
diff --git a/.zuul.yaml b/.zuul.yaml index ef999cb9a..233dc0d95 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -72,7 +72,7 @@ minimal_image_ref: ${DEFAULT_IMAGE_NAME:-cirros-0.3.6-x86_64-disk} instance_type: m1.heat_int minimal_instance_type: m1.heat_micro - image_ref: Fedora-Cloud-Base-33-1.2.x86_64 + image_ref: Fedora-Cloud-Base-36-1.5.x86_64 hidden_stack_tag: hidden heat_config_notify_script: /opt/stack/heat-agents/heat-config/bin/heat-config-notify boot_config_env: /opt/stack/heat-templates/hot/software-config/boot-config/test_image_env.yaml @@ -150,7 +150,7 @@ h-eng: true heat: true # We do run a list of tests after upgrade. This is just to bypass the req from parent. - tempest_test_regex: ^heat_tempest_plugin\.tests\.functional\.test_nova_server_networks + tempest_test_regex: ^tempest\.api\.identity\.v3\.test_tokens tox_envlist: all devstack_plugins: heat: https://opendev.org/openstack/heat @@ -181,9 +181,9 @@ - ^releasenotes/.*$ - project: + queue: heat templates: - openstack-cover-jobs - - openstack-lower-constraints-jobs - openstack-python3-zed-jobs - openstack-python3-zed-jobs-arm64 - periodic-stable-jobs @@ -196,11 +196,7 @@ - heat-functional - heat-functional-legacy gate: - queue: heat jobs: - grenade-heat-multinode - heat-functional - heat-functional-legacy - experimental: - jobs: - - tripleo-ci-centos-8-scenario002-standalone diff --git a/bindep.txt b/bindep.txt index 7071c188b..55d97b80b 100644 --- a/bindep.txt +++ b/bindep.txt @@ -6,7 +6,6 @@ mariadb-server [platform:redhat] postgresql build-essential [platform:dpkg] -python-dev [platform:dpkg] python3-all-dev [platform:dpkg] libxml2-dev [platform:dpkg] libxslt1-dev [platform:dpkg] @@ -19,7 +18,6 @@ mysql-client [platform:dpkg] postgresql-client [platform:dpkg] gcc [platform:rpm] -python-devel [platform:rpm] python3-devel [platform:fedora platform:suse] python3 [platform:suse] libxml2-devel [platform:rpm] diff --git a/devstack/lib/heat b/devstack/lib/heat index d95e15266..ea6a353c6 100644 --- a/devstack/lib/heat +++ b/devstack/lib/heat @@ -333,14 +333,6 @@ function stop_heat { fi } -# TODO(ramishra): Remove after Queens -function stop_cw_service { - if $SYSTEMCTL is-enabled devstack@h-api-cw.service; then - $SYSTEMCTL stop devstack@h-api-cw.service - $SYSTEMCTL disable devstack@h-api-cw.service - fi -} - # _cleanup_heat_apache_wsgi() - Remove wsgi files, disable and remove apache vhost file function _cleanup_heat_apache_wsgi { if [[ "$WSGI_MODE" == "uwsgi" ]]; then @@ -449,7 +441,7 @@ function configure_tempest_for_heat { # Skip LoadBalancerv2Test as deprecated neutron-lbaas service is not enabled iniset $TEMPEST_CONFIG heat_plugin skip_functional_test_list 'LoadBalancerv2Test, NotificationTest' - openstack flavor show m1.heat_int || openstack flavor create m1.heat_int --ram 512 --disk 4 + openstack flavor show m1.heat_int || openstack flavor create m1.heat_int --ram 512 --disk 10 openstack flavor show m1.heat_micro || openstack flavor create m1.heat_micro --ram 128 --disk 1 export OS_CLOUD=devstack @@ -463,14 +455,14 @@ function configure_tempest_for_heat { source /etc/ci/mirror_info.sh fi HEAT_TEST_FEDORA_IMAGE_UPSTREAM=https://download.fedoraproject.org/pub/fedora/linux - HEAT_TEST_FEDORA_IMAGE_PATH=releases/33/Cloud/x86_64/images/Fedora-Cloud-Base-33-1.2.x86_64.qcow2 + HEAT_TEST_FEDORA_IMAGE_PATH=releases/36/Cloud/x86_64/images/Fedora-Cloud-Base-36-1.5.x86_64.qcow2 if curl --output /dev/null --silent --head --fail "${NODEPOOL_FEDORA_MIRROR}/${HEAT_TEST_FEDORA_IMAGE_PATH}"; then export HEAT_TEST_FEDORA_IMAGE="${NODEPOOL_FEDORA_MIRROR}/${HEAT_TEST_FEDORA_IMAGE_PATH}" else export HEAT_TEST_FEDORA_IMAGE="${HEAT_TEST_FEDORA_IMAGE_UPSTREAM}/${HEAT_TEST_FEDORA_IMAGE_PATH}" fi TOKEN=$(openstack token issue -c id -f value) - local image_exists=$( openstack image list | grep "Fedora-Cloud-Base-33-1.2.x86_64" ) + local image_exists=$( openstack image list | grep "Fedora-Cloud-Base-36-1.5.x86_64" ) if [[ -z $image_exists ]]; then if is_service_enabled g-api; then upload_image $HEAT_TEST_FEDORA_IMAGE $TOKEN diff --git a/devstack/upgrade/resources.sh b/devstack/upgrade/resources.sh index 1b7be42ee..3f00dce9d 100755 --- a/devstack/upgrade/resources.sh +++ b/devstack/upgrade/resources.sh @@ -64,7 +64,11 @@ function _run_heat_integrationtests { # Run set of specified functional tests UPGRADE_TESTS=upgrade_tests.list _write_heat_integrationtests $UPGRADE_TESTS - + export UPPER_CONSTRAINTS_FILE=$DEST/requirements/upper-constraints.txt + export TOX_CONSTRAINTS_FILE=$UPPER_CONSTRAINTS_FILE + export HEAT_TEMPEST_PLUGIN=$DEST/heat-tempest-plugin + sudo git config --system --add safe.directory $HEAT_TEMPEST_PLUGIN + tox -evenv-tempest -- pip install -c$UPPER_CONSTRAINTS_FILE $HEAT_TEMPEST_PLUGIN tox -evenv-tempest -- stestr --test-path=$DEST/heat/heat_integrationtests --top-dir=$DEST/heat \ --group_regex='heat_tempest_plugin\.tests\.api\.test_heat_api[._]([^_]+)' \ run --whitelist-file $UPGRADE_TESTS @@ -105,7 +109,7 @@ function create { local stack_name='grenadine' resource_save heat stack_name $stack_name local loc=`dirname $BASH_SOURCE` - heat stack-create -f $loc/templates/random_string.yaml $stack_name + openstack stack create -t $loc/templates/random_string.yaml $stack_name } function verify { @@ -117,7 +121,7 @@ function verify { fi fi stack_name=$(resource_get heat stack_name) - heat stack-show $stack_name + openstack stack show $stack_name # TODO(sirushtim): Create more granular checks for Heat. } @@ -129,7 +133,7 @@ function verify_noapi { function destroy { _heat_set_user - heat stack-delete $(resource_get heat stack_name) + openstack stack delete -y $(resource_get heat stack_name) source $TOP_DIR/openrc admin admin local user_id=$(resource_get heat user_id) diff --git a/devstack/upgrade/shutdown.sh b/devstack/upgrade/shutdown.sh index dfe566cee..f512e8d4d 100755 --- a/devstack/upgrade/shutdown.sh +++ b/devstack/upgrade/shutdown.sh @@ -30,10 +30,6 @@ set -o xtrace stop_heat -# stop cloudwatch service if running -# TODO(ramishra): Remove it after Queens -stop_cw_service - SERVICES_DOWN="heat-api heat-engine heat-api-cfn" # sanity check that services are actually down diff --git a/doc/source/getting_started/on_devstack.rst b/doc/source/getting_started/on_devstack.rst index 29581390d..64d607ab2 100644 --- a/doc/source/getting_started/on_devstack.rst +++ b/doc/source/getting_started/on_devstack.rst @@ -51,8 +51,8 @@ a VM image that heat can launch. To do that add the following to `[[local|localrc]]` section of `local.conf`:: IMAGE_URL_SITE="https://download.fedoraproject.org" - IMAGE_URL_PATH="/pub/fedora/linux/releases/33/Cloud/x86_64/images/" - IMAGE_URL_FILE="Fedora-Cloud-Base-33-1.2.x86_64.qcow2" + IMAGE_URL_PATH="/pub/fedora/linux/releases/36/Cloud/x86_64/images/" + IMAGE_URL_FILE="Fedora-Cloud-Base-36-1.5.x86_64.qcow2" IMAGE_URLS+=","$IMAGE_URL_SITE$IMAGE_URL_PATH$IMAGE_URL_FILE URLs for any cloud image may be specified, but fedora images from F20 contain diff --git a/heat/api/openstack/v1/software_configs.py b/heat/api/openstack/v1/software_configs.py index 41f9486e4..c27da4fe7 100644 --- a/heat/api/openstack/v1/software_configs.py +++ b/heat/api/openstack/v1/software_configs.py @@ -43,6 +43,14 @@ class SoftwareConfigController(object): except ValueError as e: raise exc.HTTPBadRequest(str(e)) + def _extract_int_param(self, name, value, + allow_zero=True, allow_negative=False): + try: + return param_utils.extract_int(name, value, + allow_zero, allow_negative) + except ValueError as e: + raise exc.HTTPBadRequest(str(e)) + def _index(self, req, use_admin_cnxt=False): param_types = { 'limit': util.PARAM_TYPE_SINGLE, @@ -50,6 +58,10 @@ class SoftwareConfigController(object): } params = util.get_allowed_params(req.params, param_types) + key = rpc_api.PARAM_LIMIT + if key in params: + params[key] = self._extract_int_param(key, params[key]) + if use_admin_cnxt: cnxt = context.get_admin_context() else: diff --git a/heat/common/environment_util.py b/heat/common/environment_util.py index 8a6ed4393..490802830 100644 --- a/heat/common/environment_util.py +++ b/heat/common/environment_util.py @@ -12,6 +12,7 @@ # under the License. import collections +from oslo_log import log as logging from oslo_serialization import jsonutils from heat.common import environment_format as env_fmt @@ -21,15 +22,22 @@ from heat.common.i18n import _ ALLOWED_PARAM_MERGE_STRATEGIES = (OVERWRITE, MERGE, DEEP_MERGE) = ( 'overwrite', 'merge', 'deep_merge') +LOG = logging.getLogger(__name__) -def get_param_merge_strategy(merge_strategies, param_key): + +def get_param_merge_strategy(merge_strategies, param_key, + available_strategies=None): + if not available_strategies: + available_strategies = {} if merge_strategies is None: return OVERWRITE env_default = merge_strategies.get('default', OVERWRITE) + merge_strategy = merge_strategies.get( + param_key, available_strategies.get( + param_key, env_default)) - merge_strategy = merge_strategies.get(param_key, env_default) if merge_strategy in ALLOWED_PARAM_MERGE_STRATEGIES: return merge_strategy @@ -105,34 +113,39 @@ def merge_parameters(old, new, param_schemata, strategies_in_file, raise exception.InvalidMergeStrategyForParam(strategy=MERGE, param=p_key) - new_strategies = {} - - if not old: - return new, new_strategies - for key, value in new.items(): # if key not in param_schemata ignore it if key in param_schemata and value is not None: param_merge_strategy = get_param_merge_strategy( - strategies_in_file, key) + strategies_in_file, key, available_strategies) if key not in available_strategies: - new_strategies[key] = param_merge_strategy + available_strategies[key] = param_merge_strategy elif param_merge_strategy != available_strategies[key]: raise exception.ConflictingMergeStrategyForParam( strategy=param_merge_strategy, param=key, env_file=env_file) + if not old: + return new + + for key, value in new.items(): + # if key not in param_schemata ignore it + if key in param_schemata and value is not None: + param_merge_strategy = available_strategies[key] if param_merge_strategy == DEEP_MERGE: + LOG.debug("Deep Merging Parameter: %s", key) param_merge(key, value, param_schemata[key], deep_merge=True) elif param_merge_strategy == MERGE: + LOG.debug("Merging Parameter: %s", key) param_merge(key, value, param_schemata[key]) else: + LOG.debug("Overriding Parameter: %s", key) old[key] = value - return old, new_strategies + return old def merge_environments(environment_files, files, @@ -170,11 +183,10 @@ def merge_environments(environment_files, files, if section_value: if section_key in (env_fmt.PARAMETERS, env_fmt.PARAMETER_DEFAULTS): - params[section_key], new_strategies = merge_parameters( + params[section_key] = merge_parameters( params[section_key], section_value, param_schemata, strategies_in_file, available_strategies, filename) - available_strategies.update(new_strategies) else: params[section_key] = merge_map(params[section_key], section_value) diff --git a/heat/common/pluginutils.py b/heat/common/pluginutils.py index c4da0ec06..1fc9618ee 100644 --- a/heat/common/pluginutils.py +++ b/heat/common/pluginutils.py @@ -18,9 +18,17 @@ LOG = logging.getLogger(__name__) def log_fail_msg(manager, entrypoint, exception): - LOG.warning('Encountered exception while loading %(module_name)s: ' - '"%(message)s". Not using %(name)s.', - {'module_name': entrypoint.module, - 'message': getattr(exception, 'message', - str(exception)), - 'name': entrypoint.name}) + # importlib.metadata in Python 3.8 is quite old and the EntryPoint class + # does not have module. This logic is required to workaround AttributeError + # caused by that old implementation. + if hasattr(entrypoint, 'module'): + LOG.warning('Encountered exception while loading %(module_name)s: ' + '"%(message)s". Not using %(name)s.', + {'module_name': entrypoint.module, + 'message': getattr(exception, 'message', str(exception)), + 'name': entrypoint.name}) + else: + LOG.warning('Encountered exception: "%(message)s". ' + 'Not using %(name)s.', + {'message': getattr(exception, 'message', str(exception)), + 'name': entrypoint.name}) diff --git a/heat/db/sqlalchemy/types.py b/heat/db/sqlalchemy/types.py index 39766b893..4d4a213e5 100644 --- a/heat/db/sqlalchemy/types.py +++ b/heat/db/sqlalchemy/types.py @@ -23,6 +23,7 @@ loads = jsonutils.loads class LongText(types.TypeDecorator): impl = types.Text + cache_ok = True def load_dialect_impl(self, dialect): if dialect.name == 'mysql': @@ -45,6 +46,7 @@ class Json(LongText): class List(types.TypeDecorator): impl = types.Text + cache_ok = True def load_dialect_impl(self, dialect): if dialect.name == 'mysql': diff --git a/heat/engine/resources/openstack/neutron/extrarouteset.py b/heat/engine/resources/openstack/neutron/extrarouteset.py index 8b93ad8f9..c0378f2c2 100644 --- a/heat/engine/resources/openstack/neutron/extrarouteset.py +++ b/heat/engine/resources/openstack/neutron/extrarouteset.py @@ -112,7 +112,7 @@ class ExtraRouteSet(neutron.NeutronResource): def add_dependencies(self, deps): super(ExtraRouteSet, self).add_dependencies(deps) - for resource in self.stack.items(): + for resource in self.stack.values(): # depend on any RouterInterface in this template with the same # router as this router if resource.has_interface('OS::Neutron::RouterInterface'): diff --git a/heat/engine/resources/openstack/neutron/net.py b/heat/engine/resources/openstack/neutron/net.py index d0d2ebd8d..156c03042 100644 --- a/heat/engine/resources/openstack/neutron/net.py +++ b/heat/engine/resources/openstack/neutron/net.py @@ -33,11 +33,11 @@ class Net(neutron.NeutronResource): PROPERTIES = ( NAME, VALUE_SPECS, ADMIN_STATE_UP, TENANT_ID, SHARED, DHCP_AGENT_IDS, PORT_SECURITY_ENABLED, QOS_POLICY, - DNS_DOMAIN, TAGS, + DNS_DOMAIN, AVAILABILITY_ZONE_HINTS, TAGS, ) = ( 'name', 'value_specs', 'admin_state_up', 'tenant_id', 'shared', 'dhcp_agent_ids', 'port_security_enabled', 'qos_policy', - 'dns_domain', 'tags', + 'dns_domain', 'availability_zone_hints', 'tags', ) ATTRIBUTES = ( @@ -118,6 +118,12 @@ class Net(neutron.NeutronResource): update_allowed=True, support_status=support.SupportStatus(version='7.0.0') ), + AVAILABILITY_ZONE_HINTS: properties.Schema( + properties.Schema.LIST, + _('Availability zone candidates for the network. It requires the ' + 'availability_zone extension to be available.'), + support_status=support.SupportStatus(version='19.0.0') + ), TAGS: properties.Schema( properties.Schema.LIST, _('The tags to be added to the network.'), diff --git a/heat/engine/resources/openstack/neutron/provider_net.py b/heat/engine/resources/openstack/neutron/provider_net.py index 9f5a038c7..899b29590 100644 --- a/heat/engine/resources/openstack/neutron/provider_net.py +++ b/heat/engine/resources/openstack/neutron/provider_net.py @@ -37,11 +37,13 @@ class ProviderNet(net.Net): PROPERTIES = ( NAME, PROVIDER_NETWORK_TYPE, PROVIDER_PHYSICAL_NETWORK, PROVIDER_SEGMENTATION_ID, ADMIN_STATE_UP, SHARED, - PORT_SECURITY_ENABLED, ROUTER_EXTERNAL, DNS_DOMAIN, TAGS, + PORT_SECURITY_ENABLED, ROUTER_EXTERNAL, DNS_DOMAIN, + AVAILABILITY_ZONE_HINTS, TAGS, ) = ( 'name', 'network_type', 'physical_network', 'segmentation_id', 'admin_state_up', 'shared', - 'port_security_enabled', 'router_external', 'dns_domain', 'tags', + 'port_security_enabled', 'router_external', 'dns_domain', + 'availability_zone_hints', 'tags', ) @@ -119,6 +121,13 @@ class ProviderNet(net.Net): update_allowed=True, support_status=support.SupportStatus(version='15.0.0') ), + AVAILABILITY_ZONE_HINTS: properties.Schema( + properties.Schema.LIST, + _('Availability zone candidates for the network. It requires the ' + 'availability_zone extension to be available.'), + update_allowed=True, + support_status=support.SupportStatus(version='19.0.0') + ), } attributes_schema = { diff --git a/heat/engine/resources/openstack/neutron/qos.py b/heat/engine/resources/openstack/neutron/qos.py index d767050ae..a2a65adb9 100644 --- a/heat/engine/resources/openstack/neutron/qos.py +++ b/heat/engine/resources/openstack/neutron/qos.py @@ -378,10 +378,97 @@ class QoSMinimumBandwidthRule(QoSRule): return [self.resource_id, self.policy_id] +class QoSMinimumPacketRateRule(QoSRule): + """A resource for guaranteeing packet rate. + + This rule can be associated with a QoS policy, and then the policy + can be used by a neutron port to provide guaranteed packet rate QoS + capabilities. + + Depending on drivers the guarantee may be enforced on two levels. + First when a server is placed (scheduled) on physical infrastructure + and/or second in the data plane of the physical hypervisor. For details + please see Neutron documentation: + + https://docs.openstack.org/neutron/latest/admin/config-qos-min-pps.html + + The default policy usage of this resource is limited to + administrators only. + """ + + entity = 'minimum_packet_rate_rule' + + required_service_extension = 'qos-pps-minimum' + + support_status = support.SupportStatus( + status=support.SUPPORTED, + version='19.0.0', + ) + + PROPERTIES = ( + MIN_PACKET_RATE, DIRECTION + ) = ( + 'min_kpps', 'direction' + ) + + properties_schema = { + MIN_PACKET_RATE: properties.Schema( + properties.Schema.INTEGER, + _('Min packet rate in kpps.'), + required=True, + update_allowed=True, + constraints=[ + constraints.Range(min=0), + ], + ), + DIRECTION: properties.Schema( + properties.Schema.STRING, + _('Traffic direction from the point of view of the port.'), + update_allowed=True, + constraints=[ + constraints.AllowedValues(['any', 'egress', 'ingress']), + ], + default='egress', + ), + } + + properties_schema.update(QoSRule.properties_schema) + + def handle_create(self): + props = self.prepare_properties(self.properties, + self.physical_resource_name()) + props.pop(self.POLICY) + + rule = self.client().create_minimum_packet_rate_rule( + self.policy_id, + {'minimum_packet_rate_rule': props})['minimum_packet_rate_rule'] + + self.resource_id_set(rule['id']) + + def handle_delete(self): + if self.resource_id is None: + return + + with self.client_plugin().ignore_not_found: + self.client().delete_minimum_packet_rate_rule( + self.resource_id, self.policy_id) + + def handle_update(self, json_snippet, tmpl_diff, prop_diff): + if prop_diff: + self.client().update_minimum_packet_rate_rule( + self.resource_id, + self.policy_id, + {'minimum_packet_rate_rule': prop_diff}) + + def _res_get_args(self): + return [self.resource_id, self.policy_id] + + def resource_mapping(): return { 'OS::Neutron::QoSPolicy': QoSPolicy, 'OS::Neutron::QoSBandwidthLimitRule': QoSBandwidthLimitRule, 'OS::Neutron::QoSDscpMarkingRule': QoSDscpMarkingRule, 'OS::Neutron::QoSMinimumBandwidthRule': QoSMinimumBandwidthRule, + 'OS::Neutron::QoSMinimumPacketRateRule': QoSMinimumPacketRateRule, } diff --git a/heat/engine/resources/openstack/neutron/router.py b/heat/engine/resources/openstack/neutron/router.py index 1127a2c10..72f7dbbdd 100644 --- a/heat/engine/resources/openstack/neutron/router.py +++ b/heat/engine/resources/openstack/neutron/router.py @@ -35,10 +35,12 @@ class Router(neutron.NeutronResource): PROPERTIES = ( NAME, EXTERNAL_GATEWAY, VALUE_SPECS, ADMIN_STATE_UP, - L3_AGENT_ID, L3_AGENT_IDS, DISTRIBUTED, HA, TAGS, + L3_AGENT_ID, L3_AGENT_IDS, DISTRIBUTED, HA, AVAILABILITY_ZONE_HINTS, + TAGS, ) = ( 'name', 'external_gateway_info', 'value_specs', 'admin_state_up', - 'l3_agent_id', 'l3_agent_ids', 'distributed', 'ha', 'tags', + 'l3_agent_id', 'l3_agent_ids', 'distributed', 'ha', + 'availability_zone_hints', 'tags', ) _EXTERNAL_GATEWAY_KEYS = ( @@ -171,6 +173,13 @@ class Router(neutron.NeutronResource): 'do not support distributed and ha at the same time.'), support_status=support.SupportStatus(version='2015.1') ), + AVAILABILITY_ZONE_HINTS: properties.Schema( + properties.Schema.LIST, + _('Availability zone candidates for the router. It requires the ' + 'availability_zone extension to be available.'), + update_allowed=True, + support_status=support.SupportStatus(version='19.0.0') + ), TAGS: properties.Schema( properties.Schema.LIST, _('The tags to be added to the router.'), diff --git a/heat/engine/resources/openstack/nova/keypair.py b/heat/engine/resources/openstack/nova/keypair.py index b7994cc1d..25df73aca 100644 --- a/heat/engine/resources/openstack/nova/keypair.py +++ b/heat/engine/resources/openstack/nova/keypair.py @@ -22,7 +22,8 @@ from heat.engine import translation NOVA_MICROVERSIONS = (MICROVERSION_KEY_TYPE, - MICROVERSION_USER) = ('2.2', '2.10') + MICROVERSION_USER, + MICROVERSION_PUBLIC_KEY) = ('2.2', '2.10', '2.92') class KeyPair(resource.Resource): @@ -71,9 +72,10 @@ class KeyPair(resource.Resource): ), PUBLIC_KEY: properties.Schema( properties.Schema.STRING, - _('The optional public key. This allows users to supply the ' - 'public key from a pre-existing key pair. If not supplied, a ' - 'new key pair will be generated.') + _('The public key. This allows users to supply the public key ' + 'from a pre-existing key pair. In Nova api version < 2.92, ' + 'if not supplied, a new key pair will be generated. ' + 'This property is required since Nova api version 2.92.') ), KEY_TYPE: properties.Schema( properties.Schema.STRING, @@ -148,6 +150,7 @@ class KeyPair(resource.Resource): # Check if key_type is allowed to use key_type = self.properties[self.KEY_TYPE] user = self.properties[self.USER] + public_key = self.properties[self.PUBLIC_KEY] validate_props = [] c_plugin = self.client_plugin() @@ -161,6 +164,12 @@ class KeyPair(resource.Resource): 'support required api microversion.') % validate_props) raise exception.StackValidationFailed(message=msg) + if not public_key and c_plugin.is_version_supported( + MICROVERSION_PUBLIC_KEY): + msg = _('The public_key property is required by the nova API ' + 'version currently used.') + raise exception.StackValidationFailed(message=msg) + def handle_create(self): pub_key = self.properties[self.PUBLIC_KEY] or None user_id = self.properties[self.USER] diff --git a/heat/engine/resources/openstack/nova/server.py b/heat/engine/resources/openstack/nova/server.py index 7fdd31e4d..6cb782a4a 100644 --- a/heat/engine/resources/openstack/nova/server.py +++ b/heat/engine/resources/openstack/nova/server.py @@ -548,11 +548,12 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin, ), USER_DATA_UPDATE_POLICY: properties.Schema( properties.Schema.STRING, - _('Policy on how to apply a user_data update; either by ' - 'ignoring it or by replacing the entire server.'), + _('Policy on how to apply a user_data update; by ' + 'ignoring it, by replacing the entire server, ' + 'or rebuild the server.'), default='REPLACE', constraints=[ - constraints.AllowedValues(['REPLACE', 'IGNORE']), + constraints.AllowedValues(['REPLACE', 'IGNORE', 'REBUILD']), ], support_status=support.SupportStatus(version='6.0.0'), update_allowed=True @@ -1313,6 +1314,14 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin, 'kwargs': kwargs}) return prg + def _update_user_data_rebuild(self, after_props): + user_data = after_props[self.USER_DATA] + prg = progress.ServerUpdateProgress( + self.resource_id, + 'rebuild', + handler_extra={'args': (user_data,)}) + return prg + def _update_networks(self, server, after_props): updaters = [] new_networks = after_props[self.NETWORKS] @@ -1404,6 +1413,12 @@ class Server(server_base.BaseServer, sh.SchedulerHintsMixin, if self.FLAVOR in prop_diff: updaters.extend(self._update_flavor(after_props)) + if self.USER_DATA in prop_diff: + # We only care about rebuild here. The standard replace is + # dealt elsewere + if after_props[self.USER_DATA_UPDATE_POLICY] == 'REBUILD': + updaters.append(self._update_user_data_rebuild(after_props)) + if self.IMAGE in prop_diff: updaters.append(self._update_image(after_props)) elif self.ADMIN_PASS in prop_diff: diff --git a/heat/engine/resources/openstack/nova/server_group.py b/heat/engine/resources/openstack/nova/server_group.py index b4eb95662..9930033fa 100644 --- a/heat/engine/resources/openstack/nova/server_group.py +++ b/heat/engine/resources/openstack/nova/server_group.py @@ -106,6 +106,16 @@ class ServerGroup(resource.Resource): name=name, policies=policies) self.resource_id_set(server_group.id) + def needs_replace_failed(self): + if not self.resource_id: + return True + + with self.client_plugin().ignore_not_found: + self._show_resource() + return False + + return True + def physical_resource_name(self): name = self.properties[self.NAME] if name: diff --git a/heat/locale/de/LC_MESSAGES/heat.po b/heat/locale/de/LC_MESSAGES/heat.po index 093a77d63..95148ce69 100644 --- a/heat/locale/de/LC_MESSAGES/heat.po +++ b/heat/locale/de/LC_MESSAGES/heat.po @@ -14,7 +14,7 @@ msgid "" msgstr "" "Project-Id-Version: heat VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2021-11-01 17:57+0000\n" +"POT-Creation-Date: 2022-06-28 08:24+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -5553,13 +5553,6 @@ msgstr "" "Servers." msgid "" -"Policy on how to apply a user_data update; either by ignoring it or by " -"replacing the entire server." -msgstr "" -"Richtlinie zum Anwenden einer Benutzerdatenaktualisierung entweder durch " -"Ignorieren oder durch Ersetzen des gesamten Servers." - -msgid "" "Policy on how to apply an image-id update; either by requesting a server " "rebuild or by replacing the entire server." msgstr "" diff --git a/heat/policies/resource_types.py b/heat/policies/resource_types.py index 39e6d2596..3bb3fc6f1 100644 --- a/heat/policies/resource_types.py +++ b/heat/policies/resource_types.py @@ -57,6 +57,9 @@ resource_types_policies = [ name=POLICY_ROOT % 'OS::Neutron::QoSMinimumBandwidthRule', check_str=base.RULE_PROJECT_ADMIN), policy.RuleDefault( + name=POLICY_ROOT % 'OS::Neutron::QoSMinimumPacketRateRule', + check_str=base.RULE_PROJECT_ADMIN), + policy.RuleDefault( name=POLICY_ROOT % 'OS::Neutron::Segment', check_str=base.RULE_PROJECT_ADMIN), policy.RuleDefault( diff --git a/heat/tests/api/openstack_v1/test_software_configs.py b/heat/tests/api/openstack_v1/test_software_configs.py index c1516eda0..110bfaef5 100644 --- a/heat/tests/api/openstack_v1/test_software_configs.py +++ b/heat/tests/api/openstack_v1/test_software_configs.py @@ -47,6 +47,41 @@ class SoftwareConfigControllerTest(tools.ControllerTest, common.HeatTestCase): {'software_configs': []}, resp) @mock.patch.object(policy.Enforcer, 'enforce') + def test_index_limit_negative(self, mock_enforce): + self._mock_enforce_setup(mock_enforce, 'index') + params = {'limit': -1} + + with mock.patch.object( + self.controller.rpc_client, + 'list_software_configs', + return_value=[]) as mock_call: + req = self._get('/software_configs', params=params) + ex = self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.index, req, + tenant_id=self.tenant) + self.assertEqual("Value '-1' is invalid for 'limit' which only " + "accepts non-negative integer.", + str(ex)) + self.assertFalse(mock_call.called) + + @mock.patch.object(policy.Enforcer, 'enforce') + def test_index_limit_not_int(self, mock_enforce): + self._mock_enforce_setup(mock_enforce, 'index') + params = {'limit': 'not-an-int'} + + with mock.patch.object( + self.controller.rpc_client, + 'list_software_configs', + return_value=[]) as mock_call: + req = self._get('/software_configs', params=params) + ex = self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.index, req, + tenant_id=self.tenant) + self.assertEqual("Only integer is acceptable by 'limit'.", + str(ex)) + self.assertFalse(mock_call.called) + + @mock.patch.object(policy.Enforcer, 'enforce') def test_show(self, mock_enforce): self._mock_enforce_setup(mock_enforce, 'show') config_id = 'a45559cd-8736-4375-bc39-d6a7bb62ade2' diff --git a/heat/tests/api/openstack_v1/test_stacks.py b/heat/tests/api/openstack_v1/test_stacks.py index 48d1ebd61..6c1a46467 100644 --- a/heat/tests/api/openstack_v1/test_stacks.py +++ b/heat/tests/api/openstack_v1/test_stacks.py @@ -338,6 +338,20 @@ class StackControllerTest(tools.ControllerTest, common.HeatTestCase): self.assertNotIn('balrog', engine_args) @mock.patch.object(rpc_client.EngineClient, 'call') + def test_index_limit_negative(self, mock_call, mock_enforce): + self._mock_enforce_setup(mock_enforce, 'index', True) + params = {'limit': -1} + req = self._get('/stacks', params=params) + + ex = self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.index, req, + tenant_id=self.tenant) + self.assertEqual("Value '-1' is invalid for 'limit' which only " + "accepts non-negative integer.", + str(ex)) + self.assertFalse(mock_call.called) + + @mock.patch.object(rpc_client.EngineClient, 'call') def test_index_limit_not_int(self, mock_call, mock_enforce): self._mock_enforce_setup(mock_enforce, 'index', True) params = {'limit': 'not-an-int'} @@ -1936,6 +1950,31 @@ class StackControllerTest(tools.ControllerTest, common.HeatTestCase): version='1.36' ) + def test_update_timeout_negative(self, mock_enforce): + self._mock_enforce_setup(mock_enforce, 'update', True) + identity = identifier.HeatIdentifier(self.tenant, 'wibble', '6') + template = {u'Foo': u'bar'} + parameters = {u'InstanceType': u'm1.xlarge'} + body = {'template': template, + 'parameters': parameters, + 'files': {}, + 'timeout_mins': -1} + + req = self._put('/stacks/%(stack_name)s/%(stack_id)s' % identity, + json.dumps(body)) + + mock_call = self.patchobject(rpc_client.EngineClient, 'call') + ex = self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.update, req, + tenant_id=identity.tenant, + stack_name=identity.stack_name, + stack_id=identity.stack_id, + body=body) + self.assertEqual("Value '-1' is invalid for 'timeout_mins' which only " + "accepts non-negative integer.", + str(ex)) + self.assertFalse(mock_call.called) + def test_update_timeout_not_int(self, mock_enforce): self._mock_enforce_setup(mock_enforce, 'update', True) identity = identifier.HeatIdentifier(self.tenant, 'wibble', '6') @@ -2149,6 +2188,31 @@ class StackControllerTest(tools.ControllerTest, common.HeatTestCase): version='1.36' ) + def test_update_with_patch_timeout_negative(self, mock_enforce): + self._mock_enforce_setup(mock_enforce, 'update_patch', True) + identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6') + template = {u'Foo': u'bar'} + parameters = {u'InstanceType': u'm1.xlarge'} + body = {'template': template, + 'parameters': parameters, + 'files': {}, + 'timeout_mins': -1} + + req = self._patch('/stacks/%(stack_name)s/%(stack_id)s' % identity, + json.dumps(body)) + + mock_call = self.patchobject(rpc_client.EngineClient, 'call') + ex = self.assertRaises(webob.exc.HTTPBadRequest, + self.controller.update_patch, req, + tenant_id=identity.tenant, + stack_name=identity.stack_name, + stack_id=identity.stack_id, + body=body) + self.assertEqual("Value '-1' is invalid for 'timeout_mins' which only " + "accepts non-negative integer.", + str(ex)) + self.assertFalse(mock_call.called) + def test_update_with_patch_timeout_not_int(self, mock_enforce): self._mock_enforce_setup(mock_enforce, 'update_patch', True) identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6') diff --git a/heat/tests/clients/test_swift_client.py b/heat/tests/clients/test_swift_client.py index eb3146d98..ae04ae42c 100644 --- a/heat/tests/clients/test_swift_client.py +++ b/heat/tests/clients/test_swift_client.py @@ -76,7 +76,7 @@ class SwiftUtilsTest(SwiftClientPluginTestCase): url = self.swift_plugin.get_temp_url(container_name, obj_name) self.assertFalse(self.swift_client.post_account.called) regexp = ("http://fake-host.com:8080/v1/AUTH_demo/%s" - r"/%s\?temp_url_sig=[0-9a-f]{40}&" + r"/%s\?temp_url_sig=[0-9a-f]{40,64}&" "temp_url_expires=[0-9]{10}" % (container_name, obj_name)) self.assertThat(url, matchers.MatchesRegex(regexp)) @@ -119,7 +119,7 @@ class SwiftUtilsTest(SwiftClientPluginTestCase): self.assertTrue(self.swift_client.put_container.called) self.assertTrue(self.swift_client.put_object.called) regexp = ("http://fake-host.com:8080/v1/AUTH_demo/%s" - r"/%s\?temp_url_sig=[0-9a-f]{40}&" + r"/%s\?temp_url_sig=[0-9a-f]{40,64}&" "temp_url_expires=[0-9]{10}" % (container_name, obj_name)) self.assertThat(url, matchers.MatchesRegex(regexp)) diff --git a/heat/tests/openstack/heat/test_swiftsignal.py b/heat/tests/openstack/heat/test_swiftsignal.py index c3d2db0d6..64a15df1b 100644 --- a/heat/tests/openstack/heat/test_swiftsignal.py +++ b/heat/tests/openstack/heat/test_swiftsignal.py @@ -136,7 +136,7 @@ class SwiftSignalHandleTest(common.HeatTestCase): obj_name = "%s-%s-abcdefghijkl" % (st.name, handle.name) regexp = ("http://fake-host.com:8080/v1/AUTH_test_tenant/%s/test_st-" "test_wait_condition_handle-abcdefghijkl" - r"\?temp_url_sig=[0-9a-f]{40}&temp_url_expires=[0-9]{10}" + r"\?temp_url_sig=[0-9a-f]{40,64}&temp_url_expires=[0-9]{10}" % st.id) res_id = st.resources['test_wait_condition_handle'].resource_id self.assertEqual(res_id, handle.physical_resource_name()) @@ -718,7 +718,8 @@ class SwiftSignalTest(common.HeatTestCase): self.assertEqual(('CREATE', 'COMPLETE'), st.state) expected = ('http://fake-host.com:8080/v1/AUTH_test_tenant/%s/' r'test_st-test_wait_condition_handle-abcdefghijkl\?temp_' - 'url_sig=[0-9a-f]{40}&temp_url_expires=[0-9]{10}') % st.id + 'url_sig=[0-9a-f]{40,64}&' + 'temp_url_expires=[0-9]{10}') % st.id self.assertThat(handle.FnGetAtt('endpoint'), matchers.MatchesRegex(expected)) @@ -748,7 +749,7 @@ class SwiftSignalTest(common.HeatTestCase): self.assertEqual(('CREATE', 'COMPLETE'), st.state) expected = ("curl -i -X PUT 'http://fake-host.com:8080/v1/" "AUTH_test_tenant/%s/test_st-test_wait_condition_" - r"handle-abcdefghijkl\?temp_url_sig=[0-9a-f]{40}&" + r"handle-abcdefghijkl\?temp_url_sig=[0-9a-f]{40,64}&" "temp_url_expires=[0-9]{10}'") % st.id self.assertThat(handle.FnGetAtt('curl_cli'), matchers.MatchesRegex(expected)) diff --git a/heat/tests/openstack/neutron/test_neutron_net.py b/heat/tests/openstack/neutron/test_neutron_net.py index 63a68c582..f45479806 100644 --- a/heat/tests/openstack/neutron/test_neutron_net.py +++ b/heat/tests/openstack/neutron/test_neutron_net.py @@ -40,6 +40,8 @@ resources: - 28c25a04-3f73-45a7-a2b4-59e183943ddc port_security_enabled: False dns_domain: openstack.org. + availability_zone_hints: + - az1 value_specs: {'mtu': 1500} tags: - tag1 @@ -186,6 +188,7 @@ class NeutronNetTest(common.HeatTestCase): 'dns_domain': u'openstack.org.', 'shared': True, 'port_security_enabled': False, + 'availability_zone_hints': ['az1'], 'mtu': 1500} } ) diff --git a/heat/tests/openstack/neutron/test_neutron_provider_net.py b/heat/tests/openstack/neutron/test_neutron_provider_net.py index a528c4d4d..9909f160e 100644 --- a/heat/tests/openstack/neutron/test_neutron_provider_net.py +++ b/heat/tests/openstack/neutron/test_neutron_provider_net.py @@ -40,6 +40,8 @@ resources: segmentation_id: 101 router_external: False shared: true + availability_zone_hints: + - az1 tags: - tag1 - tag2 @@ -128,7 +130,8 @@ class NeutronProviderNetTest(common.HeatTestCase): 'provider:physical_network': 'physnet_1', 'provider:segmentation_id': '101', 'router:external': False, - 'shared': True + 'shared': True, + 'availability_zone_hints': ['az1'], } }) self.mockclient.replace_tag.assert_called_with( @@ -180,7 +183,8 @@ class NeutronProviderNetTest(common.HeatTestCase): 'provider:physical_network': 'physnet_1', 'provider:segmentation_id': '101', 'router:external': False, - 'shared': True} + 'shared': True, + 'availability_zone_hints': ['az1']} }) self.mockclient.replace_tag.assert_called_with( resource_type, @@ -243,6 +247,7 @@ class NeutronProviderNetTest(common.HeatTestCase): 'port_security_enabled': True, 'segmentation_id': None, 'router_external': False, + 'availability_zone_hints': [], 'tags': ['tag1', 'tag2'], } diff --git a/heat/tests/openstack/neutron/test_neutron_router.py b/heat/tests/openstack/neutron/test_neutron_router.py index 442668a2b..6c489301b 100644 --- a/heat/tests/openstack/neutron/test_neutron_router.py +++ b/heat/tests/openstack/neutron/test_neutron_router.py @@ -36,6 +36,8 @@ resources: properties: l3_agent_ids: - 792ff887-6c85-4a56-b518-23f24fa65581 + availability_zone_hints: + - az1 router_interface: type: OS::Neutron::RouterInterface @@ -262,6 +264,7 @@ class NeutronRouterTest(common.HeatTestCase): create_body = { 'router': { 'name': utils.PhysName(stack.name, 'router'), + 'availability_zone_hints': ['az1'], 'admin_state_up': True}} router_base_info = { 'router': { diff --git a/heat/tests/openstack/neutron/test_qos.py b/heat/tests/openstack/neutron/test_qos.py index d46eb64f4..306b15b4f 100644 --- a/heat/tests/openstack/neutron/test_qos.py +++ b/heat/tests/openstack/neutron/test_qos.py @@ -70,6 +70,19 @@ resources: tenant_id: d66c74c01d6c41b9846088c1ad9634d0 ''' +minimum_packet_rate_rule_template = ''' +heat_template_version: 2021-04-16 +description: This template to define a neutron minimum packet rate rule. +resources: + my_minimum_packet_rate_rule: + type: OS::Neutron::QoSMinimumPacketRateRule + properties: + policy: 477e8273-60a7-4c41-b683-fdb0bc7cd151 + min_kpps: 1000 + direction: any + tenant_id: d66c74c01d6c41b9846088c1ad9634d0 +''' + class NeutronQoSPolicyTest(common.HeatTestCase): def setUp(self): @@ -515,3 +528,118 @@ class NeutronQoSMinimumBandwidthRuleTest(common.HeatTestCase): self.neutronclient.show_minimum_bandwidth_rule.assert_called_once_with( self.minimum_bandwidth_rule.resource_id, self.policy_id) + + +class NeutronQoSMinimumPacketRateRuleTest(common.HeatTestCase): + def setUp(self): + super(NeutronQoSMinimumPacketRateRuleTest, self).setUp() + + self.ctx = utils.dummy_context() + tpl = template_format.parse(minimum_packet_rate_rule_template) + self.stack = stack.Stack( + self.ctx, + 'neutron_minimum_packet_rate_rule_test', + template.Template(tpl) + ) + + self.neutronclient = mock.MagicMock() + self.patchobject(neutron.NeutronClientPlugin, 'has_extension', + return_value=True) + self.minimum_packet_rate_rule = self.stack[ + 'my_minimum_packet_rate_rule'] + self.minimum_packet_rate_rule.client = mock.MagicMock( + return_value=self.neutronclient) + self.find_mock = self.patchobject( + neutron.neutronV20, + 'find_resourceid_by_name_or_id') + self.policy_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151' + self.find_mock.return_value = self.policy_id + + def test_rule_handle_create(self): + rule = { + 'minimum_packet_rate_rule': { + 'id': 'cf0eab12-ef8b-4a62-98d0-70576583c17a', + 'min_kpps': 1000, + 'direction': 'any', + 'tenant_id': 'd66c74c01d6c41b9846088c1ad9634d0' + } + } + + create_props = {'min_kpps': 1000, + 'direction': 'any', + 'tenant_id': 'd66c74c01d6c41b9846088c1ad9634d0'} + self.neutronclient.create_minimum_packet_rate_rule.return_value = rule + + self.minimum_packet_rate_rule.handle_create() + self.assertEqual('cf0eab12-ef8b-4a62-98d0-70576583c17a', + self.minimum_packet_rate_rule.resource_id) + self.neutronclient.create_minimum_packet_rate_rule.\ + assert_called_once_with( + self.policy_id, + {'minimum_packet_rate_rule': create_props}) + + def test_rule_handle_delete(self): + rule_id = 'cf0eab12-ef8b-4a62-98d0-70576583c17a' + self.minimum_packet_rate_rule.resource_id = rule_id + self.neutronclient.delete_minimum_packet_rate_rule.return_value = None + + self.assertIsNone(self.minimum_packet_rate_rule.handle_delete()) + self.neutronclient.delete_minimum_packet_rate_rule.\ + assert_called_once_with(rule_id, self.policy_id) + + def test_rule_handle_delete_not_found(self): + rule_id = 'cf0eab12-ef8b-4a62-98d0-70576583c17a' + self.minimum_packet_rate_rule.resource_id = rule_id + not_found = self.neutronclient.NotFound + self.neutronclient.delete_minimum_packet_rate_rule.side_effect =\ + not_found + + self.assertIsNone(self.minimum_packet_rate_rule.handle_delete()) + self.neutronclient.delete_minimum_packet_rate_rule.\ + assert_called_once_with(rule_id, self.policy_id) + + def test_rule_handle_delete_resource_id_is_none(self): + self.minimum_packet_rate_rule.resource_id = None + self.assertIsNone(self.minimum_packet_rate_rule.handle_delete()) + self.assertEqual( + 0, + self.neutronclient.minimum_packet_rate_rule.call_count) + + def test_rule_handle_update(self): + rule_id = 'cf0eab12-ef8b-4a62-98d0-70576583c17a' + self.minimum_packet_rate_rule.resource_id = rule_id + + prop_diff = { + 'min_kpps': 500 + } + + self.minimum_packet_rate_rule.handle_update( + json_snippet={}, + tmpl_diff={}, + prop_diff=prop_diff.copy()) + + self.neutronclient.update_minimum_packet_rate_rule.\ + assert_called_once_with( + rule_id, + self.policy_id, + {'minimum_packet_rate_rule': prop_diff}) + + def test_rule_get_attr(self): + self.minimum_packet_rate_rule.resource_id = 'test rule' + rule = { + 'minimum_packet_rate_rule': { + 'id': 'cf0eab12-ef8b-4a62-98d0-70576583c17a', + 'min_kpps': 1000, + 'direction': 'egress', + 'tenant_id': 'd66c74c01d6c41b9846088c1ad9634d0' + } + } + self.neutronclient.show_minimum_packet_rate_rule.return_value = rule + + self.assertEqual(rule['minimum_packet_rate_rule'], + self.minimum_packet_rate_rule.FnGetAtt('show')) + + self.neutronclient.show_minimum_packet_rate_rule.\ + assert_called_once_with( + self.minimum_packet_rate_rule.resource_id, + self.policy_id) diff --git a/heat/tests/openstack/nova/test_keypair.py b/heat/tests/openstack/nova/test_keypair.py index 29c92c0f0..83dd116d3 100644 --- a/heat/tests/openstack/nova/test_keypair.py +++ b/heat/tests/openstack/nova/test_keypair.py @@ -201,6 +201,29 @@ class NovaKeyPairTest(common.HeatTestCase): return_value='2.1') self._test_validate(user='user_A') + def test_validate_public_key(self): + self.patchobject(nova.NovaClientPlugin, 'get_max_microversion', + return_value='2.92') + template = copy.deepcopy(self.kp_template) + template['resources']['kp']['properties']['public_key'] = 'dummy' + stack = utils.parse_stack(template) + definition = stack.t.resource_definitions(stack)['kp'] + kp_res = keypair.KeyPair('kp', definition, stack) + kp_res.validate() + + def test_validate_public_key_fail(self): + self.patchobject(nova.NovaClientPlugin, 'get_max_microversion', + return_value='2.92') + template = copy.deepcopy(self.kp_template) + stack = utils.parse_stack(template) + definition = stack.t.resource_definitions(stack)['kp'] + kp_res = keypair.KeyPair('kp', definition, stack) + error = self.assertRaises(exception.StackValidationFailed, + kp_res.validate) + msg = ('The public_key property is required by the nova API version ' + 'currently used.') + self.assertIn(msg, str(error)) + def test_check_key(self): res = self._get_test_resource(self.kp_template) res.state_set(res.CREATE, res.COMPLETE, 'for test') diff --git a/heat/tests/openstack/nova/test_server.py b/heat/tests/openstack/nova/test_server.py index 529a22d57..1ed657ff2 100644 --- a/heat/tests/openstack/nova/test_server.py +++ b/heat/tests/openstack/nova/test_server.py @@ -2363,6 +2363,26 @@ class ServersTest(common.HeatTestCase): self.assertEqual((server.UPDATE, server.COMPLETE), server.state) @mock.patch.object(servers.Server, 'prepare_for_replace') + @mock.patch.object(nova.NovaClientPlugin, 'client') + def test_server_update_server_userdata_rebuild(self, mock_create, + mock_replace): + stack_name = 'update_udreplace' + (tmpl, stack) = self._setup_test_stack(stack_name) + self.patchobject(servers.Server, 'check_update_complete', + return_value=True) + + resource_defns = tmpl.resource_definitions(stack) + server = servers.Server('server_update_userdata_ignore', + resource_defns['WebServer'], stack) + + update_props = tmpl.t['Resources']['WebServer']['Properties'].copy() + update_props['user_data'] = 'changed' + update_props['user_data_update_policy'] = 'REBUILD' + update_template = server.t.freeze(properties=update_props) + updater = scheduler.TaskRunner(server.update, update_template) + self.assertRaises(resource.UpdateReplace, updater) + + @mock.patch.object(servers.Server, 'prepare_for_replace') def test_server_update_image_replace(self, mock_replace): stack_name = 'update_imgrep' (tmpl, stack) = self._setup_test_stack(stack_name) diff --git a/heat/tests/openstack/nova/test_server_group.py b/heat/tests/openstack/nova/test_server_group.py index c2dfda484..88f28b5c5 100644 --- a/heat/tests/openstack/nova/test_server_group.py +++ b/heat/tests/openstack/nova/test_server_group.py @@ -14,6 +14,9 @@ import json from unittest import mock +from novaclient import exceptions +from oslo_utils import excutils + from heat.common import template_format from heat.engine import scheduler from heat.tests import common @@ -52,12 +55,23 @@ class NovaServerGroupTest(common.HeatTestCase): # create mock clients and objects nova = mock.MagicMock() self.sg.client = mock.MagicMock(return_value=nova) - mock_plugin = mock.MagicMock() - self.patchobject(mock_plugin, - 'is_version_supported', - return_value=True) + + class FakeNovaPlugin(object): + + @excutils.exception_filter + def ignore_not_found(self, ex): + if not isinstance(ex, exceptions.NotFound): + raise ex + + def is_version_supported(self, version): + return True + + def is_conflict(self, ex): + return False + + self.patchobject(excutils.exception_filter, '__exit__') self.patchobject(self.sg, 'client_plugin', - return_value=mock_plugin) + return_value=FakeNovaPlugin()) self.sg_mgr = nova.server_groups def _create_sg(self, name): @@ -109,3 +123,21 @@ class NovaServerGroupTest(common.HeatTestCase): self.sg.client().server_groups = s_groups self.assertEqual({'server_gr': 'info'}, self.sg.FnGetAtt('show')) s_groups.get.assert_called_once_with('test') + + def test_needs_replace_failed(self): + self._create_sg('test') + self.sg.state_set(self.sg.CREATE, self.sg.FAILED) + mock_show_resource = self.patchobject(self.sg, '_show_resource') + mock_show_resource.side_effect = [exceptions.NotFound(404), None] + + self.sg.resource_id = None + self.assertTrue(self.sg.needs_replace_failed()) + self.assertEqual(0, mock_show_resource.call_count) + + self.sg.resource_id = 'sg_id' + self.assertTrue(self.sg.needs_replace_failed()) + self.assertEqual(1, mock_show_resource.call_count) + + mock_show_resource.return_value = None + self.assertFalse(self.sg.needs_replace_failed()) + self.assertEqual(2, mock_show_resource.call_count) diff --git a/heat/tests/test_common_env_util.py b/heat/tests/test_common_env_util.py index dff4c9c3d..07e077098 100644 --- a/heat/tests/test_common_env_util.py +++ b/heat/tests/test_common_env_util.py @@ -135,7 +135,7 @@ class TestMergeEnvironments(common.HeatTestCase): def test_merge_envs_with_specified_default(self): merge_strategies = {'default': 'deep_merge'} - self.env_2['parameter_merge_strategies'] = merge_strategies + self.env_1['parameter_merge_strategies'] = merge_strategies files = {'env_1': json.dumps(self.env_1), 'env_2': json.dumps(self.env_2)} environment_files = ['env_1', 'env_2'] @@ -169,7 +169,7 @@ class TestMergeEnvironments(common.HeatTestCase): 'lst_value2': 'merge', 'json_value1': 'deep_merge'} - self.env_2['parameter_merge_strategies'] = merge_strategies + self.env_1['parameter_merge_strategies'] = merge_strategies files = {'env_1': json.dumps(self.env_1), 'env_2': json.dumps(self.env_2)} @@ -202,7 +202,13 @@ class TestMergeEnvironments(common.HeatTestCase): 'lst_value1': "merge", 'json_value1': "deep_merge"} - self.env_2['parameter_merge_strategies'] = merge_strategies + env3_merge_strategies = { + 'default': "overwrite", + 'lst_value1': "deep_merge", + 'json_value1': "merge"} + + self.env_1['parameter_merge_strategies'] = merge_strategies + self.env_3['parameter_merge_strategies'] = env3_merge_strategies files = {'env_1': json.dumps(self.env_1), 'env_2': json.dumps(self.env_2), @@ -221,7 +227,12 @@ class TestMergeEnvironments(common.HeatTestCase): 'default': "overwrite", 'lst_value2': "merge"} - self.env_2['parameter_merge_strategies'] = merge_strategies + env4_merge_strategies = { + 'default': "overwrite", + 'lst_value2': "overwrite"} + + self.env_1['parameter_merge_strategies'] = merge_strategies + self.env_4['parameter_merge_strategies'] = env4_merge_strategies files = {'env_1': json.dumps(self.env_1), 'env_2': json.dumps(self.env_2), @@ -264,9 +275,9 @@ class TestMergeEnvironments(common.HeatTestCase): self.assertEqual({'value1': 0}, self.params['parameter_defaults']) def test_merge_envs_with_zeros_in_maps(self): - env1 = {'parameter_defaults': {'value1': {'foo': 1}}} - env2 = {'parameter_defaults': {'value1': {'foo': 0}}, + env1 = {'parameter_defaults': {'value1': {'foo': 1}}, 'parameter_merge_strategies': {'value1': 'deep_merge'}} + env2 = {'parameter_defaults': {'value1': {'foo': 0}}} files = {'env_1': json.dumps(env1), 'env_2': json.dumps(env2)} environment_files = ['env_1', 'env_2'] diff --git a/heat/tests/test_common_pluginutils.py b/heat/tests/test_common_pluginutils.py new file mode 100644 index 000000000..7a6264543 --- /dev/null +++ b/heat/tests/test_common_pluginutils.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# +# 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 importlib.metadata as importlib_metadata +from unittest import mock + +from heat.common import pluginutils +from heat.tests import common + + +class TestPluginUtil(common.HeatTestCase): + + def test_log_fail_msg(self): + ep = importlib_metadata.EntryPoint( + name=None, group=None, + value='package.module:attr [extra1, extra2]') + + exc = Exception('Something went wrong') + pluginutils.log_fail_msg(mock.Mock(), ep, exc) + self.assertIn("Something went wrong", self.LOG.output) diff --git a/heat/tests/utils.py b/heat/tests/utils.py index cf3e383f8..19fc1a06f 100644 --- a/heat/tests/utils.py +++ b/heat/tests/utils.py @@ -220,15 +220,19 @@ class JsonRepr(object): class ForeignKeyConstraintFixture(fixtures.Fixture): - def __init__(self, sqlite_fk=True): - self.enable_fkc = sqlite_fk - def _setUp(self): - new_context = db_api.db_context.make_new_manager() - new_context.configure(sqlite_fk=self.enable_fkc) + def __init__(self): + self.engine = get_engine() - self.useFixture(fixtures.MockPatchObject(db_api, '_facade', None)) - self.addCleanup(db_api.db_context.patch_factory(new_context._factory)) + def _setUp(self): + if self.engine.name == 'sqlite': + self.engine.execute("PRAGMA foreign_keys=ON") + + def disable_fks(): + with self.engine.connect() as conn: + conn.connection.rollback() + conn.execute("PRAGMA foreign_keys=OFF") + self.addCleanup(disable_fks) class AnyInstance(object): diff --git a/heat_integrationtests/cleanup_test_env.sh b/heat_integrationtests/cleanup_test_env.sh index 12791c9fd..8cc5fb1ce 100755 --- a/heat_integrationtests/cleanup_test_env.sh +++ b/heat_integrationtests/cleanup_test_env.sh @@ -30,4 +30,4 @@ openstack flavor delete m1.heat_int openstack flavor delete m1.heat_micro # delete the image created -openstack image delete Fedora-Cloud-Base-33-1.2.x86_64 +openstack image delete Fedora-Cloud-Base-36-1.5.x86_64 diff --git a/heat_integrationtests/functional/test_template_resource.py b/heat_integrationtests/functional/test_template_resource.py index f2a513b3a..34ecab119 100644 --- a/heat_integrationtests/functional/test_template_resource.py +++ b/heat_integrationtests/functional/test_template_resource.py @@ -15,7 +15,6 @@ import json from heatclient import exc as heat_exceptions import yaml -from heat_integrationtests.common import test from heat_integrationtests.functional import functional_base @@ -517,14 +516,13 @@ class TemplateResourceUpdateFailedTest(functional_base.FunctionalTestsBase): main_template = ''' HeatTemplateFormatVersion: '2012-12-12' Resources: - keypair: - Type: OS::Nova::KeyPair + test: + Type: OS::Heat::TestResource Properties: - name: replace-this - save_private_key: false + fail: replace-this server: Type: server_fail.yaml - DependsOn: keypair + DependsOn: test ''' nested_templ = ''' HeatTemplateFormatVersion: '2012-12-12' @@ -535,21 +533,18 @@ Resources: def setUp(self): super(TemplateResourceUpdateFailedTest, self).setUp() - self.assign_keypair() def test_update_on_failed_create(self): - # create a stack with "server" dependent on "keypair", but - # keypair fails, so "server" is not created properly. + # create a stack with "server" dependent on "test", but + # "test" fails, so "server" is not created properly. # We then fix the template and it should succeed. - broken_templ = self.main_template.replace('replace-this', - self.keypair_name) + broken_templ = self.main_template.replace('replace-this', 'true') stack_identifier = self.stack_create( template=broken_templ, files={'server_fail.yaml': self.nested_templ}, expected_status='CREATE_FAILED') - fixed_templ = self.main_template.replace('replace-this', - test.rand_name()) + fixed_templ = self.main_template.replace('replace-this', 'false') self.update_stack(stack_identifier, fixed_templ, files={'server_fail.yaml': self.nested_templ}) diff --git a/heat_integrationtests/prepare_test_env.sh b/heat_integrationtests/prepare_test_env.sh index 45b86e493..d4e440f73 100755 --- a/heat_integrationtests/prepare_test_env.sh +++ b/heat_integrationtests/prepare_test_env.sh @@ -46,7 +46,7 @@ function _config_iniset { iniset $conf_file heat_plugin instance_type m1.heat_int iniset $conf_file heat_plugin minimal_instance_type m1.heat_micro - iniset $conf_file heat_plugin image_ref Fedora-Cloud-Base-33-1.2.x86_64 + iniset $conf_file heat_plugin image_ref Fedora-Cloud-Base-36-1.5.x86_64 iniset $conf_file heat_plugin minimal_image_ref $default_image_name iniset $conf_file heat_plugin hidden_stack_tag hidden diff --git a/lower-constraints.txt b/lower-constraints.txt deleted file mode 100644 index fae3b3a64..000000000 --- a/lower-constraints.txt +++ /dev/null @@ -1,161 +0,0 @@ -alembic==0.9.8 -amqp==2.6.0 -aodhclient==0.9.0 -appdirs==1.4.3 -asn1crypto==0.24.0 -Babel==2.3.4 -bandit==1.1.0 -bcrypt==3.1.4 -cachetools==2.0.1 -certifi==2018.1.18 -cffi==1.14.0 -chardet==3.0.4 -cliff==2.11.0 -cmd2==0.8.1 -contextlib2==0.5.5 -coverage==4.0 -croniter==0.3.4 -cryptography==2.5 -ddt==1.4.1 -debtcollector==1.19.0 -decorator==4.3.0 -deprecation==2.0 -doc8==0.8.1 -docker-pycreds==0.2.2 -docker==3.1.1 -docutils==0.13.1 -dogpile.cache==0.6.5 -enum-compat==0.0.2 -eventlet==0.18.2 -extras==1.0.0 -fasteners==0.14.1 -fixtures==3.0.0 -flake8==3.7.0 -future==0.16.0 -futurist==1.6.0 -gitdb2==2.0.3 -GitPython==2.1.8 -greenlet==0.4.17 -idna==2.6 -iso8601==0.1.12 -Jinja2==2.10 -jmespath==0.9.3 -jsonpatch==1.21 -jsonpointer==2.0 -jsonschema==2.6.0 -keystoneauth1==3.18.0 -keystonemiddleware==5.1.0 -kombu==5.0.1 -linecache2==1.0.0 -lxml==4.5.0 -Mako==1.0.7 -MarkupSafe==1.1.1 -mccabe==0.6.0 -mock==3.0.3 -monotonic==1.4 -mox3==0.28.0 -msgpack==0.5.6 -msgpack-python==0.5.6 -munch==2.2.0 -netaddr==0.7.18 -netifaces==0.10.6 -neutron-lib==1.14.0 -openstacksdk==0.28.0 -os-client-config==1.29.0 -os-service-types==1.2.0 -osc-lib==1.10.0 -oslo.cache==1.26.0 -oslo.concurrency==3.26.0 -oslo.config==6.8.0 -oslo.context==2.22.0 -oslo.db==6.0.0 -oslo.i18n==3.20.0 -oslo.log==4.3.0 -oslo.messaging==5.29.0 -oslo.middleware==3.31.0 -oslo.policy==3.7.0 -oslo.reports==1.18.0 -oslo.serialization==2.25.0 -oslo.service==1.24.0 -oslo.upgradecheck==1.3.0 -oslo.utils==4.5.0 -oslo.versionedobjects==1.31.2 -oslotest==3.2.0 -osprofiler==1.4.0 -packaging==20.4 -paramiko==2.7.1 -Paste==2.0.3 -PasteDeploy==1.5.0 -pbr==3.1.1 -pika-pool==0.1.3 -pika==0.10.0 -ply==3.11 -prettytable==0.7.2 -psutil==5.4.3 -pyasn1==0.4.2 -pycadf==2.7.0 -pycparser==2.18 -Pygments==2.2.0 -pyinotify==0.9.6 -PyMySQL==0.8.0 -PyNaCl==1.2.1 -pyOpenSSL==17.5.0 -pyparsing==2.2.0 -pyperclip==1.6.0 -python-barbicanclient==4.5.2 -python-blazarclient===1.0.1 -python-cinderclient==3.3.0 -python-dateutil==2.7.0 -python-designateclient==2.7.0 -python-editor==1.0.3 -python-glanceclient==2.8.0 -python-heatclient==1.10.0 -python-ironicclient==2.8.0 -python-keystoneclient==3.8.0 -python-magnumclient==2.3.0 -python-manilaclient==1.16.0 -python-mimeparse==1.6.0 -python-mistralclient==3.1.0 -python-monascaclient==1.12.0 -python-neutronclient==6.14.0 -python-novaclient==9.1.0 -python-octaviaclient==1.8.0 -python-openstackclient==3.12.0 -python-saharaclient==1.4.0 -python-subunit==1.2.0 -python-swiftclient==3.2.0 -python-troveclient==2.2.0 -python-vitrageclient==2.7.0 -python-zaqarclient==1.3.0 -python-zunclient==3.4.0 -pytz==2013.6 -PyYAML==5.1 -repoze.lru==0.7 -requests==2.23.0 -requestsexceptions==1.4.0 -rfc3986==1.2.0 -Routes==2.3.1 -simplejson==3.13.2 -smmap2==2.0.3 -sqlalchemy-migrate==0.13.0 -SQLAlchemy==1.0.10 -sqlparse==0.2.4 -statsd==3.2.2 -stestr==2.0.0 -stevedore==3.1.0 -tempest==17.1.0 -Tempita==0.5.2 -tenacity==6.1.0 -testresources==2.0.0 -testscenarios==0.4 -testtools==2.2.0 -traceback2==1.4.0 -unittest2==1.1.0 -urllib3==1.22 -vine==1.1.4 -voluptuous==0.11.1 -warlock==1.2.0 -WebOb==1.7.1 -websocket-client==0.47.0 -wrapt==1.10.11 -yaql==1.1.3 diff --git a/releasenotes/notes/add-rebuild-for-user_data_update_policy-b1a229f3f551ea4b.yaml b/releasenotes/notes/add-rebuild-for-user_data_update_policy-b1a229f3f551ea4b.yaml new file mode 100644 index 000000000..70aee6684 --- /dev/null +++ b/releasenotes/notes/add-rebuild-for-user_data_update_policy-b1a229f3f551ea4b.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adding REBUILD option for user_data_update_policy so that + changes to user_data can be updated instead of a new create. diff --git a/releasenotes/notes/availability_zone_hints_Neutron_network_router-d01df1463193d9e6.yaml b/releasenotes/notes/availability_zone_hints_Neutron_network_router-d01df1463193d9e6.yaml new file mode 100644 index 000000000..83ee5628f --- /dev/null +++ b/releasenotes/notes/availability_zone_hints_Neutron_network_router-d01df1463193d9e6.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds the 'availability_zone_hints' property for the OS::Neutron::Router, + OS::Neutron::Net and OS::Neutron::ProviderNet resources. diff --git a/releasenotes/notes/drop-python-3-6-and-3-7-69dcd178c443e177.yaml b/releasenotes/notes/drop-python-3-6-and-3-7-69dcd178c443e177.yaml new file mode 100644 index 000000000..3d5e4e3d3 --- /dev/null +++ b/releasenotes/notes/drop-python-3-6-and-3-7-69dcd178c443e177.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + Python 3.6 & 3.7 support has been dropped. The minimum version of Python now + supported is Python 3.8.
\ No newline at end of file diff --git a/releasenotes/notes/neutron-qos-minimum-packet-rate-rule-e58e9ced636320f1.yaml b/releasenotes/notes/neutron-qos-minimum-packet-rate-rule-e58e9ced636320f1.yaml new file mode 100644 index 000000000..2e8af66ee --- /dev/null +++ b/releasenotes/notes/neutron-qos-minimum-packet-rate-rule-e58e9ced636320f1.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Added ``OS::Neutron::QoSMinimumPacketRateRule`` resource to support + ``minimum_packet_rate_rule`` in Neutron QoS. This resource depends + on Neutron API extension ``qos-pps-minimum`` and according + to the default policy it is admin-only. diff --git a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po index 68b70f458..e313682df 100644 --- a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po +++ b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po @@ -3,15 +3,16 @@ # Andi Chandler <andi@gowling.com>, 2019. #zanata # Andi Chandler <andi@gowling.com>, 2020. #zanata # Andi Chandler <andi@gowling.com>, 2021. #zanata +# Andi Chandler <andi@gowling.com>, 2022. #zanata msgid "" msgstr "" "Project-Id-Version: openstack-heat\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-11-01 17:53+0000\n" +"POT-Creation-Date: 2022-06-28 08:22+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2021-09-18 07:40+0000\n" +"PO-Revision-Date: 2022-06-25 03:50+0000\n" "Last-Translator: Andi Chandler <andi@gowling.com>\n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" @@ -58,6 +59,9 @@ msgstr "13.0.0" msgid "13.0.1" msgstr "13.0.1" +msgid "13.1.0-22" +msgstr "13.1.0-22" + msgid "14.0.0" msgstr "14.0.0" @@ -67,12 +71,18 @@ msgstr "14.2.0" msgid "15.0.0" msgstr "15.0.0" -msgid "15.0.0-21" -msgstr "15.0.0-21" +msgid "15.1.0" +msgstr "15.1.0" msgid "16.0.0" msgstr "16.0.0" +msgid "17.0.0" +msgstr "17.0.0" + +msgid "18.0.0" +msgstr "18.0.0" + msgid "5.0.1" msgstr "5.0.1" @@ -467,6 +477,17 @@ msgstr "" "properties defined on the Glance images." msgid "" +"Added ``OS::Neutron::QoSMinimumPacketRateRule`` resource to support " +"``minimum_packet_rate_rule`` in Neutron QoS. This resource depends on " +"Neutron API extension ``qos-pps-minimum`` and according to the default " +"policy it is admin-only." +msgstr "" +"Added ``OS::Neutron::QoSMinimumPacketRateRule`` resource to support " +"``minimum_packet_rate_rule`` in Neutron QoS. This resource depends on " +"Neutron API extension ``qos-pps-minimum`` and according to the default " +"policy it is admin-only." + +msgid "" "Added ``dns_domain`` property to resource type ``OS::Neutron::ProviderNet``. " "This specifies the DNS domain to use when publishing DNS records for ports " "on this network." @@ -598,6 +619,13 @@ msgstr "" "stack get response. If version of Heat API is lower than 1.19, outputs " "resolve in Heat client as well as before." +msgid "" +"Adding REBUILD option for user_data_update_policy so that changes to " +"user_data can be updated instead of a new create." +msgstr "" +"Adding REBUILD option for user_data_update_policy so that changes to " +"user_data can be updated instead of a new create." + msgid "Adds REST api support to cancel a stack create/update without rollback." msgstr "" "Adds REST API support to cancel a stack create/update without rollback." @@ -730,6 +758,13 @@ msgstr "" "resource. This enables migration from non routed network to a routed network." msgid "" +"Adds the 'availability_zone_hints' property for the OS::Neutron::Router, OS::" +"Neutron::Net and OS::Neutron::ProviderNet resources." +msgstr "" +"Adds the 'availability_zone_hints' property for the OS::Neutron::Router, OS::" +"Neutron::Net and OS::Neutron::ProviderNet resources." + +msgid "" "All developer, contributor, and user content from various guides in " "openstack-manuals has been moved in-tree and are published at `https://docs." "openstack.org/heat/pike/`." @@ -739,6 +774,15 @@ msgstr "" "openstack.org/heat/pike/`." msgid "" +"Allow Heat resources to accept more than one required_service_extension. For " +"cases where a resource required multiple service extensions. A developer can " +"now provide a list of those extensions." +msgstr "" +"Allow Heat resources to accept more than one required_service_extension. For " +"cases where a resource required multiple service extensions. A developer can " +"now provide a list of those extensions." + +msgid "" "Allow to configure Heat service to forbid creation of stacks containing " "Volume resources with ``deletion_policy`` set to ``Snapshot`` when there is " "no Cinder backup service available." @@ -1391,6 +1435,13 @@ msgstr "" "which allows users to create a network port without any fixed IPs." msgid "" +"Now the ``[DEFAULT] shared_services_types`` option includes ``volumev3`` " +"service type by default." +msgstr "" +"Now the ``[DEFAULT] shared_services_types`` option includes ``volumev3`` " +"service type by default." + +msgid "" "OS::Aodh::CompositeAlarm resource plugin is added to manage Aodh composite " "alarm, aim to replace OS::Aodh::CombinationAlarm which has been deprecated " "in Newton release." @@ -1695,6 +1746,13 @@ msgstr "" "Python 2 is no longer supported. This release runs only on Python 3 and is " "tested only on Python 3.6 and 3.7." +msgid "" +"Python 3.6 & 3.7 support has been dropped. The minimum version of Python now " +"supported is Python 3.8." +msgstr "" +"Python 3.6 & 3.7 support has been dropped. The minimum version of Python now " +"supported is Python 3.8." + msgid "Queens Series Release Notes" msgstr "Queens Series Release Notes" @@ -1847,6 +1905,9 @@ msgstr "" msgid "Support external resource reference in template." msgstr "Support external resource reference in template." +msgid "Support for Block Storage API v2 has been removed." +msgstr "Support for Block Storage API v2 has been removed." + msgid "" "Support shared services in multi region mode. The services are declared in a " "list in config. shared_services_types=image, volume, volumev2." @@ -2395,6 +2456,9 @@ msgstr "" msgid "Xena Series Release Notes" msgstr "Xena Series Release Notes" +msgid "Yoga Series Release Notes" +msgstr "Yoga Series Release Notes" + msgid "" "``OS::Neutron::Port`` resources will now be replaced when the " "``mac_address`` property is modified. Neutron is unable to update the MAC " diff --git a/requirements.txt b/requirements.txt index f263179a1..457724754 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,7 @@ +# Requirements lower bounds listed here are our best effort to keep them up to +# date but we do not test them so no guarantee of having them all correct. If +# you find any incorrect lower bounds, let us know or propose a fix. + # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. @@ -46,7 +50,7 @@ python-magnumclient>=2.3.0 # Apache-2.0 python-manilaclient>=1.16.0 # Apache-2.0 python-mistralclient!=3.2.0,>=3.1.0 # Apache-2.0 python-monascaclient>=1.12.0 # Apache-2.0 -python-neutronclient>=6.14.0 # Apache-2.0 +python-neutronclient>=7.7.0 # Apache-2.0 python-novaclient>=9.1.0 # Apache-2.0 python-octaviaclient>=1.8.0 # Apache-2.0 python-openstackclient>=3.12.0 # Apache-2.0 diff --git a/roles/run-heat-tests/defaults/main.yaml b/roles/run-heat-tests/defaults/main.yaml index b601d49d9..22ce4a490 100644 --- a/roles/run-heat-tests/defaults/main.yaml +++ b/roles/run-heat-tests/defaults/main.yaml @@ -1,2 +1,5 @@ devstack_base_dir: /opt/stack tempest_test_timeout: '' +tempest_tox_environment: {} +heat_tempest_plugin: /opt/stack/heat-tempest-plugin +constraints_file: /opt/stack/requirements/upper-constraints.txt diff --git a/roles/run-heat-tests/tasks/main.yaml b/roles/run-heat-tests/tasks/main.yaml index 75122f2a1..4af1d0e03 100644 --- a/roles/run-heat-tests/tasks/main.yaml +++ b/roles/run-heat-tests/tasks/main.yaml @@ -1,3 +1,23 @@ +- name: Set OS_TEST_TIMEOUT if requested + set_fact: + tempest_tox_environment: "{{ tempest_tox_environment | combine({'OS_TEST_TIMEOUT': tempest_test_timeout}) }}" + when: tempest_test_timeout != '' + +- name: Set TOX_CONSTRAINTS_FILE + set_fact: + tempest_tox_environment: "{{ tempest_tox_environment | combine({'UPPER_CONSTRAINTS_FILE': constraints_file}) | combine({'TOX_CONSTRAINTS_FILE': constraints_file}) }}" + +- name: Allow git to read plugin directories + become: true + command: git config --system --add safe.directory {{heat_tempest_plugin}} + +- name: Install plugins + command: tox -evenv-tempest -- pip install -c{{constraints_file}} {{heat_tempest_plugin}} + become: true + args: + chdir: "{{devstack_base_dir}}/tempest" + environment: "{{ tempest_tox_environment }}" + - name: Run heat tests command: tox -evenv-tempest -- stestr --test-path={{devstack_base_dir}}/heat/heat_integrationtests \ --top-dir={{devstack_base_dir}}/heat \ @@ -5,5 +25,4 @@ args: chdir: "{{devstack_base_dir}}/tempest" become: true - become_user: tempest - environment: '{{ {"OS_TEST_TIMEOUT": tempest_test_timeout} if tempest_test_timeout else {} }}' + environment: "{{ tempest_tox_environment }}" @@ -1,12 +1,12 @@ [metadata] name = openstack-heat summary = OpenStack Orchestration -description-file = +description_file = README.rst author = OpenStack -author-email = openstack-discuss@lists.openstack.org -home-page = https://docs.openstack.org/heat/latest/ -python-requires = >=3.6 +author_email = openstack-discuss@lists.openstack.org +home_page = https://docs.openstack.org/heat/latest/ +python_requires = >=3.8 classifier = Environment :: OpenStack Intended Audience :: Information Technology @@ -16,8 +16,6 @@ classifier = Programming Language :: Python Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 @@ -18,4 +18,5 @@ import setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], + py_modules=[], pbr=True) @@ -1,7 +1,7 @@ [tox] -envlist = py36,py37,py38,py39,pep8 +envlist = py38,py39,pep8 ignore_basepython_conflict = True -minversion = 3.1.0 +minversion = 3.18.0 skipsdist = True [testenv] @@ -9,7 +9,6 @@ basepython = python3 setenv = VIRTUAL_ENV={envdir} PYTHONWARNINGS=default::DeprecationWarning OS_TEST_PATH=heat/tests -install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} {opts} {packages} usedevelop = True deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt @@ -55,10 +54,11 @@ commands = coverage report [testenv:docs] -whitelist_externals = +allowlist_externals = rm deps = -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = rm -rf doc/build @@ -66,7 +66,7 @@ commands = [testenv:pdf-docs] deps = {[testenv:docs]deps} -whitelist_externals = +allowlist_externals = make commands = sphinx-build -W -b latex doc/source doc/build/pdf @@ -76,7 +76,7 @@ commands = # This environment is called from CI scripts to test and publish # the API Ref to docs.openstack.org. deps = -r{toxinidir}/doc/requirements.txt -whitelist_externals = rm +allowlist_externals = rm commands = rm -rf api-ref/build sphinx-build -W --keep-going -b html -d api-ref/build/doctrees api-ref/source api-ref/build/html @@ -136,7 +136,7 @@ paths = ./heat/hacking commands = oslo_debug_helper {posargs} [testenv:releasenotes] -whitelist_externals = +allowlist_externals = rm deps = -r{toxinidir}/doc/requirements.txt commands = @@ -153,10 +153,3 @@ commands = deps = bindep commands = bindep test usedevelop = False - -[testenv:lower-constraints] -install_command = pip install {opts} {packages} -deps = - -c{toxinidir}/lower-constraints.txt - -r{toxinidir}/test-requirements.txt - -r{toxinidir}/requirements.txt |