Merge "Turn on FT voting for stable/victoria" into stable/victoria
This commit is contained in:
commit
81c4b03f37
21
.zuul.yaml
21
.zuul.yaml
|
@ -1,20 +1,23 @@
|
||||||
- nodeset:
|
- nodeset:
|
||||||
name: openstack-2-nodes-focal
|
name: openstack-3-nodes-focal
|
||||||
nodes:
|
nodes:
|
||||||
- name: controller
|
- name: controller
|
||||||
label: ubuntu-focal
|
label: ubuntu-focal
|
||||||
- name: compute1
|
- name: compute1
|
||||||
label: ubuntu-focal
|
label: ubuntu-focal
|
||||||
|
- name: compute2
|
||||||
|
label: ubuntu-focal
|
||||||
groups:
|
groups:
|
||||||
# Nodes running the compute service
|
# Nodes running the compute service
|
||||||
- name: compute
|
- name: compute
|
||||||
nodes:
|
nodes:
|
||||||
- controller
|
|
||||||
- compute1
|
- compute1
|
||||||
|
- compute2
|
||||||
# Nodes that are not the controller
|
# Nodes that are not the controller
|
||||||
- name: subnode
|
- name: subnode
|
||||||
nodes:
|
nodes:
|
||||||
- compute1
|
- compute1
|
||||||
|
- compute2
|
||||||
# Switch node for multinode networking setup
|
# Switch node for multinode networking setup
|
||||||
- name: switch
|
- name: switch
|
||||||
nodes:
|
nodes:
|
||||||
|
@ -23,13 +26,14 @@
|
||||||
- name: peers
|
- name: peers
|
||||||
nodes:
|
nodes:
|
||||||
- compute1
|
- compute1
|
||||||
|
- compute2
|
||||||
|
|
||||||
- job:
|
- job:
|
||||||
name: tacker-functional-devstack-multinode-python3
|
name: tacker-functional-devstack-multinode-python3
|
||||||
parent: devstack
|
parent: devstack
|
||||||
description: |
|
description: |
|
||||||
Base multinodes job for devstack-based functional tests
|
Base multinodes job for devstack-based functional tests
|
||||||
nodeset: openstack-2-nodes-focal
|
nodeset: openstack-3-nodes-focal
|
||||||
pre-run: playbooks/devstack/pre.yaml
|
pre-run: playbooks/devstack/pre.yaml
|
||||||
run: playbooks/devstack/run.yaml
|
run: playbooks/devstack/run.yaml
|
||||||
post-run: playbooks/devstack/post.yaml
|
post-run: playbooks/devstack/post.yaml
|
||||||
|
@ -76,6 +80,11 @@
|
||||||
Q_SERVICE_PLUGIN_CLASSES: router,neutron.services.metering.metering_plugin.MeteringPlugin,networking_sfc.services.flowclassifier.plugin.FlowClassifierPlugin,neutron.services.qos.qos_plugin.QoSPlugin,qos
|
Q_SERVICE_PLUGIN_CLASSES: router,neutron.services.metering.metering_plugin.MeteringPlugin,networking_sfc.services.flowclassifier.plugin.FlowClassifierPlugin,neutron.services.qos.qos_plugin.QoSPlugin,qos
|
||||||
Q_ML2_PLUGIN_EXT_DRIVERS: port_security,qos
|
Q_ML2_PLUGIN_EXT_DRIVERS: port_security,qos
|
||||||
L2_AGENT_EXTENSIONS: qos
|
L2_AGENT_EXTENSIONS: qos
|
||||||
|
devstack_local_conf:
|
||||||
|
post-config:
|
||||||
|
$NEUTRON_DHCP_CONF:
|
||||||
|
DEFAULT:
|
||||||
|
enable_isolated_metadata: True
|
||||||
test_matrix_configs: [neutron]
|
test_matrix_configs: [neutron]
|
||||||
zuul_work_dir: src/opendev.org/openstack/tacker
|
zuul_work_dir: src/opendev.org/openstack/tacker
|
||||||
host-vars:
|
host-vars:
|
||||||
|
@ -110,7 +119,7 @@
|
||||||
n-api: true
|
n-api: true
|
||||||
n-api-meta: true
|
n-api-meta: true
|
||||||
n-cond: true
|
n-cond: true
|
||||||
n-cpu: true
|
n-cpu: false
|
||||||
n-novnc: true
|
n-novnc: true
|
||||||
n-sch: true
|
n-sch: true
|
||||||
placement-api: true
|
placement-api: true
|
||||||
|
@ -149,6 +158,7 @@
|
||||||
devstack_localrc:
|
devstack_localrc:
|
||||||
CELLSV2_SETUP: singleconductor
|
CELLSV2_SETUP: singleconductor
|
||||||
PHYSICAL_NETWORK: mgmtphysnet0
|
PHYSICAL_NETWORK: mgmtphysnet0
|
||||||
|
OVS_BRIDGE_MAPPINGS: public:br-ex,mgmtphysnet0:br-infra
|
||||||
DATABASE_TYPE: mysql
|
DATABASE_TYPE: mysql
|
||||||
devstack_services:
|
devstack_services:
|
||||||
q-agt: true
|
q-agt: true
|
||||||
|
@ -174,5 +184,4 @@
|
||||||
- release-notes-jobs-python3
|
- release-notes-jobs-python3
|
||||||
check:
|
check:
|
||||||
jobs:
|
jobs:
|
||||||
- tacker-functional-devstack-multinode-python3:
|
- tacker-functional-devstack-multinode-python3
|
||||||
voting: false
|
|
||||||
|
|
|
@ -154,7 +154,7 @@ class ViewBuilder(base.BaseViewBuilder):
|
||||||
if isinstance(vnf_lcm_subscription.id, str):
|
if isinstance(vnf_lcm_subscription.id, str):
|
||||||
decode_id = vnf_lcm_subscription.id
|
decode_id = vnf_lcm_subscription.id
|
||||||
else:
|
else:
|
||||||
decode_id = vnf_lcm_subscription.id.decode()
|
decode_id = vnf_lcm_subscription.id
|
||||||
return {
|
return {
|
||||||
"_links": {
|
"_links": {
|
||||||
"self": {
|
"self": {
|
||||||
|
@ -168,12 +168,12 @@ class ViewBuilder(base.BaseViewBuilder):
|
||||||
if 'filter' in vnf_lcm_subscription:
|
if 'filter' in vnf_lcm_subscription:
|
||||||
filter_dict = json.loads(vnf_lcm_subscription.filter)
|
filter_dict = json.loads(vnf_lcm_subscription.filter)
|
||||||
return {
|
return {
|
||||||
'id': vnf_lcm_subscription.id.decode(),
|
'id': vnf_lcm_subscription.id,
|
||||||
'filter': filter_dict,
|
'filter': filter_dict,
|
||||||
'callbackUri': vnf_lcm_subscription.callback_uri.decode(),
|
'callbackUri': vnf_lcm_subscription.callback_uri.decode(),
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
'id': vnf_lcm_subscription.id.decode(),
|
'id': vnf_lcm_subscription.id,
|
||||||
'callbackUri': vnf_lcm_subscription.callback_uri.decode(),
|
'callbackUri': vnf_lcm_subscription.callback_uri.decode(),
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -337,7 +337,7 @@ class VnfLcmController(wsgi.Controller):
|
||||||
placement_attr=placement_attr,
|
placement_attr=placement_attr,
|
||||||
status=constants.INACTIVE,
|
status=constants.INACTIVE,
|
||||||
error_reason=None,
|
error_reason=None,
|
||||||
deleted_at=datetime.min)
|
deleted_at=datetime.datetime.min)
|
||||||
context.session.add(vnf_db)
|
context.session.add(vnf_db)
|
||||||
for key, value in attributes.items():
|
for key, value in attributes.items():
|
||||||
arg = vnfm_db.VNFAttribute(
|
arg = vnfm_db.VNFAttribute(
|
||||||
|
@ -423,6 +423,7 @@ class VnfLcmController(wsgi.Controller):
|
||||||
vnf_product_name=vnfd.vnf_product_name,
|
vnf_product_name=vnfd.vnf_product_name,
|
||||||
vnf_software_version=vnfd.vnf_software_version,
|
vnf_software_version=vnfd.vnf_software_version,
|
||||||
vnfd_version=vnfd.vnfd_version,
|
vnfd_version=vnfd.vnfd_version,
|
||||||
|
vnf_pkg_id=vnfd.package_uuid,
|
||||||
tenant_id=request.context.project_id,
|
tenant_id=request.context.project_id,
|
||||||
vnf_metadata=req_body.get('metadata'))
|
vnf_metadata=req_body.get('metadata'))
|
||||||
|
|
||||||
|
@ -444,7 +445,10 @@ class VnfLcmController(wsgi.Controller):
|
||||||
# roll back db changes
|
# roll back db changes
|
||||||
self._destroy_vnf(context, vnf_instance)
|
self._destroy_vnf(context, vnf_instance)
|
||||||
vnf_instance.destroy(context)
|
vnf_instance.destroy(context)
|
||||||
self._update_package_usage_state(context, vnf_package)
|
if 'vnf_package' not in locals():
|
||||||
|
LOG.error("vnf_package is not assigned")
|
||||||
|
else:
|
||||||
|
self._update_package_usage_state(context, vnf_package)
|
||||||
|
|
||||||
# create notification data
|
# create notification data
|
||||||
notification = {
|
notification = {
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
Tacker base exception handling.
|
Tacker base exception handling.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
import webob.exc
|
import webob.exc
|
||||||
from webob import util as woutil
|
from webob import util as woutil
|
||||||
|
@ -206,6 +208,18 @@ class DuplicateResourceName(TackerException):
|
||||||
class DuplicateEntity(Conflict):
|
class DuplicateEntity(Conflict):
|
||||||
message = _("%(_type)s already exist with given %(entry)s")
|
message = _("%(_type)s already exist with given %(entry)s")
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# oslo.db does not parse duplicate key error correctly
|
||||||
|
# if MySQL server is >=8.0.19. (oslo.db BUG 1896916)
|
||||||
|
# TODO(kden): revert once the issue is resolved.
|
||||||
|
if len(kwargs["entry"]) == 1:
|
||||||
|
matched = re.match(
|
||||||
|
r"(?P<tbl>[^\.]+)\.uniq_(?P=tbl)0(?P<columns>.+)$",
|
||||||
|
kwargs["entry"][0])
|
||||||
|
if matched is not None:
|
||||||
|
kwargs["entry"] = matched.group("columns").split("0")
|
||||||
|
super(DuplicateEntity, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ValidationError(BadRequest):
|
class ValidationError(BadRequest):
|
||||||
message = "%(detail)s"
|
message = "%(detail)s"
|
||||||
|
|
|
@ -387,9 +387,8 @@ class Conductor(manager.Manager):
|
||||||
deploy_flavour.package_uuid = package_uuid
|
deploy_flavour.package_uuid = package_uuid
|
||||||
deploy_flavour.flavour_id = flavour['flavour_id']
|
deploy_flavour.flavour_id = flavour['flavour_id']
|
||||||
deploy_flavour.flavour_description = flavour['flavour_description']
|
deploy_flavour.flavour_description = flavour['flavour_description']
|
||||||
if flavour.get('instantiation_levels'):
|
deploy_flavour.instantiation_levels = \
|
||||||
deploy_flavour.instantiation_levels = \
|
flavour.get('instantiation_levels')
|
||||||
flavour.get('instantiation_levels')
|
|
||||||
deploy_flavour.create()
|
deploy_flavour.create()
|
||||||
|
|
||||||
sw_images = flavour.get('sw_images')
|
sw_images = flavour.get('sw_images')
|
||||||
|
@ -1335,9 +1334,8 @@ class Conductor(manager.Manager):
|
||||||
p_list)
|
p_list)
|
||||||
|
|
||||||
def _update_placement(self, context, vnf_dict, vnf_instance):
|
def _update_placement(self, context, vnf_dict, vnf_instance):
|
||||||
self.vnfm_plugin.update_placement_constraint(context,
|
self.vnfm_plugin.update_placement_constraint_heal(
|
||||||
vnf_dict,
|
context, vnf_dict, vnf_instance)
|
||||||
vnf_instance)
|
|
||||||
|
|
||||||
def _delete_placement(self, context, vnf_instance_id):
|
def _delete_placement(self, context, vnf_instance_id):
|
||||||
self.vnfm_plugin.delete_placement_constraint(context,
|
self.vnfm_plugin.delete_placement_constraint(context,
|
||||||
|
@ -1382,8 +1380,8 @@ class Conductor(manager.Manager):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
LOG.debug("Update vnf lcm %s %s",
|
LOG.debug("Update vnf lcm %s %s",
|
||||||
(vnf_lcm_op_occs_id,
|
vnf_lcm_op_occs_id,
|
||||||
operation_state))
|
operation_state)
|
||||||
vnf_notif = self._get_vnf_notify(context, vnf_lcm_op_occs_id)
|
vnf_notif = self._get_vnf_notify(context, vnf_lcm_op_occs_id)
|
||||||
vnf_notif.operation_state = operation_state
|
vnf_notif.operation_state = operation_state
|
||||||
if operation_state == fields.LcmOccsOperationState.FAILED_TEMP:
|
if operation_state == fields.LcmOccsOperationState.FAILED_TEMP:
|
||||||
|
@ -1483,20 +1481,20 @@ class Conductor(manager.Manager):
|
||||||
|
|
||||||
# Notification shipping
|
# Notification shipping
|
||||||
for line in vnf_lcm_subscriptions:
|
for line in vnf_lcm_subscriptions:
|
||||||
notification['subscriptionId'] = line.id.decode()
|
notification['subscriptionId'] = line.id
|
||||||
if (notification.get('notificationType') ==
|
if (notification.get('notificationType') ==
|
||||||
'VnfLcmOperationOccurrenceNotification'):
|
'VnfLcmOperationOccurrenceNotification'):
|
||||||
notification['_links'] = {}
|
notification['_links'] = {}
|
||||||
notification['_links']['subscription'] = {}
|
notification['_links']['subscription'] = {}
|
||||||
notification['_links']['subscription']['href'] = \
|
notification['_links']['subscription']['href'] = \
|
||||||
CONF.vnf_lcm.endpoint_url + \
|
CONF.vnf_lcm.endpoint_url + \
|
||||||
"/vnflcm/v1/subscriptions/" + line.id.decode()
|
"/vnflcm/v1/subscriptions/" + line.id
|
||||||
else:
|
else:
|
||||||
notification['links'] = {}
|
notification['links'] = {}
|
||||||
notification['links']['subscription'] = {}
|
notification['links']['subscription'] = {}
|
||||||
notification['links']['subscription']['href'] = \
|
notification['links']['subscription']['href'] = \
|
||||||
CONF.vnf_lcm.endpoint_url + \
|
CONF.vnf_lcm.endpoint_url + \
|
||||||
"/vnflcm/v1/subscriptions/" + line.id.decode()
|
"/vnflcm/v1/subscriptions/" + line.id
|
||||||
notification['timeStamp'] = datetime.datetime.utcnow(
|
notification['timeStamp'] = datetime.datetime.utcnow(
|
||||||
).isoformat()
|
).isoformat()
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
2c5211036579
|
d25c7c865ce8
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
# Copyright 2020 OpenStack Foundation
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
# flake8: noqa: E402
|
||||||
|
|
||||||
|
"""mod_vnflcm_subscription
|
||||||
|
|
||||||
|
Revision ID: d25c7c865ce8
|
||||||
|
Revises: 2c5211036579
|
||||||
|
Create Date: 2020-10-15 14:27:04.946002
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'd25c7c865ce8'
|
||||||
|
down_revision = '2c5211036579'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from tacker.db import types
|
||||||
|
|
||||||
|
from tacker.db import migration
|
||||||
|
|
||||||
|
def upgrade(active_plugins=None, options=None):
|
||||||
|
|
||||||
|
op.alter_column('vnf_lcm_filters', 'subscription_uuid',
|
||||||
|
type_=types.Uuid(length=36), existing_type=sa.String(length=255),
|
||||||
|
nullable=False)
|
||||||
|
|
||||||
|
sta_str = "json_unquote(json_extract('filter','$.operationTypes'))"
|
||||||
|
op.add_column(
|
||||||
|
'vnf_lcm_filters',
|
||||||
|
sa.Column('operation_types',
|
||||||
|
sa.LargeBinary(length=65536),
|
||||||
|
sa.Computed(sta_str)))
|
||||||
|
|
||||||
|
op.add_column(
|
||||||
|
'vnf_lcm_filters',
|
||||||
|
sa.Column('operation_types_len',
|
||||||
|
sa.Integer,
|
||||||
|
sa.Computed("ifnull(json_length('operation_types'),0)")))
|
||||||
|
|
||||||
|
op.drop_column('vnf_lcm_filters', 'operation_states')
|
||||||
|
op.drop_column('vnf_lcm_filters', 'operation_states_len')
|
||||||
|
|
||||||
|
op.alter_column('vnf_lcm_op_occs', 'operation_state',
|
||||||
|
type_=sa.String(length=16), existing_type=sa.String(length=255))
|
||||||
|
|
||||||
|
op.alter_column('vnf_lcm_op_occs', 'operation',
|
||||||
|
type_=sa.String(length=16),existing_type=sa.String(length=255))
|
||||||
|
|
||||||
|
op.add_column('vnf_lcm_op_occs',
|
||||||
|
sa.Column('is_cancel_pending', sa.Boolean, nullable=False)),
|
||||||
|
|
||||||
|
op.add_column('vnf_lcm_op_occs',
|
||||||
|
sa.Column('resource_changes', sa.JSON(), nullable=True))
|
||||||
|
|
||||||
|
op.add_column('vnf_lcm_op_occs',
|
||||||
|
sa.Column('error_point', sa.Integer, nullable=True))
|
||||||
|
|
||||||
|
op.add_column('vnf_lcm_op_occs',
|
||||||
|
sa.Column('changed_info', sa.JSON(), nullable=True))
|
||||||
|
|
||||||
|
op.add_column('vnf_lcm_op_occs',
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=False))
|
||||||
|
|
||||||
|
op.add_column('vnf_lcm_op_occs',
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True))
|
||||||
|
|
||||||
|
op.add_column('vnf_lcm_op_occs',
|
||||||
|
sa.Column('deleted_at', sa.DateTime(), nullable=True))
|
||||||
|
|
||||||
|
pass
|
|
@ -25,7 +25,6 @@ def register_all():
|
||||||
# function in order for it to be registered by services that may
|
# function in order for it to be registered by services that may
|
||||||
# need to receive it via RPC.
|
# need to receive it via RPC.
|
||||||
__import__('tacker.objects.heal_vnf_request')
|
__import__('tacker.objects.heal_vnf_request')
|
||||||
__import__('tacker.objects.vnfd')
|
|
||||||
__import__('tacker.objects.vnf_package')
|
__import__('tacker.objects.vnf_package')
|
||||||
__import__('tacker.objects.vnf_package_vnfd')
|
__import__('tacker.objects.vnf_package_vnfd')
|
||||||
__import__('tacker.objects.vnf_deployment_flavour')
|
__import__('tacker.objects.vnf_deployment_flavour')
|
||||||
|
@ -44,5 +43,4 @@ def register_all():
|
||||||
__import__('tacker.objects.scale_vnf_request')
|
__import__('tacker.objects.scale_vnf_request')
|
||||||
__import__('tacker.objects.grant')
|
__import__('tacker.objects.grant')
|
||||||
__import__('tacker.objects.grant_request')
|
__import__('tacker.objects.grant_request')
|
||||||
__import__('tacker.objects.vnfd')
|
|
||||||
__import__('tacker.objects.vnfd_attribute')
|
__import__('tacker.objects.vnfd_attribute')
|
||||||
|
|
|
@ -235,7 +235,7 @@ def _vnf_lcm_subscriptions_create(context, values, filter):
|
||||||
callbackUri = values.callback_uri
|
callbackUri = values.callback_uri
|
||||||
if filter:
|
if filter:
|
||||||
notification_type = filter.get('notificationTypes')
|
notification_type = filter.get('notificationTypes')
|
||||||
operation_type = filter.get('operationTypese')
|
operation_type = filter.get('operationTypes')
|
||||||
|
|
||||||
vnf_lcm_subscriptions_id = _vnf_lcm_subscriptions_id_get(
|
vnf_lcm_subscriptions_id = _vnf_lcm_subscriptions_id_get(
|
||||||
context,
|
context,
|
||||||
|
@ -244,7 +244,7 @@ def _vnf_lcm_subscriptions_create(context, values, filter):
|
||||||
operation_type=operation_type)
|
operation_type=operation_type)
|
||||||
|
|
||||||
if vnf_lcm_subscriptions_id:
|
if vnf_lcm_subscriptions_id:
|
||||||
raise Exception("303" + vnf_lcm_subscriptions_id.id.decode())
|
raise Exception("303" + vnf_lcm_subscriptions_id)
|
||||||
|
|
||||||
_add_filter_data(context, values.id, filter)
|
_add_filter_data(context, values.id, filter)
|
||||||
|
|
||||||
|
@ -253,7 +253,7 @@ def _vnf_lcm_subscriptions_create(context, values, filter):
|
||||||
callbackUri)
|
callbackUri)
|
||||||
|
|
||||||
if vnf_lcm_subscriptions_id:
|
if vnf_lcm_subscriptions_id:
|
||||||
raise Exception("303" + vnf_lcm_subscriptions_id.id.decode())
|
raise Exception("303" + vnf_lcm_subscriptions_id.id)
|
||||||
_add_filter_data(context, values.id, {})
|
_add_filter_data(context, values.id, {})
|
||||||
|
|
||||||
return values
|
return values
|
||||||
|
|
|
@ -554,8 +554,6 @@ class VnfPackage(base.TackerObject, base.TackerPersistentObject,
|
||||||
# check if vnf package is used by any vnf instances.
|
# check if vnf package is used by any vnf instances.
|
||||||
query = context.session.query(
|
query = context.session.query(
|
||||||
func.count(models.VnfInstance.id)).\
|
func.count(models.VnfInstance.id)).\
|
||||||
filter_by(
|
|
||||||
instantiation_state=fields.VnfInstanceState.INSTANTIATED).\
|
|
||||||
filter_by(tenant_id=self.tenant_id).\
|
filter_by(tenant_id=self.tenant_id).\
|
||||||
filter_by(vnfd_id=self.vnfd.vnfd_id).\
|
filter_by(vnfd_id=self.vnfd.vnfd_id).\
|
||||||
filter_by(deleted=False)
|
filter_by(deleted=False)
|
||||||
|
|
|
@ -415,6 +415,9 @@ class FenixPlugin(object):
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
instances = []
|
instances = []
|
||||||
|
if not vnf_dict['instance_id']:
|
||||||
|
return instances
|
||||||
|
|
||||||
client = self._get_openstack_clients(context, vnf_dict)
|
client = self._get_openstack_clients(context, vnf_dict)
|
||||||
resources = client.heat.resources.list(vnf_dict['instance_id'],
|
resources = client.heat.resources.list(vnf_dict['instance_id'],
|
||||||
nested_depth=2)
|
nested_depth=2)
|
||||||
|
|
|
@ -23,6 +23,7 @@ from keystoneclient import adapter
|
||||||
from neutronclient.v2_0 import client as neutron_client
|
from neutronclient.v2_0 import client as neutron_client
|
||||||
from novaclient import client as nova_client
|
from novaclient import client as nova_client
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
from tempest.lib import base
|
from tempest.lib import base
|
||||||
|
|
||||||
|
@ -32,9 +33,11 @@ from tacker.tests.functional import clients
|
||||||
from tacker.tests.utils import read_file
|
from tacker.tests.utils import read_file
|
||||||
from tacker import version
|
from tacker import version
|
||||||
|
|
||||||
|
from tackerclient.common import exceptions
|
||||||
from tackerclient.v1_0 import client as tacker_client
|
from tackerclient.v1_0 import client as tacker_client
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
@ -125,7 +128,7 @@ class BaseTackerTest(base.BaseTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def tackerclient(cls):
|
def tackerclient(cls):
|
||||||
auth_session = cls.get_auth_session()
|
auth_session = cls.get_auth_session()
|
||||||
return tacker_client.Client(session=auth_session)
|
return tacker_client.Client(session=auth_session, retries=5)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def novaclient(cls):
|
def novaclient(cls):
|
||||||
|
@ -204,11 +207,17 @@ class BaseTackerTest(base.BaseTestCase):
|
||||||
def wait_until_vnf_status(self, vnf_id, target_status, timeout,
|
def wait_until_vnf_status(self, vnf_id, target_status, timeout,
|
||||||
sleep_interval):
|
sleep_interval):
|
||||||
start_time = int(time.time())
|
start_time = int(time.time())
|
||||||
|
status = None
|
||||||
while True:
|
while True:
|
||||||
vnf_result = self.client.show_vnf(vnf_id)
|
try:
|
||||||
status = vnf_result['vnf']['status']
|
vnf_result = self.client.show_vnf(vnf_id)
|
||||||
if (status == target_status) or (
|
status = vnf_result['vnf']['status']
|
||||||
(int(time.time()) - start_time) > timeout):
|
if status == target_status:
|
||||||
|
break
|
||||||
|
except exceptions.InternalServerError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if int(time.time()) - start_time > timeout:
|
||||||
break
|
break
|
||||||
time.sleep(sleep_interval)
|
time.sleep(sleep_interval)
|
||||||
|
|
||||||
|
@ -233,18 +242,24 @@ class BaseTackerTest(base.BaseTestCase):
|
||||||
constants.VNF_CIRROS_CREATE_TIMEOUT,
|
constants.VNF_CIRROS_CREATE_TIMEOUT,
|
||||||
constants.ACTIVE_SLEEP_TIME)
|
constants.ACTIVE_SLEEP_TIME)
|
||||||
|
|
||||||
def wait_until_vnf_delete(self, vnf_id, timeout):
|
def wait_until_vnf_delete(self, vnf_id, timeout, sleep_interval=1):
|
||||||
start_time = int(time.time())
|
start_time = int(time.time())
|
||||||
while True:
|
while True:
|
||||||
|
status = None
|
||||||
try:
|
try:
|
||||||
vnf_result = self.client.show_vnf(vnf_id)
|
vnf_result = self.client.show_vnf(vnf_id)
|
||||||
time.sleep(1)
|
status = vnf_result['vnf']['status']
|
||||||
except Exception:
|
except exceptions.NotFound:
|
||||||
return
|
return
|
||||||
status = vnf_result['vnf']['status']
|
except Exception as e:
|
||||||
if (status != 'PENDING_DELETE') or ((
|
LOG.error("Failed to get vnf status: %s", str(e))
|
||||||
int(time.time()) - start_time) > timeout):
|
|
||||||
|
if status is not None and status != 'PENDING_DELETE':
|
||||||
raise Exception("Failed with status: %s" % status)
|
raise Exception("Failed with status: %s" % status)
|
||||||
|
if int(time.time()) - start_time > timeout:
|
||||||
|
raise Exception("Timeout for deleting vnf %s.",
|
||||||
|
vnf_id)
|
||||||
|
time.sleep(sleep_interval)
|
||||||
|
|
||||||
def wait_until_vnf_dead(self, vnf_id, timeout, sleep_interval):
|
def wait_until_vnf_dead(self, vnf_id, timeout, sleep_interval):
|
||||||
self.wait_until_vnf_status(vnf_id, 'DEAD', timeout,
|
self.wait_until_vnf_status(vnf_id, 'DEAD', timeout,
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import time
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
@ -50,6 +51,10 @@ class TestRemoteCommandExecutor(base.BaseTackerTest):
|
||||||
def _test_cmd_executor(self, vnfd_file, vnf_name):
|
def _test_cmd_executor(self, vnfd_file, vnf_name):
|
||||||
vnf_id, mgmt_ip = self._test_create_vnf(vnfd_file, vnf_name)
|
vnf_id, mgmt_ip = self._test_create_vnf(vnfd_file, vnf_name)
|
||||||
|
|
||||||
|
# Wait for VM initialization. It takes time after VM started until
|
||||||
|
# sshd starts. It is a bit ad-hoc but adopted it for simplicity.
|
||||||
|
time.sleep(100)
|
||||||
|
|
||||||
# Login on VNF instance, and execute 'hostname' command to verify
|
# Login on VNF instance, and execute 'hostname' command to verify
|
||||||
# connection and command output.
|
# connection and command output.
|
||||||
usr = 'cirros'
|
usr = 'cirros'
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
from keystoneauth1 import exceptions
|
from keystoneauth1 import exceptions
|
||||||
from keystoneauth1.identity import v3
|
from keystoneauth1.identity import v3
|
||||||
from keystoneauth1 import session
|
from keystoneauth1 import session
|
||||||
|
@ -24,6 +26,8 @@ from oslo_log import log as logging
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
KEYSTONE_CLIENT_RETRIES = 3
|
||||||
|
KEYSTONE_RETRY_WAIT = 5
|
||||||
|
|
||||||
|
|
||||||
class Keystone(object):
|
class Keystone(object):
|
||||||
|
@ -51,5 +55,17 @@ class Keystone(object):
|
||||||
verify = 'True' == kwargs.pop('cert_verify', 'False')
|
verify = 'True' == kwargs.pop('cert_verify', 'False')
|
||||||
auth_plugin = v3.Password(**kwargs)
|
auth_plugin = v3.Password(**kwargs)
|
||||||
ses = self.get_session(auth_plugin=auth_plugin, verify=verify)
|
ses = self.get_session(auth_plugin=auth_plugin, verify=verify)
|
||||||
cli = client.Client('v3', session=ses)
|
|
||||||
|
client_retries = KEYSTONE_CLIENT_RETRIES
|
||||||
|
while client_retries > 0:
|
||||||
|
try:
|
||||||
|
cli = client.Client('v3', session=ses)
|
||||||
|
break
|
||||||
|
except exceptions.InternalServerError:
|
||||||
|
LOG.warning("keystone service responds with 500 "
|
||||||
|
"(InternalServerError).")
|
||||||
|
client_retries = client_retries - 1
|
||||||
|
if client_retries == 0:
|
||||||
|
raise
|
||||||
|
time.sleep(KEYSTONE_RETRY_WAIT)
|
||||||
return cli
|
return cli
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
import time
|
import time
|
||||||
|
import unittest
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
|
@ -111,11 +112,15 @@ class VnfTestAlarmMonitor(base.BaseTackerTest):
|
||||||
self.addCleanup(self.wait_until_vnf_delete, vnf_id,
|
self.addCleanup(self.wait_until_vnf_delete, vnf_id,
|
||||||
constants.VNF_CIRROS_DELETE_TIMEOUT)
|
constants.VNF_CIRROS_DELETE_TIMEOUT)
|
||||||
|
|
||||||
|
# TODO(kden): restore this test once Change #757537 is reverted.
|
||||||
|
@unittest.skip("Until ceilometer is restored")
|
||||||
def test_vnf_alarm_respawn(self):
|
def test_vnf_alarm_respawn(self):
|
||||||
self._test_vnf_tosca_alarm(
|
self._test_vnf_tosca_alarm(
|
||||||
'sample-tosca-alarm-respawn.yaml',
|
'sample-tosca-alarm-respawn.yaml',
|
||||||
'alarm and respawn-vnf')
|
'alarm and respawn-vnf')
|
||||||
|
|
||||||
|
# TODO(kden): restore this test once Change #757537 is reverted.
|
||||||
|
@unittest.skip("Until ceilometer is restored")
|
||||||
def test_vnf_alarm_scale(self):
|
def test_vnf_alarm_scale(self):
|
||||||
self._test_vnf_tosca_alarm(
|
self._test_vnf_tosca_alarm(
|
||||||
'sample-tosca-alarm-scale.yaml',
|
'sample-tosca-alarm-scale.yaml',
|
||||||
|
|
|
@ -12,15 +12,14 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import ast
|
|
||||||
import time
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import testtools
|
import testtools
|
||||||
|
import time
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from blazarclient import exception
|
from blazarclient import exception
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
from tacker.plugins.common import constants as evt_constants
|
from tacker.plugins.common import constants as evt_constants
|
||||||
from tacker.tests import constants
|
from tacker.tests import constants
|
||||||
from tacker.tests.functional import base
|
from tacker.tests.functional import base
|
||||||
|
@ -126,20 +125,10 @@ class VnfTestReservationMonitor(base.BaseTackerTest):
|
||||||
vnf = self.client.show_vnf(vnf_id)['vnf']
|
vnf = self.client.show_vnf(vnf_id)['vnf']
|
||||||
# {"VDU1": ["10.0.0.14", "10.0.0.5"]}
|
# {"VDU1": ["10.0.0.14", "10.0.0.5"]}
|
||||||
if scale_type == 'scaling-in' and vdu_count == 0:
|
if scale_type == 'scaling-in' and vdu_count == 0:
|
||||||
try:
|
self.assertFalse(jsonutils.loads(vnf.get('mgmt_ip_address',
|
||||||
# After sacling-in the vnf['mgmt_ip_address'] will be the
|
'{}')),
|
||||||
# list containg null values. As vnf['mgmt_ip_address']
|
"Once scaling-in completes, mgmt_ip_address"
|
||||||
# is string so we can not access ip address list for VDU1
|
" should be empty.")
|
||||||
# so converting that into the dict using ast lib.
|
|
||||||
# If the list contains the null value then it will raise
|
|
||||||
# ValueError so on the basis of that we are confirming the
|
|
||||||
# scaling-in is successful.
|
|
||||||
ast.literal_eval(vnf['mgmt_ip_address'])
|
|
||||||
self.fail("Scaling-in should not contain "
|
|
||||||
"mgmt_ip_address")
|
|
||||||
except ValueError:
|
|
||||||
assert True, ("Management Ip address list for VDU1 "
|
|
||||||
"contains null values.")
|
|
||||||
elif scale_type == 'scaling-out':
|
elif scale_type == 'scaling-out':
|
||||||
self.assertEqual(vdu_count, len(json.loads(
|
self.assertEqual(vdu_count, len(json.loads(
|
||||||
vnf['mgmt_ip_address'])['VDU1']))
|
vnf['mgmt_ip_address'])['VDU1']))
|
||||||
|
@ -193,7 +182,7 @@ class VnfTestReservationMonitor(base.BaseTackerTest):
|
||||||
blazarclient = self.blazarclient()
|
blazarclient = self.blazarclient()
|
||||||
reservations = [{'disk_gb': 0,
|
reservations = [{'disk_gb': 0,
|
||||||
'vcpus': 1, 'memory_mb': 1,
|
'vcpus': 1, 'memory_mb': 1,
|
||||||
'amount': 1, 'affinity': False,
|
'amount': 2, 'affinity': True,
|
||||||
'resource_properties': '',
|
'resource_properties': '',
|
||||||
'resource_type': 'virtual:instance'}]
|
'resource_type': 'virtual:instance'}]
|
||||||
events = []
|
events = []
|
||||||
|
@ -208,6 +197,7 @@ class VnfTestReservationMonitor(base.BaseTackerTest):
|
||||||
blazar_host = blazarclient.host.create(
|
blazar_host = blazarclient.host.create(
|
||||||
hypervisor.hypervisor_hostname)
|
hypervisor.hypervisor_hostname)
|
||||||
host_added = True
|
host_added = True
|
||||||
|
break
|
||||||
except exception.BlazarClientException:
|
except exception.BlazarClientException:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -437,7 +437,7 @@ class VnfPackageTest(base.BaseTackerTest):
|
||||||
id=self.package_id1, base_path=self.base_url),
|
id=self.package_id1, base_path=self.base_url),
|
||||||
"GET", body={}, headers={})
|
"GET", body={}, headers={})
|
||||||
self.assertEqual(200, response[0].status_code)
|
self.assertEqual(200, response[0].status_code)
|
||||||
self.assertEqual('12804503', response[0].headers['Content-Length'])
|
self.assertEqual('12804568', response[0].headers['Content-Length'])
|
||||||
|
|
||||||
def test_fetch_vnf_package_content_combined_download(self):
|
def test_fetch_vnf_package_content_combined_download(self):
|
||||||
"""Combine two partial downloads using 'Range' requests for csar zip"""
|
"""Combine two partial downloads using 'Range' requests for csar zip"""
|
||||||
|
@ -458,7 +458,7 @@ class VnfPackageTest(base.BaseTackerTest):
|
||||||
zipf.writestr(file_path, data)
|
zipf.writestr(file_path, data)
|
||||||
|
|
||||||
# Partial download 2
|
# Partial download 2
|
||||||
range_ = 'bytes=11-12804503'
|
range_ = 'bytes=11-12804568'
|
||||||
headers = {'Range': range_}
|
headers = {'Range': range_}
|
||||||
response_2 = self.http_client.do_request(
|
response_2 = self.http_client.do_request(
|
||||||
'{base_path}/{id}/package_content'.format(
|
'{base_path}/{id}/package_content'.format(
|
||||||
|
@ -471,7 +471,7 @@ class VnfPackageTest(base.BaseTackerTest):
|
||||||
size_2 = int(response_2[0].headers['Content-Length'])
|
size_2 = int(response_2[0].headers['Content-Length'])
|
||||||
total_size = size_1 + size_2
|
total_size = size_1 + size_2
|
||||||
self.assertEqual(True, zipfile.is_zipfile(zip_file_path))
|
self.assertEqual(True, zipfile.is_zipfile(zip_file_path))
|
||||||
self.assertEqual(12804503, total_size)
|
self.assertEqual(12804568, total_size)
|
||||||
zip_file_path.close()
|
zip_file_path.close()
|
||||||
|
|
||||||
def test_fetch_vnf_package_artifacts(self):
|
def test_fetch_vnf_package_artifacts(self):
|
||||||
|
|
|
@ -130,7 +130,7 @@ class TestConductor(SqlTestCase, unit_base.FixturedTestCase):
|
||||||
self.subscription_authentication = json.dumps(
|
self.subscription_authentication = json.dumps(
|
||||||
auth_params).encode()
|
auth_params).encode()
|
||||||
|
|
||||||
self.id = uuidsentinel.lcm_subscription_id.encode()
|
self.id = uuidsentinel.lcm_subscription_id
|
||||||
self.callback_uri = 'https://localhost/callback'.encode()
|
self.callback_uri = 'https://localhost/callback'.encode()
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
|
|
|
@ -609,9 +609,9 @@ class TestVnflcmDriver(db_base.SqlTestCase):
|
||||||
# Vnf resource software images will be created during
|
# Vnf resource software images will be created during
|
||||||
# instantiation.
|
# instantiation.
|
||||||
self.assertEqual(1, mock_resource_create.call_count)
|
self.assertEqual(1, mock_resource_create.call_count)
|
||||||
# Invoke will be called 6 times, 3 for deleting the vnf
|
# Invoke will be called 7 times, 3 for deleting the vnf
|
||||||
# resources and 3 during instantiation.
|
# resources and 4 during instantiation.
|
||||||
self.assertEqual(6, self._vnf_manager.invoke.call_count)
|
self.assertEqual(7, self._vnf_manager.invoke.call_count)
|
||||||
expected_msg = ("Request received for healing vnf '%s' "
|
expected_msg = ("Request received for healing vnf '%s' "
|
||||||
"is completed successfully")
|
"is completed successfully")
|
||||||
mock_log.info.assert_called_with(expected_msg,
|
mock_log.info.assert_called_with(expected_msg,
|
||||||
|
|
|
@ -307,7 +307,7 @@ def _process_query_metadata(metadata, policy, unique_id):
|
||||||
|
|
||||||
def _process_query_metadata_reservation(metadata, policy):
|
def _process_query_metadata_reservation(metadata, policy):
|
||||||
query_metadata = dict()
|
query_metadata = dict()
|
||||||
policy_actions = policy.entity_tpl['reservation'].keys()
|
policy_actions = list(policy.entity_tpl['reservation'].keys())
|
||||||
policy_actions.remove('properties')
|
policy_actions.remove('properties')
|
||||||
for action in policy_actions:
|
for action in policy_actions:
|
||||||
query_template = [{
|
query_template = [{
|
||||||
|
@ -335,7 +335,7 @@ def _process_alarm_actions(vnf, policy):
|
||||||
def _process_alarm_actions_for_reservation(vnf, policy):
|
def _process_alarm_actions_for_reservation(vnf, policy):
|
||||||
# process alarm url here
|
# process alarm url here
|
||||||
alarm_actions = dict()
|
alarm_actions = dict()
|
||||||
policy_actions = policy.entity_tpl['reservation'].keys()
|
policy_actions = list(policy.entity_tpl['reservation'].keys())
|
||||||
policy_actions.remove('properties')
|
policy_actions.remove('properties')
|
||||||
for action in policy_actions:
|
for action in policy_actions:
|
||||||
alarm_url = vnf['attributes'].get(action)
|
alarm_url = vnf['attributes'].get(action)
|
||||||
|
|
|
@ -303,10 +303,11 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
|
||||||
vim_connection_info, instantiate_vnf_req):
|
vim_connection_info, instantiate_vnf_req):
|
||||||
vnfd_dict = vnflcm_utils._get_vnfd_dict(context, vnf_instance.vnfd_id,
|
vnfd_dict = vnflcm_utils._get_vnfd_dict(context, vnf_instance.vnfd_id,
|
||||||
instantiate_vnf_req.flavour_id)
|
instantiate_vnf_req.flavour_id)
|
||||||
base_hot_dict, nested_hot_dict = vnflcm_utils. \
|
base_hot_dict, nested_hot_dict = \
|
||||||
get_base_nest_hot_dict(context,
|
vnflcm_utils.get_base_nest_hot_dict(
|
||||||
instantiate_vnf_req.flavour_id,
|
context,
|
||||||
vnf_instance.vnfd_id)
|
instantiate_vnf_req.flavour_id,
|
||||||
|
vnf_instance.vnfd_id)
|
||||||
vnf_package_path = None
|
vnf_package_path = None
|
||||||
if base_hot_dict is not None:
|
if base_hot_dict is not None:
|
||||||
vnf_package_path = vnflcm_utils._get_vnf_package_path(
|
vnf_package_path = vnflcm_utils._get_vnf_package_path(
|
||||||
|
@ -544,10 +545,22 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
|
||||||
|
|
||||||
instantiate_vnf_request = objects.InstantiateVnfRequest.\
|
instantiate_vnf_request = objects.InstantiateVnfRequest.\
|
||||||
from_vnf_instance(vnf_instance)
|
from_vnf_instance(vnf_instance)
|
||||||
|
vnf_instance.instantiated_vnf_info.reinitialize()
|
||||||
|
vnf_instance.task_state = fields.VnfInstanceTaskState.INSTANTIATING
|
||||||
|
vnfd_dict = vnflcm_utils._get_vnfd_dict(
|
||||||
|
context, vnf_instance.vnfd_id, instantiate_vnf_request.flavour_id)
|
||||||
|
vnflcm_utils._build_instantiated_vnf_info(
|
||||||
|
vnfd_dict, instantiate_vnf_request, vnf_instance,
|
||||||
|
vim_connection_info.vim_id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._instantiate_vnf(context, vnf_instance, vnf_dict,
|
self._instantiate_vnf(context, vnf_instance, vnf_dict,
|
||||||
vim_connection_info, instantiate_vnf_request)
|
vim_connection_info, instantiate_vnf_request)
|
||||||
|
self._vnf_manager.invoke(
|
||||||
|
vim_connection_info.vim_type, 'post_vnf_instantiation',
|
||||||
|
context=context, vnf_instance=vnf_instance,
|
||||||
|
vim_connection_info=vim_connection_info)
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
with excutils.save_and_reraise_exception() as exc_ctxt:
|
with excutils.save_and_reraise_exception() as exc_ctxt:
|
||||||
exc_ctxt.reraise = False
|
exc_ctxt.reraise = False
|
||||||
|
|
|
@ -163,10 +163,11 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
|
||||||
|
|
||||||
if user_data_path is not None and user_data_class is not None:
|
if user_data_path is not None and user_data_class is not None:
|
||||||
LOG.info('Execute user data and create heat-stack.')
|
LOG.info('Execute user data and create heat-stack.')
|
||||||
base_hot_dict, nested_hot_dict = vnflcm_utils. \
|
base_hot_dict, nested_hot_dict = \
|
||||||
get_base_nest_hot_dict(context,
|
vnflcm_utils.get_base_nest_hot_dict(
|
||||||
inst_req_info.flavour_id,
|
context,
|
||||||
vnf_instance.vnfd_id)
|
inst_req_info.flavour_id,
|
||||||
|
vnf_instance.vnfd_id)
|
||||||
if base_hot_dict is None:
|
if base_hot_dict is None:
|
||||||
error_reason = _("failed to get Base HOT.")
|
error_reason = _("failed to get Base HOT.")
|
||||||
raise vnfm.LCMUserDataFailed(reason=error_reason)
|
raise vnfm.LCMUserDataFailed(reason=error_reason)
|
||||||
|
|
Loading…
Reference in New Issue