summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.zuul.yaml10
-rw-r--r--bindep.txt2
-rw-r--r--devstack/lib/heat14
-rwxr-xr-xdevstack/upgrade/resources.sh12
-rwxr-xr-xdevstack/upgrade/shutdown.sh4
-rw-r--r--doc/source/getting_started/on_devstack.rst4
-rw-r--r--heat/api/openstack/v1/software_configs.py12
-rw-r--r--heat/common/environment_util.py36
-rw-r--r--heat/common/pluginutils.py20
-rw-r--r--heat/db/sqlalchemy/types.py2
-rw-r--r--heat/engine/resources/openstack/neutron/extrarouteset.py2
-rw-r--r--heat/engine/resources/openstack/neutron/net.py10
-rw-r--r--heat/engine/resources/openstack/neutron/provider_net.py13
-rw-r--r--heat/engine/resources/openstack/neutron/qos.py87
-rw-r--r--heat/engine/resources/openstack/neutron/router.py13
-rw-r--r--heat/engine/resources/openstack/nova/keypair.py17
-rw-r--r--heat/engine/resources/openstack/nova/server.py21
-rw-r--r--heat/engine/resources/openstack/nova/server_group.py10
-rw-r--r--heat/locale/de/LC_MESSAGES/heat.po9
-rw-r--r--heat/policies/resource_types.py3
-rw-r--r--heat/tests/api/openstack_v1/test_software_configs.py35
-rw-r--r--heat/tests/api/openstack_v1/test_stacks.py64
-rw-r--r--heat/tests/clients/test_swift_client.py4
-rw-r--r--heat/tests/openstack/heat/test_swiftsignal.py7
-rw-r--r--heat/tests/openstack/neutron/test_neutron_net.py3
-rw-r--r--heat/tests/openstack/neutron/test_neutron_provider_net.py9
-rw-r--r--heat/tests/openstack/neutron/test_neutron_router.py3
-rw-r--r--heat/tests/openstack/neutron/test_qos.py128
-rw-r--r--heat/tests/openstack/nova/test_keypair.py23
-rw-r--r--heat/tests/openstack/nova/test_server.py20
-rw-r--r--heat/tests/openstack/nova/test_server_group.py42
-rw-r--r--heat/tests/test_common_env_util.py23
-rw-r--r--heat/tests/test_common_pluginutils.py31
-rw-r--r--heat/tests/utils.py18
-rwxr-xr-xheat_integrationtests/cleanup_test_env.sh2
-rw-r--r--heat_integrationtests/functional/test_template_resource.py21
-rwxr-xr-xheat_integrationtests/prepare_test_env.sh2
-rw-r--r--lower-constraints.txt161
-rw-r--r--releasenotes/notes/add-rebuild-for-user_data_update_policy-b1a229f3f551ea4b.yaml5
-rw-r--r--releasenotes/notes/availability_zone_hints_Neutron_network_router-d01df1463193d9e6.yaml5
-rw-r--r--releasenotes/notes/drop-python-3-6-and-3-7-69dcd178c443e177.yaml5
-rw-r--r--releasenotes/notes/neutron-qos-minimum-packet-rate-rule-e58e9ced636320f1.yaml7
-rw-r--r--releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po72
-rw-r--r--requirements.txt6
-rw-r--r--roles/run-heat-tests/defaults/main.yaml3
-rw-r--r--roles/run-heat-tests/tasks/main.yaml23
-rw-r--r--setup.cfg10
-rw-r--r--setup.py1
-rw-r--r--tox.ini21
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 }}"
diff --git a/setup.cfg b/setup.cfg
index 9a9ae5af5..28b93c875 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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
diff --git a/setup.py b/setup.py
index f63cc23c5..dd26ed49b 100644
--- a/setup.py
+++ b/setup.py
@@ -18,4 +18,5 @@ import setuptools
setuptools.setup(
setup_requires=['pbr>=2.0.0'],
+ py_modules=[],
pbr=True)
diff --git a/tox.ini b/tox.ini
index bac5a4ca5..662ebcac7 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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