Support CNF scale operations based on ETSI NFV

Implements Container based VNF scale operation support with ETSI
NFV-SOL003 v2.6.1 VNF Lifecycle Management. Users can scale the number
of pod replicas managed by controller resources such as Kubernetes
Deployment, StatefulSet, and ReplicaSet.

Other changes:
* Check the vnf_state before scale operation in controller as other
  LCM operations.
* Enable to store multiple flavour to `vnfd_attribute` when
  `Conductor._onboard_vnfd()` method called.
* For FT, sqlalchemy is used, which calls functions in pymysql.py,
  so the dependency module PyMySQL needs to be added.

Implements: blueprint support-cnf-scale
Spec: https://specs.openstack.org/openstack/tacker-specs/specs/wallaby/support-cnf-scale.rst
Change-Id: I739fa60a18323b248bc913f6062ef2138ad2e6ee
This commit is contained in:
Ayumu Ueha
2020-12-07 20:15:16 +09:00
parent 22d69f971a
commit 8a8948e616
24 changed files with 2651 additions and 180 deletions

View File

@@ -0,0 +1,7 @@
---
features:
- |
Add Container based VNF scale operation support with ETSI NFV-SOL003
v2.6.1 VNF Lifecycle Management. Users can scale the number of pod replicas
managed by controller resources such as Kubernetes Deployment, StatefulSet,
and ReplicaSet.

View File

@@ -26,6 +26,7 @@ from oslo_utils import excutils
from oslo_utils import timeutils from oslo_utils import timeutils
from oslo_utils import uuidutils from oslo_utils import uuidutils
from sqlalchemy import exc as sqlexc from sqlalchemy import exc as sqlexc
from toscaparser import tosca_template
import ast import ast
import functools import functools
@@ -53,6 +54,8 @@ from tacker.objects import fields
from tacker.objects import vnf_lcm_subscriptions as subscription_obj from tacker.objects import vnf_lcm_subscriptions as subscription_obj
from tacker.plugins.common import constants from tacker.plugins.common import constants
from tacker.policies import vnf_lcm as vnf_lcm_policies from tacker.policies import vnf_lcm as vnf_lcm_policies
from tacker.tosca import utils as toscautils
from tacker.vnflcm import utils as vnflcm_utils
import tacker.vnfm.nfvo_client as nfvo_client import tacker.vnfm.nfvo_client as nfvo_client
from tacker.vnfm import vim_client from tacker.vnfm import vim_client
from tacker import wsgi from tacker import wsgi
@@ -977,7 +980,24 @@ class VnfLcmController(wsgi.Controller):
return self._make_problem_detail( return self._make_problem_detail(
str(e), 500, title='Internal Server Error') str(e), 500, title='Internal Server Error')
def _scale(self, context, vnf_info, vnf_instance, request_body): def _get_scale_max_level_from_vnfd(self, context, vnf_instance, aspect_id):
vnfd_dict = vnflcm_utils._get_vnfd_dict(context,
vnf_instance.vnfd_id,
vnf_instance.instantiated_vnf_info.flavour_id)
tosca = tosca_template.ToscaTemplate(parsed_params={}, a_file=False,
yaml_dict_tpl=vnfd_dict)
tosca_policies = tosca.topology_template.policies
aspect_max_level_dict = {}
toscautils._extract_policy_info(
tosca_policies, {}, {}, {}, {}, {}, aspect_max_level_dict)
return aspect_max_level_dict.get(aspect_id)
@check_vnf_state(action="scale",
instantiation_state=[fields.VnfInstanceState.INSTANTIATED],
task_state=[None])
def _scale(self, context, vnf_instance, vnf_info, request_body):
req_body = utils.convert_camelcase_to_snakecase(request_body) req_body = utils.convert_camelcase_to_snakecase(request_body)
scale_vnf_request = objects.ScaleVnfRequest.obj_from_primitive( scale_vnf_request = objects.ScaleVnfRequest.obj_from_primitive(
req_body, context=context) req_body, context=context)
@@ -1005,18 +1025,34 @@ class VnfLcmController(wsgi.Controller):
if not scale_vnf_request.additional_params.get('is_auto'): if not scale_vnf_request.additional_params.get('is_auto'):
scale_vnf_request.additional_params['is_auto'] = "False" scale_vnf_request.additional_params['is_auto'] = "False"
vim_type = vnf_instance.vim_connection_info[0].vim_type
if scale_vnf_request.type == 'SCALE_IN': if scale_vnf_request.type == 'SCALE_IN':
if current_level == 0 or\ if current_level == 0 or\
current_level < scale_vnf_request.number_of_steps: current_level < scale_vnf_request.number_of_steps:
return self._make_problem_detail( return self._make_problem_detail(
'can not scale_in', 400, title='can not scale_in') 'can not scale_in', 400, title='can not scale_in')
if vim_type == "kubernetes" and\
scale_vnf_request.additional_params['is_reverse'] == "True":
return self._make_problem_detail(
'is_reverse option is not supported when Kubernetes '
'scale operation',
400,
title='is_reverse option is not supported when Kubernetes '
'scale operation')
scale_level = current_level - scale_vnf_request.number_of_steps scale_level = current_level - scale_vnf_request.number_of_steps
elif scale_vnf_request.type == 'SCALE_OUT': elif scale_vnf_request.type == 'SCALE_OUT':
scaleGroupDict = jsonutils.loads( if vim_type == "kubernetes":
vnf_info['attributes']['scale_group']) max_level = self._get_scale_max_level_from_vnfd(
max_level = (scaleGroupDict['scaleGroupDict'] context=context,
[scale_vnf_request.aspect_id]['maxLevel']) vnf_instance=vnf_instance,
aspect_id=scale_vnf_request.aspect_id)
else:
scaleGroupDict = jsonutils.loads(
vnf_info['attributes']['scale_group'])
max_level = (scaleGroupDict['scaleGroupDict']
[scale_vnf_request.aspect_id]['maxLevel'])
scale_level = current_level + scale_vnf_request.number_of_steps scale_level = current_level + scale_vnf_request.number_of_steps
if max_level < scale_level: if max_level < scale_level:
return self._make_problem_detail( return self._make_problem_detail(
@@ -1043,6 +1079,9 @@ class VnfLcmController(wsgi.Controller):
error_point=1) error_point=1)
vnf_lcm_op_occ.create() vnf_lcm_op_occ.create()
vnf_instance.task_state = fields.VnfInstanceTaskState.SCALING
vnf_instance.save()
vnflcm_url = CONF.vnf_lcm.endpoint_url + \ vnflcm_url = CONF.vnf_lcm.endpoint_url + \
"/vnflcm/v1/vnf_lcm_op_occs/" + vnf_lcm_op_occs_id "/vnflcm/v1/vnf_lcm_op_occs/" + vnf_lcm_op_occs_id
insta_url = CONF.vnf_lcm.endpoint_url + \ insta_url = CONF.vnf_lcm.endpoint_url + \
@@ -1096,7 +1135,7 @@ class VnfLcmController(wsgi.Controller):
if not vnf_instance.instantiated_vnf_info.scale_status: if not vnf_instance.instantiated_vnf_info.scale_status:
return self._make_problem_detail( return self._make_problem_detail(
'NOT SCALE VNF', 409, title='NOT SCALE VNF') 'NOT SCALE VNF', 409, title='NOT SCALE VNF')
return self._scale(context, vnf_info, vnf_instance, body) return self._scale(context, vnf_instance, vnf_info, body)
except vnfm.VNFNotFound as vnf_e: except vnfm.VNFNotFound as vnf_e:
return self._make_problem_detail( return self._make_problem_detail(
str(vnf_e), 404, title='VNF NOT FOUND') str(vnf_e), 404, title='VNF NOT FOUND')

View File

@@ -453,8 +453,6 @@ class Conductor(manager.Manager):
yaml.dump(flavour.get('tpl_dict'), default_flow_style=False) yaml.dump(flavour.get('tpl_dict'), default_flow_style=False)
vnfd_attribute.create() vnfd_attribute.create()
break
@revert_upload_vnf_package @revert_upload_vnf_package
def upload_vnf_package_content(self, context, vnf_package): def upload_vnf_package_content(self, context, vnf_package):
vnf_package.onboarding_state = ( vnf_package.onboarding_state = (

View File

@@ -109,6 +109,14 @@ class CNFCreateWaitFailed(exceptions.TackerException):
message = _('CNF Create Failed with reason: %(reason)s') message = _('CNF Create Failed with reason: %(reason)s')
class CNFScaleFailed(exceptions.TackerException):
message = _('CNF Scale Failed with reason: %(reason)s')
class CNFScaleWaitFailed(exceptions.TackerException):
message = _('CNF Scale Wait Failed with reason: %(reason)s')
class ServiceTypeNotFound(exceptions.NotFound): class ServiceTypeNotFound(exceptions.NotFound):
message = _('service type %(service_type_id)s could not be found') message = _('service type %(service_type_id)s could not be found')

View File

@@ -137,9 +137,10 @@ class VnfInstanceTaskState(BaseTackerEnum):
INSTANTIATING = 'INSTANTIATING' INSTANTIATING = 'INSTANTIATING'
HEALING = 'HEALING' HEALING = 'HEALING'
TERMINATING = 'TERMINATING' TERMINATING = 'TERMINATING'
SCALING = 'SCALING'
ERROR = 'ERROR' ERROR = 'ERROR'
ALL = (INSTANTIATING, HEALING, TERMINATING, ERROR) ALL = (INSTANTIATING, HEALING, TERMINATING, SCALING, ERROR)
class VnfInstanceTaskStateField(BaseEnumField): class VnfInstanceTaskStateField(BaseEnumField):

View File

@@ -0,0 +1,105 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- helloworld3_types.yaml
topology_template:
inputs:
descriptor_id:
type: string
descriptor_version:
type: string
provider:
type: string
product_name:
type: string
software_version:
type: string
vnfm_info:
type: list
entry_schema:
type: string
flavour_id:
type: string
flavour_description:
type: string
substitution_mappings:
node_type: company.provider.VNF
properties:
flavour_id: scalingsteps
requirements:
virtual_link_external: []
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_description: A flavour for setting scaling_step to 2
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: vdu1
description: kubernetes controller resource as VDU
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 5
policies:
- scaling_aspects:
type: tosca.policies.nfv.ScalingAspects
properties:
aspects:
vdu1_aspect:
name: vdu1_aspect
description: vdu1 scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
- vdu1_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU1 ]
- vdu1_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: vdu1_aspect
deltas:
delta_1:
number_of_instances: 2
targets: [ VDU1 ]
- instantiation_levels:
type: tosca.policies.nfv.InstantiationLevels
properties:
levels:
instantiation_level_1:
description: Smallest size
scale_info:
vdu1_aspect:
scale_level: 0
instantiation_level_2:
description: Largest size
scale_info:
vdu1_aspect:
scale_level: 2
default_level: instantiation_level_1
- vdu1_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 5
targets: [ VDU1 ]

View File

@@ -0,0 +1,105 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- helloworld3_types.yaml
topology_template:
inputs:
descriptor_id:
type: string
descriptor_version:
type: string
provider:
type: string
product_name:
type: string
software_version:
type: string
vnfm_info:
type: list
entry_schema:
type: string
flavour_id:
type: string
flavour_description:
type: string
substitution_mappings:
node_type: company.provider.VNF
properties:
flavour_id: simple
requirements:
virtual_link_external: []
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_description: A simple flavour
VDU1:
type: tosca.nodes.nfv.Vdu.Compute
properties:
name: vdu1
description: kubernetes controller resource as VDU
vdu_profile:
min_number_of_instances: 1
max_number_of_instances: 3
policies:
- scaling_aspects:
type: tosca.policies.nfv.ScalingAspects
properties:
aspects:
vdu1_aspect:
name: vdu1_aspect
description: vdu1 scaling aspect
max_scale_level: 2
step_deltas:
- delta_1
- vdu1_initial_delta:
type: tosca.policies.nfv.VduInitialDelta
properties:
initial_delta:
number_of_instances: 1
targets: [ VDU1 ]
- vdu1_scaling_aspect_deltas:
type: tosca.policies.nfv.VduScalingAspectDeltas
properties:
aspect: vdu1_aspect
deltas:
delta_1:
number_of_instances: 1
targets: [ VDU1 ]
- instantiation_levels:
type: tosca.policies.nfv.InstantiationLevels
properties:
levels:
instantiation_level_1:
description: Smallest size
scale_info:
vdu1_aspect:
scale_level: 0
instantiation_level_2:
description: Largest size
scale_info:
vdu1_aspect:
scale_level: 2
default_level: instantiation_level_1
- vdu1_instantiation_levels:
type: tosca.policies.nfv.VduInstantiationLevels
properties:
levels:
instantiation_level_1:
number_of_instances: 1
instantiation_level_2:
number_of_instances: 3
targets: [ VDU1 ]

View File

@@ -0,0 +1,32 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: Sample VNF
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
- helloworld3_types.yaml
- helloworld3_df_simple.yaml
- helloworld3_df_scalingsteps.yaml
topology_template:
inputs:
selected_flavour:
type: string
description: VNF deployment flavour selected by the consumer. It is provided in the API
node_templates:
VNF:
type: company.provider.VNF
properties:
flavour_id: { get_input: selected_flavour }
descriptor_id: b1bb0ce7-ebca-4fa7-95ed-4840d70a1177
provider: Company
product_name: Sample VNF
software_version: '1.0'
descriptor_version: '1.0'
vnfm_info:
- Tacker
requirements:
#- virtual_link_external # mapped in lower-level templates
#- virtual_link_internal # mapped in lower-level templates

View File

@@ -0,0 +1,53 @@
tosca_definitions_version: tosca_simple_yaml_1_2
description: VNF type definition
imports:
- etsi_nfv_sol001_common_types.yaml
- etsi_nfv_sol001_vnfd_types.yaml
node_types:
company.provider.VNF:
derived_from: tosca.nodes.nfv.VNF
properties:
descriptor_id:
type: string
constraints: [ valid_values: [ b1bb0ce7-ebca-4fa7-95ed-4840d70a1177 ] ]
default: b1bb0ce7-ebca-4fa7-95ed-4840d70a1177
descriptor_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
provider:
type: string
constraints: [ valid_values: [ 'Company' ] ]
default: 'Company'
product_name:
type: string
constraints: [ valid_values: [ 'Sample VNF' ] ]
default: 'Sample VNF'
software_version:
type: string
constraints: [ valid_values: [ '1.0' ] ]
default: '1.0'
vnfm_info:
type: list
entry_schema:
type: string
constraints: [ valid_values: [ Tacker ] ]
default: [ Tacker ]
flavour_id:
type: string
constraints: [ valid_values: [ simple,scalingsteps ] ]
default: simple
flavour_description:
type: string
default: ""
requirements:
- virtual_link_external:
capability: tosca.capabilities.nfv.VirtualLinkable
- virtual_link_internal:
capability: tosca.capabilities.nfv.VirtualLinkable
interfaces:
Vnflcm:
type: tosca.interfaces.nfv.Vnflcm

View File

@@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: vdu1
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: webserver
template:
metadata:
labels:
app: webserver
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
protocol: TCP

View File

@@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: vdu1
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: webserver
template:
metadata:
labels:
app: webserver
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
protocol: TCP

View File

@@ -0,0 +1,61 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: vdu1
namespace: default
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: k8s.gcr.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 64Mi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: www-vdu1-0
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 64Mi
hostPath:
path: /data
type: DirectoryOrCreate
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: www-vdu1-1
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 64Mi
hostPath:
path: /data
type: DirectoryOrCreate

View File

@@ -0,0 +1,19 @@
TOSCA-Meta-File-Version: 1.0
Created-by: dummy_user
CSAR-Version: 1.1
Entry-Definitions: Definitions/helloworld3_top.vnfd.yaml
Name: Files/kubernetes/deployment_scale.yaml
Content-Type: application/yaml
Algorithm: SHA-256
Hash: 8f8c45d63880fe9190e49c42458bf40c6aa7752ec84049db1027389715e12840
Name: Files/kubernetes/statefulset_scale.yaml
Content-Type: application/yaml
Algorithm: SHA-256
Hash: a9c35728419fb1e72ba73362fb17472afc63010a82ad4e3b84f3cf52161186e4
Name: Files/kubernetes/replicaset_scale.yaml
Content-Type: application/yaml
Algorithm: SHA-256
Hash: 6585f8e34425c3c5ffd5ca40a262acaea1ede7fc7caa9920e5af8deb2d8dd83c

View File

@@ -0,0 +1,484 @@
# Copyright (C) 2020 FUJITSU
# All Rights Reserved.
#
# 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 os
import time
import unittest
from oslo_serialization import jsonutils
from oslo_utils import uuidutils
from sqlalchemy import desc
from sqlalchemy.orm import joinedload
from tacker.common import exceptions
from tacker import context
from tacker.db import api as db_api
from tacker.db.db_sqlalchemy import api
from tacker.db.db_sqlalchemy import models
from tacker.objects import fields
from tacker.objects import vnf_lcm_op_occs
from tacker.tests.functional import base
from tacker.tests import utils
VNF_PACKAGE_UPLOAD_TIMEOUT = 300
VNF_INSTANTIATE_TIMEOUT = 600
VNF_TERMINATE_TIMEOUT = 600
VNF_SCALE_TIMEOUT = 600
RETRY_WAIT_TIME = 5
def _create_and_upload_vnf_package(tacker_client, csar_package_name,
user_defined_data):
# create vnf package
body = jsonutils.dumps({"userDefinedData": user_defined_data})
resp, vnf_package = tacker_client.do_request(
'/vnfpkgm/v1/vnf_packages', "POST", body=body)
# upload vnf package
csar_package_path = "../../../etc/samples/etsi/nfv/%s" % csar_package_name
file_path = os.path.abspath(os.path.join(os.path.dirname(__file__),
csar_package_path))
# Generating unique vnfd id. This is required when multiple workers
# are running concurrently. The call below creates a new temporary
# CSAR with unique vnfd id.
file_path, uniqueid = utils.create_csar_with_unique_vnfd_id(file_path)
with open(file_path, 'rb') as file_object:
resp, resp_body = tacker_client.do_request(
'/vnfpkgm/v1/vnf_packages/{id}/package_content'.format(
id=vnf_package['id']),
"PUT", body=file_object, content_type='application/zip')
# wait for onboard
timeout = VNF_PACKAGE_UPLOAD_TIMEOUT
start_time = int(time.time())
show_url = os.path.join('/vnfpkgm/v1/vnf_packages', vnf_package['id'])
vnfd_id = None
while True:
resp, body = tacker_client.do_request(show_url, "GET")
if body['onboardingState'] == "ONBOARDED":
vnfd_id = body['vnfdId']
break
if ((int(time.time()) - start_time) > timeout):
raise Exception("Failed to onboard vnf package")
time.sleep(1)
# remove temporarily created CSAR file
os.remove(file_path)
return vnf_package['id'], vnfd_id
def _delete_wait_vnf_instance(tacker_client, id):
timeout = VNF_TERMINATE_TIMEOUT
url = os.path.join("/vnflcm/v1/vnf_instances", id)
start_time = int(time.time())
while True:
resp, body = tacker_client.do_request(url, "DELETE")
if 204 == resp.status_code:
break
if ((int(time.time()) - start_time) > timeout):
raise Exception("Failed to delete vnf instance")
time.sleep(RETRY_WAIT_TIME)
def _delete_vnf_instance(tacker_client, id):
_delete_wait_vnf_instance(tacker_client, id)
# verify vnf instance is deleted
url = os.path.join("/vnflcm/v1/vnf_instances", id)
resp, body = tacker_client.do_request(url, "GET")
def _show_vnf_instance(tacker_client, id):
show_url = os.path.join("/vnflcm/v1/vnf_instances", id)
resp, vnf_instance = tacker_client.do_request(show_url, "GET")
return vnf_instance
def _vnf_instance_wait(
tacker_client, id,
instantiation_state=fields.VnfInstanceState.INSTANTIATED,
timeout=VNF_INSTANTIATE_TIMEOUT):
show_url = os.path.join("/vnflcm/v1/vnf_instances", id)
start_time = int(time.time())
while True:
resp, body = tacker_client.do_request(show_url, "GET")
if body['instantiationState'] == instantiation_state:
break
if ((int(time.time()) - start_time) > timeout):
raise Exception("Failed to wait vnf instance")
time.sleep(RETRY_WAIT_TIME)
def _terminate_vnf_instance(tacker_client, id, request_body):
url = os.path.join("/vnflcm/v1/vnf_instances", id, "terminate")
resp, body = tacker_client.do_request(
url, "POST", body=jsonutils.dumps(request_body))
timeout = request_body.get('gracefulTerminationTimeout')
start_time = int(time.time())
_vnf_instance_wait(
tacker_client, id,
instantiation_state=fields.VnfInstanceState.NOT_INSTANTIATED,
timeout=VNF_TERMINATE_TIMEOUT)
# If gracefulTerminationTimeout is set, check whether vnf
# instantiation_state is set to NOT_INSTANTIATED after
# gracefulTerminationTimeout seconds.
if timeout and int(time.time()) - start_time < timeout:
raise Exception("Failed to terminate vnf instance")
class VnfLcmKubernetesScaleTest(base.BaseTackerTest):
@classmethod
def setUpClass(cls):
cls.tacker_client = base.BaseTackerTest.tacker_http_client()
cls.vnf_package_resource, cls.vnfd_id_resource = \
_create_and_upload_vnf_package(
cls.tacker_client, "test_cnf_scale",
{"key": "sample_scale_functional"})
cls.vnf_instance_ids = []
super(VnfLcmKubernetesScaleTest, cls).setUpClass()
@classmethod
def tearDownClass(cls):
# Terminate vnf forcefully
terminate_req_body = {
"terminationType": fields.VnfInstanceTerminationType.FORCEFUL,
}
for id in cls.vnf_instance_ids:
_terminate_vnf_instance(cls.tacker_client, id,
terminate_req_body)
_delete_vnf_instance(cls.tacker_client, id)
# Update vnf package operational state to DISABLED
update_req_body = jsonutils.dumps({
"operationalState": "DISABLED"})
base_path = "/vnfpkgm/v1/vnf_packages"
for package_id in [cls.vnf_package_resource]:
resp, resp_body = cls.tacker_client.do_request(
'{base_path}/{id}'.format(id=package_id,
base_path=base_path),
"PATCH", content_type='application/json',
body=update_req_body)
# Delete vnf package
url = '/vnfpkgm/v1/vnf_packages/%s' % package_id
cls.tacker_client.do_request(url, "DELETE")
super(VnfLcmKubernetesScaleTest, cls).tearDownClass()
def setUp(self):
super(VnfLcmKubernetesScaleTest, self).setUp()
self.base_vnf_instances_url = "/vnflcm/v1/vnf_instances"
self.base_vnf_lcm_op_occs_url = "/vnflcm/v1/vnf_lcm_op_occs"
self.context = context.get_admin_context()
vim_list = self.client.list_vims()
if not vim_list:
self.skipTest("Vims are not configured")
vim_id = 'vim-kubernetes'
vim = self.get_vim(vim_list, vim_id)
if not vim:
self.skipTest("Kubernetes VIM '%s' is missing" % vim_id)
self.vim_id = vim['id']
def _instantiate_vnf_instance_request(
self, flavour_id, vim_id=None, additional_param=None):
request_body = {"flavourId": flavour_id}
if vim_id:
request_body["vimConnectionInfo"] = [
{"id": uuidutils.generate_uuid(),
"vimId": vim_id,
"vimType": "kubernetes"}]
if additional_param:
request_body["additionalParams"] = additional_param
return request_body
def _create_vnf_instance(self, vnfd_id, vnf_instance_name=None,
vnf_instance_description=None):
request_body = {'vnfdId': vnfd_id}
if vnf_instance_name:
request_body['vnfInstanceName'] = vnf_instance_name
if vnf_instance_description:
request_body['vnfInstanceDescription'] = vnf_instance_description
resp, response_body = self.http_client.do_request(
self.base_vnf_instances_url, "POST",
body=jsonutils.dumps(request_body))
return resp, response_body
def _instantiate_vnf_instance(self, id, request_body):
url = os.path.join(self.base_vnf_instances_url, id, "instantiate")
resp, body = self.http_client.do_request(
url, "POST", body=jsonutils.dumps(request_body))
self.assertEqual(202, resp.status_code)
_vnf_instance_wait(self.tacker_client, id)
def _create_and_instantiate_vnf_instance(self, flavour_id,
additional_params):
# create vnf instance
vnf_instance_name = "test_vnf_instance_for_cnf_scale-%s" % \
uuidutils.generate_uuid()
vnf_instance_description = "vnf instance for cnf scale testing"
resp, vnf_instance = self._create_vnf_instance(
self.vnfd_id_resource, vnf_instance_name=vnf_instance_name,
vnf_instance_description=vnf_instance_description)
# instantiate vnf instance
additional_param = additional_params
request_body = self._instantiate_vnf_instance_request(
flavour_id, vim_id=self.vim_id, additional_param=additional_param)
self._instantiate_vnf_instance(vnf_instance['id'], request_body)
vnf_instance = _show_vnf_instance(
self.tacker_client, vnf_instance['id'])
self.vnf_instance_ids.append(vnf_instance['id'])
return vnf_instance
def _scale_vnf_instance(self, id, type, aspect_id,
number_of_steps=1):
url = os.path.join(self.base_vnf_instances_url, id, "scale")
# generate body
request_body = {
"type": type,
"aspectId": aspect_id,
"numberOfSteps": number_of_steps}
resp, body = self.http_client.do_request(
url, "POST", body=jsonutils.dumps(request_body))
self.assertEqual(202, resp.status_code)
def _test_scale_cnf(self, id, type, aspect_id, previous_level,
delta_num=1, number_of_steps=1):
# scale operation
self._scale_vnf_instance(id, type, aspect_id, number_of_steps)
# wait vnflcm_op_occs.operation_state become COMPLETE
self._wait_vnflcm_op_occs(self.context, id)
# check scaleStatus after scale operation
vnf_instance = _show_vnf_instance(
self.tacker_client, id)
scale_status_after = \
vnf_instance['instantiatedVnfInfo']['scaleStatus']
if type == 'SCALE_OUT':
expected_level = previous_level + number_of_steps
else:
expected_level = previous_level - number_of_steps
for status in scale_status_after:
if status.get('aspectId') == aspect_id:
self.assertEqual(status.get('scaleLevel'), expected_level)
previous_level = status.get('scaleLevel')
return previous_level
def _test_scale_cnf_fail(self, id, type, aspect_id, previous_level,
delta_num=1, number_of_steps=1):
# scale operation
self._scale_vnf_instance(id, type, aspect_id, number_of_steps)
# wait vnflcm_op_occs.operation_state become FAILED_TEMP
self._wait_vnflcm_op_occs(self.context, id, "FAILED_TEMP")
# check scaleStatus after scale operation
vnf_instance = _show_vnf_instance(
self.tacker_client, id)
scale_status_after = \
vnf_instance['instantiatedVnfInfo']['scaleStatus']
expected_level = previous_level
for status in scale_status_after:
if status.get('aspectId') == aspect_id:
self.assertEqual(status.get('scaleLevel'), expected_level)
previous_level = status.get('scaleLevel')
return previous_level
def _rollback_vnf_instance(self, vnf_lcm_op_occ_id):
url = os.path.join(
self.base_vnf_lcm_op_occs_url, vnf_lcm_op_occ_id, "rollback")
# generate body
resp, body = self.http_client.do_request(url, "POST")
self.assertEqual(202, resp.status_code)
def _test_rollback_cnf(self, id, aspect_id, previous_level,
delta_num=1, number_of_steps=1):
# get vnflcm_op_occ id for rollback
vnflcm_op_occ = self._vnf_notify_get_by_id(self.context, id)
vnf_lcm_op_occ_id = vnflcm_op_occ.id
# rollback operation
self._rollback_vnf_instance(vnf_lcm_op_occ_id)
# wait vnflcm_op_occs.operation_state become ROLLED_BACK
self._wait_vnflcm_op_occs(self.context, id, "ROLLED_BACK")
# check scaleStatus after scale operation
vnf_instance = _show_vnf_instance(
self.tacker_client, id)
scale_status_after = \
vnf_instance['instantiatedVnfInfo']['scaleStatus']
expected_level = previous_level
for status in scale_status_after:
if status.get('aspectId') == aspect_id:
self.assertEqual(status.get('scaleLevel'), expected_level)
previous_level = status.get('scaleLevel')
@db_api.context_manager.reader
def _vnf_notify_get_by_id(self, context, vnf_instance_id,
columns_to_join=None):
query = api.model_query(
context, models.VnfLcmOpOccs,
read_deleted="no", project_only=True).filter_by(
vnf_instance_id=vnf_instance_id).order_by(
desc("created_at"))
if columns_to_join:
for column in columns_to_join:
query = query.options(joinedload(column))
db_vnflcm_op_occ = query.first()
if not db_vnflcm_op_occ:
raise exceptions.VnfInstanceNotFound(id=vnf_instance_id)
vnflcm_op_occ = vnf_lcm_op_occs.VnfLcmOpOcc.obj_from_db_obj(
context, db_vnflcm_op_occ)
return vnflcm_op_occ
def _wait_vnflcm_op_occs(
self, context, vnf_instance_id,
operation_state='COMPLETED'):
timeout = VNF_SCALE_TIMEOUT
start_time = int(time.time())
while True:
vnflcm_op_occ = self._vnf_notify_get_by_id(
context, vnf_instance_id)
if vnflcm_op_occ.operation_state == operation_state:
break
if ((int(time.time()) - start_time) > timeout):
raise Exception("Failed to wait scale instance")
time.sleep(RETRY_WAIT_TIME)
def test_scale_cnf_with_statefulset(self):
inst_additional_param = {
"lcm-kubernetes-def-files": [
"Files/kubernetes/statefulset_scale.yaml"]}
vnf_instance = self._create_and_instantiate_vnf_instance(
"simple", inst_additional_param)
aspect_id = "vdu1_aspect"
scale_status_initial = \
vnf_instance['instantiatedVnfInfo']['scaleStatus']
self.assertTrue(len(scale_status_initial) > 0)
for status in scale_status_initial:
self.assertIsNotNone(status.get('aspectId'))
self.assertIsNotNone(status.get('scaleLevel'))
if status.get('aspectId') == aspect_id:
previous_level = status.get('scaleLevel')
# test scale out
previous_level = self._test_scale_cnf(
vnf_instance['id'], 'SCALE_OUT', aspect_id, previous_level)
# test scale in
previous_level = self._test_scale_cnf(
vnf_instance['id'], 'SCALE_IN', aspect_id, previous_level)
def test_scale_cnf_with_replicaset(self):
inst_additional_param = {
"lcm-kubernetes-def-files": [
"Files/kubernetes/replicaset_scale.yaml"]}
vnf_instance = self._create_and_instantiate_vnf_instance(
"simple", inst_additional_param)
aspect_id = "vdu1_aspect"
scale_status_initial = \
vnf_instance['instantiatedVnfInfo']['scaleStatus']
self.assertTrue(len(scale_status_initial) > 0)
for status in scale_status_initial:
self.assertIsNotNone(status.get('aspectId'))
self.assertIsNotNone(status.get('scaleLevel'))
if status.get('aspectId') == aspect_id:
previous_level = status.get('scaleLevel')
# test scale out
previous_level = self._test_scale_cnf(
vnf_instance['id'], 'SCALE_OUT', aspect_id, previous_level)
# test scale in
previous_level = self._test_scale_cnf(
vnf_instance['id'], 'SCALE_IN', aspect_id, previous_level)
def test_scale_cnf_deployment_with_scaling_and_delta_two(self):
inst_additional_param = {
"lcm-kubernetes-def-files": [
"Files/kubernetes/deployment_scale.yaml"]}
# Use flavour_id scalingsteps that is set to delta_num=2
vnf_instance = self._create_and_instantiate_vnf_instance(
"scalingsteps", inst_additional_param)
aspect_id = "vdu1_aspect"
scale_status_initial = \
vnf_instance['instantiatedVnfInfo']['scaleStatus']
self.assertTrue(len(scale_status_initial) > 0)
for status in scale_status_initial:
self.assertIsNotNone(status.get('aspectId'))
self.assertIsNotNone(status.get('scaleLevel'))
if status.get('aspectId') == aspect_id:
previous_level = status.get('scaleLevel')
# test scale out (test for delta_num=2 and number_of_steps=2)
previous_level = self._test_scale_cnf(
vnf_instance['id'], 'SCALE_OUT', aspect_id, previous_level,
delta_num=2, number_of_steps=2)
# test scale in (test for delta_num=2 and number_of_steps=2)
previous_level = self._test_scale_cnf(
vnf_instance['id'], 'SCALE_IN', aspect_id, previous_level,
delta_num=2, number_of_steps=2)
@unittest.skip("Reduce test time")
def test_scale_out_cnf_rollback(self):
inst_additional_param = {
"lcm-kubernetes-def-files": [
"Files/kubernetes/statefulset_scale.yaml"]}
vnf_instance = self._create_and_instantiate_vnf_instance(
"simple", inst_additional_param)
aspect_id = "vdu1_aspect"
scale_status_initial = \
vnf_instance['instantiatedVnfInfo']['scaleStatus']
self.assertTrue(len(scale_status_initial) > 0)
for status in scale_status_initial:
self.assertIsNotNone(status.get('aspectId'))
self.assertIsNotNone(status.get('scaleLevel'))
if status.get('aspectId') == aspect_id:
previous_level = status.get('scaleLevel')
# fail scale out for rollback
previous_level = self._test_scale_cnf_fail(
vnf_instance['id'], 'SCALE_OUT', aspect_id, previous_level,
number_of_steps=2)
# test rollback
self._test_rollback_cnf(vnf_instance['id'], aspect_id, previous_level)

View File

@@ -213,6 +213,9 @@ class FakeVNFMPlugin(mock.Mock):
'name': 'dummy_vnf_update', 'name': 'dummy_vnf_update',
'attributes': {}} 'attributes': {}}
def _update_vnf_scaling(self, *args, **kwargs):
pass
class TestNfvoPlugin(db_base.SqlTestCase): class TestNfvoPlugin(db_base.SqlTestCase):
def setUp(self): def setUp(self):

View File

@@ -184,8 +184,9 @@ def return_vnf_instance(
} }
instantiated_vnf_info = get_instantiated_vnf_info instantiated_vnf_info = get_instantiated_vnf_info
s_status = {"aspect_id": "SP1", "scale_level": 1} if scale_status == "scale_status":
scale_status = objects.ScaleInfo(**s_status) s_status = {"aspect_id": "SP1", "scale_level": 1}
scale_status = objects.ScaleInfo(**s_status)
instantiated_vnf_info.update( instantiated_vnf_info.update(
{"ext_cp_info": [], {"ext_cp_info": [],
@@ -792,10 +793,10 @@ def _get_vnf(**updates):
return vnf_data return vnf_data
def scale_request(type, number_of_steps, is_reverse): def scale_request(type, aspect_id, number_of_steps, is_reverse):
scale_request_data = { scale_request_data = {
'type': type, 'type': type,
'aspect_id': "SP1", 'aspect_id': aspect_id,
'number_of_steps': number_of_steps, 'number_of_steps': number_of_steps,
'scale_level': 1, 'scale_level': 1,
'additional_params': {"is_reverse": is_reverse}, 'additional_params': {"is_reverse": is_reverse},
@@ -844,12 +845,41 @@ def vnf_scale():
vim_id=uuidsentinel.vim_id) vim_id=uuidsentinel.vim_id)
def vnflcm_rollback(error_point=7): def vnflcm_scale_in_cnf():
dt = datetime.datetime(2000, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
return objects.VnfLcmOpOcc( return objects.VnfLcmOpOcc(
state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, state_entered_time=dt,
tzinfo=iso8601.UTC), start_time=dt,
start_time=datetime.datetime(2000, 1, 1, 1, 1, 1, vnf_instance_id=uuidsentinel.vnf_instance_id,
tzinfo=iso8601.UTC), operation='SCALE',
operation_state='STARTING',
is_automatic_invocation=False,
operation_params='{"type": "SCALE_IN", "aspect_id": "vdu1_aspect"}',
error_point=1,
id=constants.UUID,
created_at=dt)
def vnflcm_scale_out_cnf():
dt = datetime.datetime(2000, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
return objects.VnfLcmOpOcc(
state_entered_time=dt,
start_time=dt,
vnf_instance_id=uuidsentinel.vnf_instance_id,
operation='SCALE',
operation_state='STARTING',
is_automatic_invocation=False,
operation_params='{"type": "SCALE_OUT", "aspect_id": "vdu1_aspect"}',
error_point=1,
id=constants.UUID,
created_at=dt)
def vnflcm_rollback(error_point=7):
dt = datetime.datetime(2000, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
return objects.VnfLcmOpOcc(
state_entered_time=dt,
start_time=dt,
vnf_instance_id=uuidsentinel.vnf_instance_id, vnf_instance_id=uuidsentinel.vnf_instance_id,
operation='SCALE', operation='SCALE',
operation_state='FAILED_TEMP', operation_state='FAILED_TEMP',
@@ -857,16 +887,14 @@ def vnflcm_rollback(error_point=7):
operation_params='{"type": "SCALE_OUT", "aspect_id": "SP1"}', operation_params='{"type": "SCALE_OUT", "aspect_id": "SP1"}',
error_point=error_point, error_point=error_point,
id=constants.UUID, id=constants.UUID,
created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, created_at=dt)
tzinfo=iso8601.UTC))
def vnflcm_rollback_insta(error_point=7): def vnflcm_rollback_insta(error_point=7):
dt = datetime.datetime(2000, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
return objects.VnfLcmOpOcc( return objects.VnfLcmOpOcc(
state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, state_entered_time=dt,
tzinfo=iso8601.UTC), start_time=dt,
start_time=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
vnf_instance_id=uuidsentinel.vnf_instance_id, vnf_instance_id=uuidsentinel.vnf_instance_id,
operation='INSTANTIATE', operation='INSTANTIATE',
operation_state='FAILED_TEMP', operation_state='FAILED_TEMP',
@@ -874,16 +902,14 @@ def vnflcm_rollback_insta(error_point=7):
operation_params='{}', operation_params='{}',
error_point=error_point, error_point=error_point,
id=constants.UUID, id=constants.UUID,
created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, created_at=dt)
tzinfo=iso8601.UTC))
def vnflcm_rollback_active(): def vnflcm_rollback_active():
dt = datetime.datetime(2000, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
return objects.VnfLcmOpOcc( return objects.VnfLcmOpOcc(
state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, state_entered_time=dt,
tzinfo=iso8601.UTC), start_time=dt,
start_time=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
vnf_instance_id=uuidsentinel.vnf_instance_id, vnf_instance_id=uuidsentinel.vnf_instance_id,
operation='SCALE', operation='SCALE',
operation_state='ACTIVE', operation_state='ACTIVE',
@@ -891,16 +917,14 @@ def vnflcm_rollback_active():
operation_params='{"type": "SCALE_OUT", "aspect_id": "SP1"}', operation_params='{"type": "SCALE_OUT", "aspect_id": "SP1"}',
error_point=7, error_point=7,
id=constants.UUID, id=constants.UUID,
created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, created_at=dt)
tzinfo=iso8601.UTC))
def vnflcm_rollback_ope(): def vnflcm_rollback_ope():
dt = datetime.datetime(2000, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
return objects.VnfLcmOpOcc( return objects.VnfLcmOpOcc(
state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, state_entered_time=dt,
tzinfo=iso8601.UTC), start_time=dt,
start_time=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
vnf_instance_id=uuidsentinel.vnf_instance_id, vnf_instance_id=uuidsentinel.vnf_instance_id,
operation='HEAL', operation='HEAL',
operation_state='FAILED_TEMP', operation_state='FAILED_TEMP',
@@ -908,16 +932,14 @@ def vnflcm_rollback_ope():
operation_params='{}', operation_params='{}',
error_point=7, error_point=7,
id=constants.UUID, id=constants.UUID,
created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, created_at=dt)
tzinfo=iso8601.UTC))
def vnflcm_rollback_scale_in(): def vnflcm_rollback_scale_in():
dt = datetime.datetime(2000, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
return objects.VnfLcmOpOcc( return objects.VnfLcmOpOcc(
state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1, state_entered_time=dt,
tzinfo=iso8601.UTC), start_time=dt,
start_time=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
vnf_instance_id=uuidsentinel.vnf_instance_id, vnf_instance_id=uuidsentinel.vnf_instance_id,
operation='SCALE', operation='SCALE',
operation_state='FAILED_TEMP', operation_state='FAILED_TEMP',
@@ -925,8 +947,7 @@ def vnflcm_rollback_scale_in():
operation_params='{"type": "SCALE_IN", "aspect_id": "SP1"}', operation_params='{"type": "SCALE_IN", "aspect_id": "SP1"}',
error_point=7, error_point=7,
id=constants.UUID, id=constants.UUID,
created_at=datetime.datetime(2000, 1, 1, 1, 1, 1, created_at=dt)
tzinfo=iso8601.UTC))
def vnf_rollback(): def vnf_rollback():
@@ -1090,6 +1111,110 @@ def vnf_dict():
return vnf_dict return vnf_dict
def vnf_dict_cnf():
vnf_dict = {
'attributes': {},
'status': 'ACTIVE',
'vnfd_id': 'e889e4fe-52fe-437d-b1e1-a690dc95e3f8',
'tenant_id': '13d2ca8de70d48b2a2e0dbac2c327c0b',
'vim_id': '3f41faa7-5630-47d2-9d4a-1216953c8887',
'instance_id': 'd1121d3c-368b-4ac2-b39d-835aa3e4ccd8',
'placement_attr': {'vim_name': 'kubernetes-vim'},
'id': '436aaa6e-2db6-4d6e-a3fc-e728b2f0ac56',
'name': 'cnf_create_1',
'vnfd': {
'attributes': {
'vnfd_simple': 'dummy'
}
}
}
return vnf_dict
def vnfd_dict_cnf():
tacker_dir = os.getcwd()
def_dir = tacker_dir + "/samples/vnf_packages/Definitions/"
vnfd_dict = {
"tosca_definitions_version": "tosca_simple_yaml_1_2",
"description": "Sample VNF flavour for Sample VNF",
"imports": [
def_dir + "etsi_nfv_sol001_common_types.yaml",
def_dir + "etsi_nfv_sol001_vnfd_types.yaml",
def_dir + "helloworld3_types.yaml"],
"topology_template": {
"node_templates": {
"VNF": {
"type": "company.provider.VNF",
"properties": {
"flavour_description": "A simple flavour"}},
"VDU1": {
"type": "tosca.nodes.nfv.Vdu.Compute",
"properties": {
"name": "vdu1",
"description": "vdu1 compute node",
"vdu_profile": {
"min_number_of_instances": 1,
"max_number_of_instances": 3}}}},
"policies": [
{
"scaling_aspects": {
"type": "tosca.policies.nfv.ScalingAspects",
"properties": {
"aspects": {
"vdu1_aspect": {
"name": "vdu1_aspect",
"description": "vdu1 scaling aspect",
"max_scale_level": 2,
"step_deltas": ["delta_1"]}}}}},
{
"vdu1_initial_delta": {
"type": "tosca.policies.nfv.VduInitialDelta",
"properties": {
"initial_delta": {
"number_of_instances": 0}},
"targets": ["VDU1"]}},
{
"vdu1_scaling_aspect_deltas": {
"type": "tosca.policies.nfv.VduScalingAspectDeltas",
"properties": {
"aspect": "vdu1_aspect",
"deltas": {
"delta_1": {
"number_of_instances": 1}}},
"targets": ["VDU1"]}},
{
"instantiation_levels": {
"type": "tosca.policies.nfv.InstantiationLevels",
"properties": {
"levels": {
"instantiation_level_1": {
"description": "Smallest size",
"scale_info": {
"vdu1_aspect": {
"scale_level": 0}}},
"instantiation_level_2": {
"description": "Largest size",
"scale_info": {
"vdu1_aspect": {
"scale_level": 2}}}
},
"default_level": "instantiation_level_1"}}},
{
"vdu1_instantiation_levels": {
"type": "tosca.policies.nfv.VduInstantiationLevels",
"properties": {
"levels": {
"instantiation_level_1": {
"number_of_instances": 0},
"instantiation_level_2": {
"number_of_instances": 2}}},
"targets": ["VDU1"]}}
]
}
}
return vnfd_dict
class InjectContext(wsgi.Middleware): class InjectContext(wsgi.Middleware):
"""Add a 'tacker.context' to WSGI environ.""" """Add a 'tacker.context' to WSGI environ."""
@@ -1294,13 +1419,12 @@ def fake_vnf_lcm_op_occs():
} }
changed_info_obj = objects.VnfInfoModifications(**changed_info) changed_info_obj = objects.VnfInfoModifications(**changed_info)
dt = datetime.datetime(1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
vnf_lcm_op_occs = { vnf_lcm_op_occs = {
'id': constants.UUID, 'id': constants.UUID,
'operation_state': 'COMPLETED', 'operation_state': 'COMPLETED',
'state_entered_time': datetime.datetime(1900, 1, 1, 1, 1, 1, 'state_entered_time': dt,
tzinfo=iso8601.UTC), 'start_time': dt,
'start_time': datetime.datetime(1900, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
'vnf_instance_id': constants.UUID, 'vnf_instance_id': constants.UUID,
'operation': 'MODIFY_INFO', 'operation': 'MODIFY_INFO',
'is_automatic_invocation': False, 'is_automatic_invocation': False,

View File

@@ -69,6 +69,8 @@ class FakeVNFMPlugin(mock.Mock):
self.vnf3_vnfd_id = 'e4015e9f-1ef2-49fb-adb6-070791ad3c45' self.vnf3_vnfd_id = 'e4015e9f-1ef2-49fb-adb6-070791ad3c45'
self.vnf3_vnf_id = '7168062e-9fa1-4203-8cb7-f5c99ff3ee1b' self.vnf3_vnf_id = '7168062e-9fa1-4203-8cb7-f5c99ff3ee1b'
self.vnf3_update_vnf_id = '10f66bc5-b2f1-45b7-a7cd-6dd6ad0017f5' self.vnf3_update_vnf_id = '10f66bc5-b2f1-45b7-a7cd-6dd6ad0017f5'
self.vnf_for_cnf_vnfd_id = 'e889e4fe-52fe-437d-b1e1-a690dc95e3f8'
self.vnf_for_cnf_vnf_id = '436aaa6e-2db6-4d6e-a3fc-e728b2f0ac56'
self.cp11_id = 'd18c8bae-898a-4932-bff8-d5eac981a9c9' self.cp11_id = 'd18c8bae-898a-4932-bff8-d5eac981a9c9'
self.cp11_update_id = 'a18c8bae-898a-4932-bff8-d5eac981a9b8' self.cp11_update_id = 'a18c8bae-898a-4932-bff8-d5eac981a9b8'
@@ -110,6 +112,8 @@ class FakeVNFMPlugin(mock.Mock):
return self.get_dummy_vnf_error() return self.get_dummy_vnf_error()
elif self.vnf3_vnf_id in args: elif self.vnf3_vnf_id in args:
return self.get_dummy_vnf_not_error() return self.get_dummy_vnf_not_error()
elif self.vnf_for_cnf_vnf_id in args:
return fakes.vnf_dict_cnf()
else: else:
return self.get_dummy_vnf_active() return self.get_dummy_vnf_active()
@@ -2369,6 +2373,7 @@ class TestController(base.TestCase):
return_value={'VNFM': FakeVNFMPlugin()}) return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.VnfLcmOpOcc, "create") @mock.patch.object(objects.VnfLcmOpOcc, "create")
@mock.patch.object(objects.ScaleVnfRequest, "obj_from_primitive") @mock.patch.object(objects.ScaleVnfRequest, "obj_from_primitive")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(objects.VnfInstance, "get_by_id") @mock.patch.object(objects.VnfInstance, "get_by_id")
@mock.patch.object(tacker.db.vnfm.vnfm_db.VNFMPluginDb, "get_vnf") @mock.patch.object(tacker.db.vnfm.vnfm_db.VNFMPluginDb, "get_vnf")
@mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "scale") @mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "scale")
@@ -2379,13 +2384,18 @@ class TestController(base.TestCase):
mock_scale, mock_scale,
mock_get_vnf, mock_get_vnf,
mock_vnf_instance_get_by_id, mock_vnf_instance_get_by_id,
mock_vnf_instance_save,
mock_obj_from_primitive, mock_obj_from_primitive,
mock_create, mock_create,
mock_get_service_plugins): mock_get_service_plugins):
mock_get_vnf.return_value = fakes._get_vnf() mock_get_vnf.return_value = fakes._get_vnf()
vim_connection_info = objects.VimConnectionInfo(
vim_type="openstack")
update = {'vim_connection_info': [vim_connection_info]}
mock_vnf_instance_get_by_id.return_value = fakes.return_vnf_instance( mock_vnf_instance_get_by_id.return_value = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED, scale_status="scale_status") fields.VnfInstanceState.INSTANTIATED, scale_status="scale_status",
**update)
mock_obj_from_primitive.return_value = fakes.scale_request_make( mock_obj_from_primitive.return_value = fakes.scale_request_make(
"SCALE_IN", 1) "SCALE_IN", 1)
mock_create.return_value = 200 mock_create.return_value = 200
@@ -2410,6 +2420,7 @@ class TestController(base.TestCase):
return_value={'VNFM': FakeVNFMPlugin()}) return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.VnfLcmOpOcc, "create") @mock.patch.object(objects.VnfLcmOpOcc, "create")
@mock.patch.object(objects.ScaleVnfRequest, "obj_from_primitive") @mock.patch.object(objects.ScaleVnfRequest, "obj_from_primitive")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(objects.VnfInstance, "get_by_id") @mock.patch.object(objects.VnfInstance, "get_by_id")
@mock.patch.object(tacker.db.vnfm.vnfm_db.VNFMPluginDb, "get_vnf") @mock.patch.object(tacker.db.vnfm.vnfm_db.VNFMPluginDb, "get_vnf")
@mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "scale") @mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "scale")
@@ -2420,13 +2431,18 @@ class TestController(base.TestCase):
mock_scale, mock_scale,
mock_get_vnf, mock_get_vnf,
mock_vnf_instance_get_by_id, mock_vnf_instance_get_by_id,
mock_vnf_instance_save,
mock_obj_from_primitive, mock_obj_from_primitive,
mock_create, mock_create,
mock_get_service_plugins): mock_get_service_plugins):
mock_get_vnf.return_value = fakes._get_vnf() mock_get_vnf.return_value = fakes._get_vnf()
vim_connection_info = objects.VimConnectionInfo(
vim_type="openstack")
update = {'vim_connection_info': [vim_connection_info]}
mock_vnf_instance_get_by_id.return_value = fakes.return_vnf_instance( mock_vnf_instance_get_by_id.return_value = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED, scale_status="scale_status") fields.VnfInstanceState.INSTANTIATED, scale_status="scale_status",
**update)
mock_obj_from_primitive.return_value = fakes.scale_request_make( mock_obj_from_primitive.return_value = fakes.scale_request_make(
"SCALE_OUT", 1) "SCALE_OUT", 1)
mock_create.return_value = 200 mock_create.return_value = 200
@@ -2451,6 +2467,7 @@ class TestController(base.TestCase):
return_value={'VNFM': FakeVNFMPlugin()}) return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.VnfLcmOpOcc, "create") @mock.patch.object(objects.VnfLcmOpOcc, "create")
@mock.patch.object(objects.ScaleVnfRequest, "obj_from_primitive") @mock.patch.object(objects.ScaleVnfRequest, "obj_from_primitive")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(objects.VnfInstance, "get_by_id") @mock.patch.object(objects.VnfInstance, "get_by_id")
@mock.patch.object(tacker.db.vnfm.vnfm_db.VNFMPluginDb, "get_vnf") @mock.patch.object(tacker.db.vnfm.vnfm_db.VNFMPluginDb, "get_vnf")
@mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "scale") @mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "scale")
@@ -2459,13 +2476,19 @@ class TestController(base.TestCase):
mock_scale, mock_scale,
mock_get_vnf, mock_get_vnf,
mock_vnf_instance_get_by_id, mock_vnf_instance_get_by_id,
mock_vnf_instance_save,
mock_obj_from_primitive, mock_obj_from_primitive,
mock_create, mock_create,
mock_get_service_plugins): mock_get_service_plugins):
mock_get_vnf.return_value = fakes._get_vnf() mock_get_vnf.return_value = fakes._get_vnf()
vim_connection_info = objects.VimConnectionInfo(
vim_type="openstack")
update = {'vim_connection_info': [vim_connection_info]}
mock_vnf_instance_get_by_id.return_value = fakes.return_vnf_instance( mock_vnf_instance_get_by_id.return_value = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED, scale_status="scale_status") fields.VnfInstanceState.INSTANTIATED, scale_status="scale_status",
**update)
mock_obj_from_primitive.return_value = fakes.scale_request_make( mock_obj_from_primitive.return_value = fakes.scale_request_make(
"SCALE_IN", 4) "SCALE_IN", 4)
mock_create.return_value = 200 mock_create.return_value = 200
@@ -2505,8 +2528,12 @@ class TestController(base.TestCase):
mock_get_service_plugins): mock_get_service_plugins):
mock_get_vnf.return_value = fakes._get_vnf() mock_get_vnf.return_value = fakes._get_vnf()
vim_connection_info = objects.VimConnectionInfo(
vim_type="openstack")
update = {'vim_connection_info': [vim_connection_info]}
mock_vnf_instance_get_by_id.return_value = fakes.return_vnf_instance( mock_vnf_instance_get_by_id.return_value = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED, scale_status="scale_status") fields.VnfInstanceState.INSTANTIATED, scale_status="scale_status",
**update)
mock_obj_from_primitive.return_value = fakes.scale_request_make( mock_obj_from_primitive.return_value = fakes.scale_request_make(
"SCALE_OUT", 4) "SCALE_OUT", 4)
mock_create.return_value = 200 mock_create.return_value = 200
@@ -2533,6 +2560,7 @@ class TestController(base.TestCase):
return_value={'VNFM': FakeVNFMPlugin()}) return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.ScaleVnfRequest, "obj_from_primitive") @mock.patch.object(objects.ScaleVnfRequest, "obj_from_primitive")
@mock.patch.object(controller.VnfLcmController, "_get_rollback_vnf") @mock.patch.object(controller.VnfLcmController, "_get_rollback_vnf")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(objects.VnfInstance, "get_by_id") @mock.patch.object(objects.VnfInstance, "get_by_id")
@mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "send_notification") @mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "send_notification")
@mock.patch.object(objects.VnfLcmOpOcc, "create") @mock.patch.object(objects.VnfLcmOpOcc, "create")
@@ -2541,6 +2569,7 @@ class TestController(base.TestCase):
mock_create, mock_create,
mock_send_notification, mock_send_notification,
mock_vnf_instance, mock_vnf_instance,
mock_vnf_instance_save,
mock_get_vnf, mock_get_vnf,
mock_obj_from_primitive, mock_obj_from_primitive,
get_service_plugins): get_service_plugins):
@@ -2556,9 +2585,13 @@ class TestController(base.TestCase):
"SCALE_IN", 1) "SCALE_IN", 1)
mock_get_vnf.return_value = vnf_obj mock_get_vnf.return_value = vnf_obj
vim_connection_info = objects.VimConnectionInfo(
vim_type="openstack")
update = {'vim_connection_info': [vim_connection_info]}
vnf_instance = fakes.return_vnf_instance( vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED, fields.VnfInstanceState.INSTANTIATED,
scale_status="scale_status") scale_status="scale_status",
**update)
vnf_instance.instantiated_vnf_info.instance_id =\ vnf_instance.instantiated_vnf_info.instance_id =\
uuidsentinel.instance_id uuidsentinel.instance_id
@@ -2571,9 +2604,10 @@ class TestController(base.TestCase):
vnf_info = fakes._get_vnf() vnf_info = fakes._get_vnf()
vnf_instance = fakes.return_vnf_instance( vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED, scale_status="scale_status") fields.VnfInstanceState.INSTANTIATED, scale_status="scale_status",
**update)
self.controller._scale(self.context, self.controller._scale(self.context,
vnf_info, vnf_instance, body) vnf_instance, vnf_info, body)
mock_send_notification.assert_called_once() mock_send_notification.assert_called_once()
self.assertEqual(mock_send_notification.call_args[0][1].get( self.assertEqual(mock_send_notification.call_args[0][1].get(
@@ -2592,6 +2626,228 @@ class TestController(base.TestCase):
self.assertEqual(mock_send_notification.call_args[0][1].get( self.assertEqual(mock_send_notification.call_args[0][1].get(
'isAutomaticInvocation'), 'False') 'isAutomaticInvocation'), 'False')
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.VnfPackageVnfd, "get_by_id")
@mock.patch.object(objects.VnfLcmOpOcc, "create")
@mock.patch.object(objects.ScaleVnfRequest, "obj_from_primitive")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(objects.VnfInstance, "get_by_id")
@mock.patch.object(tacker.db.vnfm.vnfm_db.VNFMPluginDb, "get_vnf")
@mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "scale")
@mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "send_notification")
def test_scale_in_cnf(
self,
mock_send_notification,
mock_scale,
mock_get_vnf,
mock_vnf_instance_get_by_id,
mock_vnf_instance_save,
mock_obj_from_primitive,
mock_create,
mock_vnf_package_vnfd_get_by_id,
mock_get_service_plugins):
mock_get_vnf.return_value = fakes.vnf_dict_cnf()
vim_connection_info = objects.VimConnectionInfo(
vim_type="kubernetes")
update = {'vim_connection_info': [vim_connection_info]}
scale_status = objects.ScaleInfo(
aspect_id='vdu1_aspect', scale_level=1)
mock_vnf_instance_get_by_id.return_value = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED, scale_status=scale_status,
**update)
mock_vnf_package_vnfd_get_by_id.return_value = \
fakes.return_vnf_package_vnfd()
scale_request = fakes.scale_request_make("SCALE_IN", 1)
scale_request.aspect_id = "vdu1_aspect"
mock_obj_from_primitive.return_value = scale_request
mock_create.return_value = 200
body = {
"type": "SCALE_IN",
"aspectId": "vdu1_aspect",
"numberOfSteps": 1,
"additionalParams": {}}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/scale' %
FakeVNFMPlugin().vnf_for_cnf_vnf_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
resp = req.get_response(self.app)
self.assertEqual(http_client.ACCEPTED, resp.status_code)
mock_scale.assert_called_once()
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch('tacker.vnflcm.utils._get_vnfd_dict')
@mock.patch.object(objects.VnfPackageVnfd, "get_by_id")
@mock.patch.object(objects.VnfLcmOpOcc, "create")
@mock.patch.object(objects.ScaleVnfRequest, "obj_from_primitive")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(objects.VnfInstance, "get_by_id")
@mock.patch.object(tacker.db.vnfm.vnfm_db.VNFMPluginDb, "get_vnf")
@mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "scale")
@mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "send_notification")
def test_scale_out_cnf(
self,
mock_send_notification,
mock_scale,
mock_get_vnf,
mock_vnf_instance_get_by_id,
mock_vnf_instance_save,
mock_obj_from_primitive,
mock_create,
mock_vnf_package_vnfd_get_by_id,
mock_vnfd_dict,
mock_get_service_plugins):
vnf_info = fakes.vnf_dict_cnf()
mock_get_vnf.return_value = vnf_info
vim_connection_info = objects.VimConnectionInfo(
vim_type="kubernetes")
update = {'vim_connection_info': [vim_connection_info]}
scale_status = objects.ScaleInfo(
aspect_id='vdu1_aspect', scale_level=1)
mock_vnf_instance_get_by_id.return_value = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED, scale_status=scale_status,
**update)
mock_vnf_package_vnfd_get_by_id.return_value = \
fakes.return_vnf_package_vnfd()
mock_vnfd_dict.return_value = fakes.vnfd_dict_cnf()
scale_request = fakes.scale_request_make("SCALE_OUT", 1)
scale_request.aspect_id = "vdu1_aspect"
mock_obj_from_primitive.return_value = scale_request
mock_create.return_value = 200
body = {
"type": "SCALE_OUT",
"aspectId": "vdu1_aspect",
"numberOfSteps": 1,
"additionalParams": {}}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/scale' %
FakeVNFMPlugin().vnf_for_cnf_vnf_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
resp = req.get_response(self.app)
self.assertEqual(http_client.ACCEPTED, resp.status_code)
mock_scale.assert_called_once()
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.VnfLcmOpOcc, "create")
@mock.patch.object(objects.ScaleVnfRequest, "obj_from_primitive")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(objects.VnfInstance, "get_by_id")
@mock.patch.object(tacker.db.vnfm.vnfm_db.VNFMPluginDb, "get_vnf")
@mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "scale")
@mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "send_notification")
def test_scale_in_cnf_error_is_reverse(
self,
mock_send_notification,
mock_scale,
mock_get_vnf,
mock_vnf_instance_get_by_id,
mock_vnf_instance_save,
mock_obj_from_primitive,
mock_create,
mock_get_service_plugins):
mock_get_vnf.return_value = fakes.vnf_dict_cnf()
vim_connection_info = objects.VimConnectionInfo(
vim_type="kubernetes")
update = {'vim_connection_info': [vim_connection_info]}
scale_status = objects.ScaleInfo(
aspect_id='vdu1_aspect', scale_level=1)
mock_vnf_instance_get_by_id.return_value = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED, scale_status=scale_status,
**update)
scale_request = fakes.scale_request_make("SCALE_IN", 1)
scale_request.aspect_id = "vdu1_aspect"
scale_request.additional_params = {"is_reverse": "True"}
mock_obj_from_primitive.return_value = scale_request
mock_create.return_value = 200
body = {
"type": "SCALE_IN",
"aspectId": "vdu1_aspect",
"numberOfSteps": 1,
"additionalParams": {
"is_reverse": "True"}}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/scale' %
FakeVNFMPlugin().vnf_for_cnf_vnf_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
resp = req.get_response(self.app)
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
mock_scale.assert_not_called()
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch('tacker.vnflcm.utils._get_vnfd_dict')
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfPackage, "get_by_id")
@mock.patch.object(objects.VnfLcmOpOcc, "create")
@mock.patch.object(objects.ScaleVnfRequest, "obj_from_primitive")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(objects.VnfInstance, "get_by_id")
@mock.patch.object(tacker.db.vnfm.vnfm_db.VNFMPluginDb, "get_vnf")
@mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "scale")
@mock.patch.object(vnf_lcm_rpc.VNFLcmRPCAPI, "send_notification")
def test_scale_out_cnf_err_over_max_scale_level(
self,
mock_send_notification,
mock_scale,
mock_get_vnf,
mock_vnf_instance_get_by_id,
mock_vnf_instance_save,
mock_obj_from_primitive,
mock_create,
mock_vnf_package_get_by_id,
mock_vnf_package_vnfd_get_by_id,
mock_vnfd_dict,
mock_get_service_plugins):
vnf_info = fakes.vnf_dict_cnf()
mock_get_vnf.return_value = vnf_info
vim_connection_info = objects.VimConnectionInfo(
vim_type="kubernetes")
update = {'vim_connection_info': [vim_connection_info]}
scale_status = objects.ScaleInfo(
aspect_id='vdu1_aspect', scale_level=1)
mock_vnf_instance_get_by_id.return_value = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED, scale_status=scale_status,
**update)
mock_vnf_package_vnfd_get_by_id.return_value = \
fakes.return_vnf_package_vnfd()
mock_vnf_package_get_by_id.return_value = \
fakes.return_vnf_package_with_deployment_flavour()
mock_vnfd_dict.return_value = fakes.vnfd_dict_cnf()
scale_request = fakes.scale_request_make("SCALE_OUT", 3)
scale_request.aspect_id = "vdu1_aspect"
mock_obj_from_primitive.return_value = scale_request
mock_create.return_value = 200
body = {
"type": "SCALE_OUT",
"aspectId": "vdu1_aspect",
"numberOfSteps": 3,
"additionalParams": {}}
req = fake_request.HTTPRequest.blank(
'/vnf_instances/%s/scale' %
FakeVNFMPlugin().vnf_for_cnf_vnf_id)
req.body = jsonutils.dump_as_bytes(body)
req.headers['Content-Type'] = 'application/json'
req.method = 'POST'
resp = req.get_response(self.app)
self.assertEqual(http_client.BAD_REQUEST, resp.status_code)
mock_scale.assert_not_called()
@mock.patch.object(TackerManager, 'get_service_plugins', @mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()}) return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id")

View File

@@ -411,7 +411,10 @@ class TestVnflcmDriver(db_base.SqlTestCase):
test_utils.copy_csar_files(fake_csar, "vnflcm4") test_utils.copy_csar_files(fake_csar, "vnflcm4")
self._mock_vnf_manager(fail_method_name='create_wait') self._mock_vnf_manager(fail_method_name='create_wait')
driver = vnflcm_driver.VnfLcmDriver() driver = vnflcm_driver.VnfLcmDriver()
vnf_dict = {"vnfd": {"attributes": {}}, "attributes": {}} scale_status = objects.ScaleInfo(aspect_id='SP1', scale_level=0)
vnf_dict = {"vnfd": {"attributes": {}},
"attributes": {"scaling_group_names": {"SP1": "G1"}},
"scale_status": [scale_status]}
error = self.assertRaises(exceptions.VnfInstantiationWaitFailed, error = self.assertRaises(exceptions.VnfInstantiationWaitFailed,
driver.instantiate_vnf, self.context, vnf_instance_obj, vnf_dict, driver.instantiate_vnf, self.context, vnf_instance_obj, vnf_dict,
instantiate_vnf_req_obj) instantiate_vnf_req_obj)
@@ -750,7 +753,9 @@ class TestVnflcmDriver(db_base.SqlTestCase):
uuidsentinel.instance_id uuidsentinel.instance_id
self._mock_vnf_manager() self._mock_vnf_manager()
driver = vnflcm_driver.VnfLcmDriver() driver = vnflcm_driver.VnfLcmDriver()
vnf_dict = {"attributes": {}} scale_status = objects.ScaleInfo(aspect_id='SP1', scale_level=0)
vnf_dict = {"attributes": {"scaling_group_names": {"SP1": "G1"}},
"scale_status": [scale_status]}
mock_make_final_vnf_dict.return_value = {} mock_make_final_vnf_dict.return_value = {}
driver.heal_vnf(self.context, vnf_instance, vnf_dict, heal_vnf_req) driver.heal_vnf(self.context, vnf_instance, vnf_dict, heal_vnf_req)
self.assertEqual(1, mock_save.call_count) self.assertEqual(1, mock_save.call_count)
@@ -1062,9 +1067,9 @@ class TestVnflcmDriver(db_base.SqlTestCase):
'{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \ '{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \
'1, \"maxLevel\": 3, \"initialNum\": 0, ' + \ '1, \"maxLevel\": 3, \"initialNum\": 0, ' + \
'\"initialLevel\": 0, \"default\": 0 }}}' '\"initialLevel\": 0, \"default\": 0 }}}'
scale_vnf_request = fakes.scale_request("SCALE_IN", 1, "True") scale_vnf_request = fakes.scale_request("SCALE_IN", "SP1", 1, "True")
vim_connection_info = vim_connection.VimConnectionInfo( vim_connection_info = vim_connection.VimConnectionInfo(
vim_type="fake_type") vim_type="openstack")
scale_name_list = ["fake"] scale_name_list = ["fake"]
grp_id = "fake_id" grp_id = "fake_id"
driver = vnflcm_driver.VnfLcmDriver() driver = vnflcm_driver.VnfLcmDriver()
@@ -1089,9 +1094,9 @@ class TestVnflcmDriver(db_base.SqlTestCase):
'{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \ '{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \
'1, \"maxLevel\": 3, \"initialNum\": 0, ' + \ '1, \"maxLevel\": 3, \"initialNum\": 0, ' + \
'\"initialLevel\": 0, \"default\": 0 }}}' '\"initialLevel\": 0, \"default\": 0 }}}'
scale_vnf_request = fakes.scale_request("SCALE_IN", 1, "False") scale_vnf_request = fakes.scale_request("SCALE_IN", "SP1", 1, "False")
vim_connection_info = vim_connection.VimConnectionInfo( vim_connection_info = vim_connection.VimConnectionInfo(
vim_type="fake_type") vim_type="openstack")
scale_name_list = ["fake"] scale_name_list = ["fake"]
grp_id = "fake_id" grp_id = "fake_id"
with open(vnf_info["attributes"]["heat_template"], "r") as f: with open(vnf_info["attributes"]["heat_template"], "r") as f:
@@ -1119,9 +1124,9 @@ class TestVnflcmDriver(db_base.SqlTestCase):
'{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \ '{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \
'1, \"maxLevel\": 3, \"initialNum\": 0, ' + \ '1, \"maxLevel\": 3, \"initialNum\": 0, ' + \
'\"initialLevel\": 0, \"default\": 0 }}}' '\"initialLevel\": 0, \"default\": 0 }}}'
scale_vnf_request = fakes.scale_request("SCALE_OUT", 1, "False") scale_vnf_request = fakes.scale_request("SCALE_OUT", "SP1", 1, "False")
vim_connection_info = vim_connection.VimConnectionInfo( vim_connection_info = vim_connection.VimConnectionInfo(
vim_type="fake_type") vim_type="openstack")
scale_name_list = ["fake"] scale_name_list = ["fake"]
grp_id = "fake_id" grp_id = "fake_id"
with open(vnf_info["attributes"]["heat_template"], "r") as f: with open(vnf_info["attributes"]["heat_template"], "r") as f:
@@ -1149,9 +1154,9 @@ class TestVnflcmDriver(db_base.SqlTestCase):
'{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \ '{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \
'1, \"maxLevel\": 3, \"initialNum\": 0, ' + \ '1, \"maxLevel\": 3, \"initialNum\": 0, ' + \
'\"initialLevel\": 0, \"default\": 1 }}}' '\"initialLevel\": 0, \"default\": 1 }}}'
scale_vnf_request = fakes.scale_request("SCALE_OUT", 1, "False") scale_vnf_request = fakes.scale_request("SCALE_OUT", "SP1", 1, "False")
vim_connection_info = vim_connection.VimConnectionInfo( vim_connection_info = vim_connection.VimConnectionInfo(
vim_type="fake_type") vim_type="openstack")
scale_name_list = ["fake"] scale_name_list = ["fake"]
grp_id = "fake_id" grp_id = "fake_id"
with open(vnf_info["attributes"]["heat_template"], "r") as f: with open(vnf_info["attributes"]["heat_template"], "r") as f:
@@ -1161,6 +1166,108 @@ class TestVnflcmDriver(db_base.SqlTestCase):
driver.scale(self.context, vnf_info, scale_vnf_request, driver.scale(self.context, vnf_info, scale_vnf_request,
vim_connection_info, scale_name_list, grp_id) vim_connection_info, scale_name_list, grp_id)
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(VnfLcmDriver,
'_init_mgmt_driver_hash')
@mock.patch.object(yaml, "safe_load")
@mock.patch('tacker.vnflcm.utils._get_vnfd_dict')
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(vim_client.VimClient, "get_vim")
@mock.patch.object(objects.VnfLcmOpOcc, "save")
@mock.patch.object(objects.VnfInstance, "get_by_id")
@mock.patch.object(driver_manager.DriverManager, "invoke")
def test_scale_in_cnf(self, mock_invoke, mock_vnf_instance_get_by_id,
mock_lcm_save, mock_vim, mock_vnf_package_vnfd,
mock_vnfd_dict, mock_yaml_safe_load, mock_init_hash,
mock_get_service_plugins):
mock_init_hash.return_value = {
"vnflcm_noop": "ffea638bfdbde3fb01f191bbe75b031859"
"b18d663b127100eb72b19eecd7ed51"
}
vnf_info = fakes.vnf_dict_cnf()
vnf_info['vnf_lcm_op_occ'] = fakes.vnflcm_scale_in_cnf()
vnf_info['scale_level'] = 1
vnf_info['after_scale_level'] = 0
vnf_info['notification'] = {}
scale_vnf_request = fakes.scale_request(
"SCALE_IN", "vdu1_aspect", 1, "False")
vim_connection_info = vim_connection.VimConnectionInfo(
vim_type="kubernetes")
update = {'vim_connection_info': [vim_connection_info]}
scale_status = objects.ScaleInfo(
aspect_id='vdu1_aspect', scale_level=1)
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED, scale_status=scale_status,
**update)
mock_vnfd_dict.return_value = fakes.vnfd_dict_cnf()
mock_yaml_safe_load.return_value = fakes.vnfd_dict_cnf()
mock_invoke.side_effect = [
# Kubernetes.get_scale_in_ids called in _scale_vnf_pre()
[[], [], None, None],
# Kubernetes.scale called in scale()
None,
# Kubernetes.scale_wait called in scale()
None,
# scale_resource_update called in _scale_resource_update()
None]
mock_vnf_package_vnfd.return_value = fakes.return_vnf_package_vnfd()
driver = vnflcm_driver.VnfLcmDriver()
vim_obj = {'vim_id': uuidsentinel.vim_id,
'vim_name': 'fake_vim',
'vim_type': 'kubernetes',
'vim_auth': {
'auth_url': 'http://localhost:8443',
'password': 'test_pw',
'username': 'test_user',
'project_name': 'test_project'}}
self.vim_client.get_vim.return_value = vim_obj
driver.scale_vnf(self.context, vnf_info, vnf_instance,
scale_vnf_request)
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(yaml, "safe_load")
@mock.patch('tacker.vnflcm.utils._get_vnfd_dict')
@mock.patch.object(objects.VnfPackageVnfd, 'get_by_id')
@mock.patch.object(objects.VnfLcmOpOcc, "save")
@mock.patch.object(objects.VnfInstance, "get_by_id")
@mock.patch.object(driver_manager.DriverManager, "invoke")
def test_scale_out_cnf(self, mock_invoke, mock_vnf_instance_get_by_id,
mock_lcm_save, mock_vnf_package_vnfd, mock_vnfd_dict,
mock_yaml_safe_load, mock_get_service_plugins):
vnf_info = fakes.vnf_dict_cnf()
vnf_info['vnf_lcm_op_occ'] = fakes.vnflcm_scale_out_cnf()
vnf_info['scale_level'] = 0
vnf_info['after_scale_level'] = 1
vnf_info['notification'] = {}
scale_vnf_request = fakes.scale_request(
"SCALE_OUT", "vdu1_aspect", 1, "False")
vim_connection_info = vim_connection.VimConnectionInfo(
vim_type="kubernetes")
update = {'vim_connection_info': [vim_connection_info]}
scale_status = objects.ScaleInfo(
aspect_id='vdu1_aspect', scale_level=1)
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED, scale_status=scale_status,
**update)
mock_vnf_instance_get_by_id.return_value = vnf_instance
mock_vnf_package_vnfd.return_value = fakes.return_vnf_package_vnfd()
mock_vnfd_dict.return_value = fakes.vnfd_dict_cnf()
mock_yaml_safe_load.return_value = fakes.vnfd_dict_cnf()
driver = vnflcm_driver.VnfLcmDriver()
vim_obj = {'vim_id': uuidsentinel.vim_id,
'vim_name': 'fake_vim',
'vim_type': 'kubernetes',
'vim_auth': {
'auth_url': 'http://localhost:8443',
'password': 'test_pw',
'username': 'test_user',
'project_name': 'test_project'}}
self.vim_client.get_vim.return_value = vim_obj
driver.scale_vnf(self.context, vnf_info, vnf_instance,
scale_vnf_request)
@mock.patch.object(TackerManager, 'get_service_plugins', @mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()}) return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(VnfLcmDriver, @mock.patch.object(VnfLcmDriver,
@@ -2068,3 +2175,69 @@ class TestVnflcmDriver(db_base.SqlTestCase):
self.assertEqual(1, mock_scale.call_count) self.assertEqual(1, mock_scale.call_count)
self.assertEqual(1, mock_wait.call_count) self.assertEqual(1, mock_wait.call_count)
self.assertEqual(2, mock_scale_resource.call_count) self.assertEqual(2, mock_scale_resource.call_count)
@mock.patch.object(TackerManager, 'get_service_plugins',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(VnfLcmDriver,
'_init_mgmt_driver_hash')
@mock.patch('tacker.vnflcm.utils._get_vnfd_dict')
@mock.patch.object(yaml, "safe_load")
@mock.patch.object(objects.VnfLcmOpOcc, "save")
@mock.patch.object(VNFLcmRPCAPI, "send_notification")
@mock.patch.object(objects.VnfInstance, "save")
@mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback_pre")
@mock.patch.object(vnflcm_driver.VnfLcmDriver, "_update_vnf_rollback")
def test_rollback_vnf_scale_cnf(
self,
mock_update,
mock_up,
mock_insta_save,
mock_notification,
mock_lcm_save,
mock_yaml_safe_load,
mock_vnfd_dict,
mock_init_hash,
mock_get_service_plugins):
mock_init_hash.return_value = {
"vnflcm_noop": "ffea638bfdbde3fb01f191bbe75b031859"
"b18d663b127100eb72b19eecd7ed51"
}
vim_connection_info = vim_connection.VimConnectionInfo(
vim_type="kubernetes")
update = {'vim_connection_info': [vim_connection_info]}
vnf_instance = fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED, **update)
vnf_instance.instantiated_vnf_info.instance_id =\
uuidsentinel.instance_id
vnf_instance.instantiated_vnf_info.scale_status = []
vnf_instance.instantiated_vnf_info.scale_status.append(
objects.ScaleInfo(aspect_id='vdu1_aspect', scale_level=0))
vnf_lcm_op_occs = fakes.vnflcm_rollback()
vnf_lcm_op_occs.operation_params = \
'{"type": "SCALE_OUT", "aspect_id": "vdu1_aspect"}'
vnf_info = fakes.vnf_dict_cnf()
vnf_info['vnf_lcm_op_occ'] = vnf_lcm_op_occs
vnf_info['scale_level'] = 1
mock_vnfd_dict.return_value = fakes.vnfd_dict_cnf()
operation_params = jsonutils.loads(vnf_lcm_op_occs.operation_params)
mock_yaml_safe_load.return_value = fakes.vnfd_dict_cnf()
vim_obj = {'vim_id': uuidsentinel.vim_id,
'vim_name': 'fake_vim',
'vim_type': 'kubernetes',
'vim_auth': {
'auth_url': 'http://localhost:8443',
'password': 'test_pw',
'username': 'test_user',
'project_name': 'test_project'}}
self.vim_client.get_vim.return_value = vim_obj
self._mock_vnf_manager()
driver = vnflcm_driver.VnfLcmDriver()
driver.rollback_vnf(
self.context,
vnf_info,
vnf_instance,
operation_params)
self.assertEqual(1, mock_lcm_save.call_count)

View File

@@ -15,6 +15,8 @@
from kubernetes import client from kubernetes import client
from tacker.db.db_sqlalchemy import models
from tacker.tests import uuidsentinel
CREATE_K8S_FALSE_VALUE = None CREATE_K8S_FALSE_VALUE = None
@@ -982,3 +984,44 @@ def fake_pod_list():
) )
)] )]
) )
def get_scale_policy(type, aspect_id='vdu1', delta_num=1):
policy = dict()
policy['vnf_instance_id'] = uuidsentinel.vnf_instance_id
policy['action'] = type
policy['name'] = aspect_id
policy['delta_num'] = delta_num
policy['vdu_defs'] = {
'VDU1': {
'type': 'tosca.nodes.nfv.Vdu.Compute',
'properties': {
'name': 'fake_name',
'description': 'test description',
'vdu_profile': {
'min_number_of_instances': 1,
'max_number_of_instances': 3}}}}
return policy
def get_vnf_resource_list(kind, name='fake_name'):
vnf_resource = models.VnfResource()
vnf_resource.vnf_instance_id = uuidsentinel.vnf_instance_id
vnf_resource.resource_name = \
_("fake_namespace,{name}").format(name=name)
vnf_resource.resource_type = \
_("v1,{kind}").format(kind=kind)
return [vnf_resource]
def get_fake_pod_info(kind, name='fake_name', pod_status='Running'):
if kind == 'Deployment':
pod_name = _('{name}-1234567890-abcde').format(name=name)
elif kind == 'ReplicaSet':
pod_name = _('{name}-12345').format(name=name)
elif kind == 'StatefulSet':
pod_name = _('{name}-1').format(name=name)
return client.V1Pod(
metadata=client.V1ObjectMeta(name=pod_name),
status=client.V1PodStatus(phase=pod_status))

View File

@@ -30,6 +30,7 @@ from tacker.objects import vnf_package_vnfd
from tacker.objects import vnf_resources as vnf_resource_obj from tacker.objects import vnf_resources as vnf_resource_obj
from tacker.tests.unit import base from tacker.tests.unit import base
from tacker.tests.unit.db import utils from tacker.tests.unit.db import utils
from tacker.tests.unit.vnflcm import fakes as vnflcm_fakes
from tacker.tests.unit.vnfm.infra_drivers.kubernetes import fakes from tacker.tests.unit.vnfm.infra_drivers.kubernetes import fakes
from tacker.tests.unit.vnfm.infra_drivers.openstack.fixture_data import \ from tacker.tests.unit.vnfm.infra_drivers.openstack.fixture_data import \
fixture_data_utils as fd_utils fixture_data_utils as fd_utils
@@ -1799,3 +1800,490 @@ class TestKubernetes(base.TestCase):
"{'namespace': 'test', 'name': " + "{'namespace': 'test', 'name': " +
"'curry-test001', 'apiVersion': 'apps/v1', " + "'curry-test001', 'apiVersion': 'apps/v1', " +
"'kind': 'Deployment', 'status': 'Creating'}") "'kind': 'Deployment', 'status': 'Creating'}")
@mock.patch.object(client.AppsV1Api, 'patch_namespaced_deployment_scale')
@mock.patch.object(client.AppsV1Api, 'read_namespaced_deployment_scale')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_in_deployment(self, mock_vnf_resource_list,
mock_read_namespaced_deployment_scale,
mock_patch_namespaced_deployment_scale):
policy = fakes.get_scale_policy(type='in')
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='Deployment')
mock_read_namespaced_deployment_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=2),
status=client.V1ScaleStatus(replicas=2))
mock_patch_namespaced_deployment_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=1),
status=client.V1ScaleStatus(replicas=1))
self.kubernetes.scale(context=self.context, plugin=None,
auth_attr=utils.get_vim_auth_obj(),
policy=policy,
region_name=None)
mock_read_namespaced_deployment_scale.assert_called_once()
mock_patch_namespaced_deployment_scale.assert_called_once()
@mock.patch.object(client.AppsV1Api, 'patch_namespaced_stateful_set_scale')
@mock.patch.object(client.AppsV1Api, 'read_namespaced_stateful_set_scale')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_in_stateful_set(self, mock_vnf_resource_list,
mock_read_namespaced_stateful_set_scale,
mock_patch_namespaced_stateful_set_scale):
policy = fakes.get_scale_policy(type='in')
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='StatefulSet')
mock_read_namespaced_stateful_set_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=2),
status=client.V1ScaleStatus(replicas=2))
mock_patch_namespaced_stateful_set_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=1),
status=client.V1ScaleStatus(replicas=1))
self.kubernetes.scale(context=self.context, plugin=None,
auth_attr=utils.get_vim_auth_obj(),
policy=policy,
region_name=None)
mock_read_namespaced_stateful_set_scale.assert_called_once()
mock_patch_namespaced_stateful_set_scale.assert_called_once()
@mock.patch.object(client.AppsV1Api, 'patch_namespaced_replica_set_scale')
@mock.patch.object(client.AppsV1Api, 'read_namespaced_replica_set_scale')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_in_replica_set(self, mock_vnf_resource_list,
mock_read_namespaced_replica_set_scale,
mock_patch_namespaced_replica_set_scale):
policy = fakes.get_scale_policy(type='in')
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='ReplicaSet')
mock_read_namespaced_replica_set_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=2),
status=client.V1ScaleStatus(replicas=2))
mock_patch_namespaced_replica_set_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=1),
status=client.V1ScaleStatus(replicas=1))
self.kubernetes.scale(context=self.context, plugin=None,
auth_attr=utils.get_vim_auth_obj(),
policy=policy,
region_name=None)
mock_read_namespaced_replica_set_scale.assert_called_once()
mock_patch_namespaced_replica_set_scale.assert_called_once()
@mock.patch.object(client.AppsV1Api, 'patch_namespaced_deployment_scale')
@mock.patch.object(client.AppsV1Api, 'read_namespaced_deployment_scale')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_out(self, mock_vnf_resource_list,
mock_read_namespaced_deployment_scale,
mock_patch_namespaced_deployment_scale):
policy = fakes.get_scale_policy(type='out')
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='Deployment')
mock_read_namespaced_deployment_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=1),
status=client.V1ScaleStatus(replicas=1))
mock_patch_namespaced_deployment_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=2),
status=client.V1ScaleStatus(replicas=2))
self.kubernetes.scale(context=self.context, plugin=None,
auth_attr=utils.get_vim_auth_obj(),
policy=policy,
region_name=None)
mock_read_namespaced_deployment_scale.assert_called_once()
mock_patch_namespaced_deployment_scale.assert_called_once()
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_target_not_found(self, mock_vnf_resource_list):
policy = fakes.get_scale_policy(type='in')
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='Depoyment', name='other_name')
self.assertRaises(vnfm.CNFScaleFailed,
self.kubernetes.scale,
self.context, None,
utils.get_vim_auth_obj(), policy, None)
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_out_of_target_kind(self, mock_vnf_resource_list):
policy = fakes.get_scale_policy(type='in')
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='Pod')
self.assertRaises(vnfm.CNFScaleFailed,
self.kubernetes.scale,
self.context, None,
utils.get_vim_auth_obj(), policy, None)
@mock.patch.object(client.AppsV1Api, 'read_namespaced_deployment_scale')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_in_less_than_min_replicas(self, mock_vnf_resource_list,
mock_read_namespaced_deployment_scale):
policy = fakes.get_scale_policy(type='in')
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='Deployment')
mock_read_namespaced_deployment_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=1),
status=client.V1ScaleStatus(replicas=1))
self.assertRaises(vnfm.CNFScaleFailed,
self.kubernetes.scale,
self.context, None,
utils.get_vim_auth_obj(), policy, None)
@mock.patch.object(client.AppsV1Api, 'read_namespaced_deployment_scale')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_out_over_max_replicas(self, mock_vnf_resource_list,
mock_read_namespaced_deployment_scale):
policy = fakes.get_scale_policy(type='out')
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='Deployment')
mock_read_namespaced_deployment_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=3),
status=client.V1ScaleStatus(replicas=3))
self.assertRaises(vnfm.CNFScaleFailed,
self.kubernetes.scale,
self.context, None,
utils.get_vim_auth_obj(), policy, None)
def _test_scale_legacy(self, scale_type,
current_replicas, after_replicas,
mock_vnf_resource_list,
mock_read_namespaced_deployment,
mock_patch_namespaced_deployment_scale,
mock_read_namespaced_horizontal_pod_autoscaler):
policy = fakes.get_scale_policy(type=scale_type, aspect_id='SP1')
policy['instance_id'] = "fake_namespace,fake_name"
mock_vnf_resource_list.return_value = []
mock_read_namespaced_deployment.return_value = \
client.V1Deployment(
spec=client.V1ScaleSpec(replicas=current_replicas),
status=client.V1DeploymentStatus(replicas=current_replicas),
metadata=client.V1ObjectMeta(labels={'scaling_name': 'SP1'}))
mock_read_namespaced_horizontal_pod_autoscaler.return_value = \
client.V1HorizontalPodAutoscaler(
spec=client.V1HorizontalPodAutoscalerSpec(
min_replicas=1, max_replicas=3,
scale_target_ref=client.V1CrossVersionObjectReference(
kind='Deployment', name='fake_name')))
mock_patch_namespaced_deployment_scale.return_value = \
client.V1Scale(
spec=client.V1ScaleSpec(replicas=after_replicas),
status=client.V1ScaleStatus(replicas=after_replicas))
self.kubernetes.scale(context=self.context, plugin=None,
auth_attr=utils.get_vim_auth_obj(),
policy=policy,
region_name=None)
@mock.patch.object(client.AutoscalingV1Api,
'read_namespaced_horizontal_pod_autoscaler')
@mock.patch.object(client.AppsV1Api, 'patch_namespaced_deployment_scale')
@mock.patch.object(client.AppsV1Api, 'read_namespaced_deployment')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_legacy_in(self, mock_vnf_resource_list,
mock_read_namespaced_deployment,
mock_patch_namespaced_deployment_scale,
mock_read_namespaced_horizontal_pod_autoscaler):
self._test_scale_legacy('in', 2, 1,
mock_vnf_resource_list,
mock_read_namespaced_deployment,
mock_patch_namespaced_deployment_scale,
mock_read_namespaced_horizontal_pod_autoscaler)
mock_read_namespaced_deployment.assert_called_once()
mock_read_namespaced_horizontal_pod_autoscaler.assert_called_once()
mock_patch_namespaced_deployment_scale.assert_called_once()
@mock.patch.object(client.AutoscalingV1Api,
'read_namespaced_horizontal_pod_autoscaler')
@mock.patch.object(client.AppsV1Api, 'patch_namespaced_deployment_scale')
@mock.patch.object(client.AppsV1Api, 'read_namespaced_deployment')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_legacy_out(self, mock_vnf_resource_list,
mock_read_namespaced_deployment,
mock_patch_namespaced_deployment_scale,
mock_read_namespaced_horizontal_pod_autoscaler):
self._test_scale_legacy('out', 2, 3,
mock_vnf_resource_list,
mock_read_namespaced_deployment,
mock_patch_namespaced_deployment_scale,
mock_read_namespaced_horizontal_pod_autoscaler)
mock_read_namespaced_deployment.assert_called_once()
mock_read_namespaced_horizontal_pod_autoscaler.assert_called_once()
mock_patch_namespaced_deployment_scale.assert_called_once()
@mock.patch.object(client.AutoscalingV1Api,
'read_namespaced_horizontal_pod_autoscaler')
@mock.patch.object(client.AppsV1Api, 'patch_namespaced_deployment_scale')
@mock.patch.object(client.AppsV1Api, 'read_namespaced_deployment')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_legacy_in_less_than_min(self, mock_vnf_resource_list,
mock_read_namespaced_deployment,
mock_patch_namespaced_deployment_scale,
mock_read_namespaced_horizontal_pod_autoscaler):
self._test_scale_legacy('in', 1, 1,
mock_vnf_resource_list,
mock_read_namespaced_deployment,
mock_patch_namespaced_deployment_scale,
mock_read_namespaced_horizontal_pod_autoscaler)
mock_read_namespaced_deployment.assert_called_once()
mock_read_namespaced_horizontal_pod_autoscaler.assert_called_once()
mock_patch_namespaced_deployment_scale.assert_called_once()
@mock.patch.object(client.AutoscalingV1Api,
'read_namespaced_horizontal_pod_autoscaler')
@mock.patch.object(client.AppsV1Api, 'patch_namespaced_deployment_scale')
@mock.patch.object(client.AppsV1Api, 'read_namespaced_deployment')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_legacy_out_over_max(self, mock_vnf_resource_list,
mock_read_namespaced_deployment,
mock_patch_namespaced_deployment_scale,
mock_read_namespaced_horizontal_pod_autoscaler):
self._test_scale_legacy('out', 3, 3,
mock_vnf_resource_list,
mock_read_namespaced_deployment,
mock_patch_namespaced_deployment_scale,
mock_read_namespaced_horizontal_pod_autoscaler)
mock_read_namespaced_deployment.assert_called_once()
mock_read_namespaced_horizontal_pod_autoscaler.assert_called_once()
mock_patch_namespaced_deployment_scale.assert_called_once()
@mock.patch.object(client.AppsV1Api, 'read_namespaced_deployment_scale')
@mock.patch.object(client.CoreV1Api, 'list_namespaced_pod')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_wait_deployment(self, mock_vnf_resource_list,
mock_list_namespaced_pod,
mock_read_namespaced_deployment_scale):
policy = fakes.get_scale_policy(type='out')
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='Deployment')
mock_list_namespaced_pod.return_value = \
client.V1PodList(items=[
fakes.get_fake_pod_info(kind='Deployment')])
mock_read_namespaced_deployment_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=1),
status=client.V1ScaleStatus(replicas=1))
self.kubernetes.scale_wait(context=self.context, plugin=None,
auth_attr=utils.get_vim_auth_obj(),
policy=policy,
region_name=None,
last_event_id=None)
mock_list_namespaced_pod.assert_called_once()
@mock.patch.object(client.AppsV1Api, 'read_namespaced_stateful_set_scale')
@mock.patch.object(client.CoreV1Api, 'list_namespaced_pod')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_wait_stateful_set(self, mock_vnf_resource_list,
mock_list_namespaced_pod,
mock_read_namespaced_stateful_set_scale):
policy = fakes.get_scale_policy(type='out')
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='StatefulSet')
mock_list_namespaced_pod.return_value = \
client.V1PodList(items=[
fakes.get_fake_pod_info(kind='StatefulSet')])
mock_read_namespaced_stateful_set_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=1),
status=client.V1ScaleStatus(replicas=1))
self.kubernetes.scale_wait(context=self.context, plugin=None,
auth_attr=utils.get_vim_auth_obj(),
policy=policy,
region_name=None,
last_event_id=None)
mock_list_namespaced_pod.assert_called_once()
@mock.patch.object(client.AppsV1Api, 'read_namespaced_replica_set_scale')
@mock.patch.object(client.CoreV1Api, 'list_namespaced_pod')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_wait_replica_set(self, mock_vnf_resource_list,
mock_list_namespaced_pod,
mock_read_namespaced_replica_set_scale):
policy = fakes.get_scale_policy(type='out')
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='ReplicaSet')
mock_list_namespaced_pod.return_value = \
client.V1PodList(items=[
fakes.get_fake_pod_info(kind='ReplicaSet')])
mock_read_namespaced_replica_set_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=1),
status=client.V1ScaleStatus(replicas=1))
self.kubernetes.scale_wait(context=self.context, plugin=None,
auth_attr=utils.get_vim_auth_obj(),
policy=policy,
region_name=None,
last_event_id=None)
mock_list_namespaced_pod.assert_called_once()
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_wait_target_not_found(self, mock_vnf_resource_list):
policy = fakes.get_scale_policy(type='out')
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='Depoyment', name='other_name')
self.assertRaises(vnfm.CNFScaleWaitFailed,
self.kubernetes.scale_wait,
self.context, None,
utils.get_vim_auth_obj(), policy, None, None)
@mock.patch.object(client.AppsV1Api, 'read_namespaced_deployment_scale')
@mock.patch.object(client.CoreV1Api, 'list_namespaced_pod')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_wait_retry_over(self, mock_vnf_resource_list,
mock_list_namespaced_pod,
mock_read_namespaced_deployment_scale):
policy = fakes.get_scale_policy(type='out')
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='Deployment')
mock_list_namespaced_pod.return_value = \
client.V1PodList(items=[
fakes.get_fake_pod_info(
kind='Deployment', pod_status='Pending')])
mock_read_namespaced_deployment_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=2),
status=client.V1ScaleStatus(replicas=2))
self.assertRaises(vnfm.CNFScaleWaitFailed,
self.kubernetes.scale_wait,
self.context, None,
utils.get_vim_auth_obj(), policy, None, None)
@mock.patch.object(client.AppsV1Api, 'read_namespaced_deployment_scale')
@mock.patch.object(client.CoreV1Api, 'list_namespaced_pod')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_wait_status_unknown(self, mock_vnf_resource_list,
mock_list_namespaced_pod,
mock_read_namespaced_deployment_scale):
policy = fakes.get_scale_policy(type='out')
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='Deployment')
mock_list_namespaced_pod.return_value = \
client.V1PodList(items=[
fakes.get_fake_pod_info(
kind='Deployment', pod_status='Unknown')])
mock_read_namespaced_deployment_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=2),
status=client.V1ScaleStatus(replicas=2))
self.assertRaises(vnfm.CNFScaleWaitFailed,
self.kubernetes.scale_wait,
self.context, None,
utils.get_vim_auth_obj(), policy, None, None)
@mock.patch.object(client.CoreV1Api, 'list_namespaced_pod')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_wait_legacy(self, mock_vnf_resource_list,
mock_list_namespaced_pod):
policy = fakes.get_scale_policy(type='out', aspect_id='SP1')
policy['instance_id'] = "fake_namespace,fake_name"
mock_vnf_resource_list.return_value = []
mock_list_namespaced_pod.return_value = \
client.V1PodList(items=[
fakes.get_fake_pod_info(
kind='Deployment', pod_status='Running')])
self.kubernetes.scale_wait(context=self.context, plugin=None,
auth_attr=utils.get_vim_auth_obj(),
policy=policy,
region_name=None,
last_event_id=None)
mock_list_namespaced_pod.assert_called_once()
@mock.patch.object(client.CoreV1Api, 'list_namespaced_pod')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_wait_legacy_retry_over(self, mock_vnf_resource_list,
mock_list_namespaced_pod):
policy = fakes.get_scale_policy(type='out', aspect_id='SP1')
policy['instance_id'] = "fake_namespace,fake_name"
mock_vnf_resource_list.return_value = []
mock_list_namespaced_pod.return_value = \
client.V1PodList(items=[
fakes.get_fake_pod_info(
kind='Deployment', pod_status='Pending')])
self.assertRaises(vnfm.VNFCreateWaitFailed,
self.kubernetes.scale_wait,
self.context, None,
utils.get_vim_auth_obj(), policy, None, None)
@mock.patch.object(client.CoreV1Api, 'list_namespaced_pod')
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
def test_scale_wait_legacy_status_unknown(self, mock_vnf_resource_list,
mock_list_namespaced_pod):
policy = fakes.get_scale_policy(type='out', aspect_id='SP1')
policy['instance_id'] = "fake_namespace,fake_name"
mock_vnf_resource_list.return_value = []
mock_list_namespaced_pod.return_value = \
client.V1PodList(items=[
fakes.get_fake_pod_info(
kind='Deployment', pod_status='Unknown')])
self.assertRaises(vnfm.VNFCreateWaitFailed,
self.kubernetes.scale_wait,
self.context, None,
utils.get_vim_auth_obj(), policy, None, None)
@mock.patch.object(client.AppsV1Api, 'patch_namespaced_deployment_scale')
@mock.patch.object(client.AppsV1Api, 'read_namespaced_deployment_scale')
@mock.patch('tacker.vnflcm.utils._get_vnfd_dict')
@mock.patch.object(objects.VnfPackageVnfd, "get_by_id")
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
@mock.patch.object(objects.VnfInstance, "get_by_id")
def test_scale_in_reverse(self, mock_vnf_instance_get_by_id,
mock_vnf_resource_list,
mock_vnf_package_vnfd_get_by_id,
mock_vnfd_dict,
mock_read_namespaced_deployment_scale,
mock_patch_namespaced_deployment_scale):
vnf_info = vnflcm_fakes.vnf_dict_cnf()
vnf_info['vnf_lcm_op_occ'] = vnflcm_fakes.vnflcm_scale_out_cnf()
scale_vnf_request = vnflcm_fakes.scale_request(
"SCALE_OUT", "vdu1_aspect", 1, "False")
scale_status = objects.ScaleInfo(
aspect_id='vdu1_aspect', scale_level=1)
mock_vnf_instance_get_by_id.return_value = \
vnflcm_fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED,
scale_status=scale_status)
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='Deployment', name='vdu1')
mock_vnf_package_vnfd_get_by_id.return_value = \
vnflcm_fakes.return_vnf_package_vnfd()
mock_vnfd_dict.return_value = vnflcm_fakes.vnfd_dict_cnf()
mock_read_namespaced_deployment_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=2),
status=client.V1ScaleStatus(replicas=2))
mock_patch_namespaced_deployment_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=1),
status=client.V1ScaleStatus(replicas=1))
self.kubernetes.scale_in_reverse(context=self.context, plugin=None,
auth_attr=utils.get_vim_auth_obj(),
vnf_info=vnf_info,
scale_vnf_request=scale_vnf_request,
region_name=None,
scale_name_list=None,
grp_id=None)
mock_read_namespaced_deployment_scale.assert_called_once()
mock_patch_namespaced_deployment_scale.assert_called_once()
@mock.patch.object(client.AppsV1Api, 'read_namespaced_deployment_scale')
@mock.patch.object(client.CoreV1Api, 'list_namespaced_pod')
@mock.patch('tacker.vnflcm.utils._get_vnfd_dict')
@mock.patch.object(objects.VnfPackageVnfd, "get_by_id")
@mock.patch.object(objects.VnfResourceList, "get_by_vnf_instance_id")
@mock.patch.object(objects.VnfInstance, "get_by_id")
def test_scale_update_wait(self, mock_vnf_instance_get_by_id,
mock_vnf_resource_list,
mock_vnf_package_vnfd_get_by_id,
mock_vnfd_dict,
mock_list_namespaced_pod,
mock_read_namespaced_deployment_scale):
vnf_info = vnflcm_fakes.vnf_dict_cnf()
vnf_info['vnf_lcm_op_occ'] = vnflcm_fakes.vnflcm_scale_out_cnf()
scale_status = objects.ScaleInfo(
aspect_id='vdu1_aspect', scale_level=1)
mock_vnf_instance_get_by_id.return_value = \
vnflcm_fakes.return_vnf_instance(
fields.VnfInstanceState.INSTANTIATED,
scale_status=scale_status)
mock_vnf_resource_list.return_value = \
fakes.get_vnf_resource_list(kind='Deployment', name='vdu1')
mock_vnf_package_vnfd_get_by_id.return_value = \
vnflcm_fakes.return_vnf_package_vnfd()
mock_vnfd_dict.return_value = vnflcm_fakes.vnfd_dict_cnf()
mock_list_namespaced_pod.return_value = \
client.V1PodList(items=[
fakes.get_fake_pod_info(kind='Deployment', name='vdu1')])
mock_read_namespaced_deployment_scale.return_value = \
client.V1Scale(spec=client.V1ScaleSpec(replicas=1),
status=client.V1ScaleStatus(replicas=1))
self.kubernetes.scale_update_wait(context=self.context, plugin=None,
auth_attr=utils.get_vim_auth_obj(),
vnf_info=vnf_info,
region_name=None)
mock_list_namespaced_pod.assert_called_once()

View File

@@ -700,12 +700,15 @@ def convert_inst_req_info(heat_dict, inst_req_info, tosca):
aspect_id_dict = {} aspect_id_dict = {}
# { vduId: initialDelta } # { vduId: initialDelta }
vdu_delta_dict = {} vdu_delta_dict = {}
# { aspectId: maxScaleLevel }
aspect_max_level_dict = {}
tosca_policies = tosca.topology_template.policies tosca_policies = tosca.topology_template.policies
default_inst_level_id = _extract_policy_info( default_inst_level_id = _extract_policy_info(
tosca_policies, inst_level_dict, tosca_policies, inst_level_dict,
aspect_delta_dict, aspect_id_dict, aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict) aspect_vdu_dict, vdu_delta_dict,
aspect_max_level_dict)
if inst_level_id is not None: if inst_level_id is not None:
# Case which instLevelId is defined. # Case which instLevelId is defined.
@@ -848,7 +851,8 @@ def _convert_ext_mng_vl(heat_dict, vl_name, vl_id):
def _extract_policy_info(tosca_policies, inst_level_dict, def _extract_policy_info(tosca_policies, inst_level_dict,
aspect_delta_dict, aspect_id_dict, aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict): aspect_vdu_dict, vdu_delta_dict,
aspect_max_level_dict):
default_inst_level_id = None default_inst_level_id = None
if tosca_policies: if tosca_policies:
for p in tosca_policies: for p in tosca_policies:
@@ -885,7 +889,8 @@ def _extract_policy_info(tosca_policies, inst_level_dict,
delta_names = aspect_val['step_deltas'] delta_names = aspect_val['step_deltas']
delta_name = delta_names[0] delta_name = delta_names[0]
aspect_id_dict[aspect_id] = delta_name aspect_id_dict[aspect_id] = delta_name
aspect_max_level_dict[aspect_id] = \
aspect_val['max_scale_level']
elif p.type == ETSI_INITIAL_DELTA: elif p.type == ETSI_INITIAL_DELTA:
vdus = p.targets vdus = p.targets
initial_delta = \ initial_delta = \

View File

@@ -994,6 +994,7 @@ def _convert_desired_capacity(inst_level_id, vnfd_dict, vdu):
inst_level_dict = {} inst_level_dict = {}
aspect_id_dict = {} aspect_id_dict = {}
vdu_delta_dict = {} vdu_delta_dict = {}
aspect_max_level_dict = {}
desired_capacity = 1 desired_capacity = 1
tosca = tosca_template.ToscaTemplate(parsed_params={}, a_file=False, tosca = tosca_template.ToscaTemplate(parsed_params={}, a_file=False,
@@ -1002,7 +1003,8 @@ def _convert_desired_capacity(inst_level_id, vnfd_dict, vdu):
default_inst_level_id = toscautils._extract_policy_info( default_inst_level_id = toscautils._extract_policy_info(
tosca_policies, inst_level_dict, tosca_policies, inst_level_dict,
aspect_delta_dict, aspect_id_dict, aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict) aspect_vdu_dict, vdu_delta_dict,
aspect_max_level_dict)
if vdu_delta_dict.get(vdu) is None: if vdu_delta_dict.get(vdu) is None:
return desired_capacity return desired_capacity
@@ -1064,3 +1066,89 @@ def get_base_nest_hot_dict(context, flavour_id, vnfd_id):
LOG.debug("Loaded base hot: %s", base_hot_dict) LOG.debug("Loaded base hot: %s", base_hot_dict)
LOG.debug("Loaded nested_hot_dict: %s", nested_hot_dict) LOG.debug("Loaded nested_hot_dict: %s", nested_hot_dict)
return base_hot_dict, nested_hot_dict return base_hot_dict, nested_hot_dict
def get_extract_policy_infos(tosca):
aspect_delta_dict = {}
aspect_vdu_dict = {}
inst_level_dict = {}
aspect_id_dict = {}
vdu_delta_dict = {}
aspect_max_level_dict = {}
tosca_policies = tosca.topology_template.policies
default_inst_level_id = toscautils._extract_policy_info(
tosca_policies, inst_level_dict,
aspect_delta_dict, aspect_id_dict,
aspect_vdu_dict, vdu_delta_dict,
aspect_max_level_dict)
extract_policy_infos = dict()
extract_policy_infos['inst_level_dict'] = inst_level_dict
extract_policy_infos['aspect_delta_dict'] = aspect_delta_dict
extract_policy_infos['aspect_id_dict'] = aspect_id_dict
extract_policy_infos['aspect_vdu_dict'] = aspect_vdu_dict
extract_policy_infos['vdu_delta_dict'] = vdu_delta_dict
extract_policy_infos['aspect_max_level_dict'] = aspect_max_level_dict
extract_policy_infos['default_inst_level_id'] = default_inst_level_id
return extract_policy_infos
def get_scale_delta_num(extract_policy_infos, aspect_id):
delta_num = 1
if extract_policy_infos['aspect_id_dict'] is None:
return delta_num
delta_id = extract_policy_infos['aspect_id_dict'].get(aspect_id)
if delta_id is None:
return delta_num
delta_num = \
extract_policy_infos['aspect_delta_dict'].get(aspect_id).get(delta_id)
return delta_num
def get_default_scale_status(context, vnf_instance, vnfd_dict):
default_scale_status = None
vnfd_dict = _get_vnfd_dict(context,
vnf_instance.vnfd_id,
vnf_instance.instantiated_vnf_info.flavour_id)
tosca = tosca_template.ToscaTemplate(parsed_params={}, a_file=False,
yaml_dict_tpl=vnfd_dict)
extract_policy_infos = get_extract_policy_infos(tosca)
if extract_policy_infos['inst_level_dict'] is None:
return default_scale_status
default_inst_level_id = extract_policy_infos['default_inst_level_id']
default_al_dict = \
extract_policy_infos['inst_level_dict'].get(default_inst_level_id)
if default_al_dict is None:
return default_scale_status
default_scale_status = []
for aspect_id, level_num in default_al_dict.items():
default_scale_status.append(
objects.ScaleInfo(
aspect_id=aspect_id,
scale_level=level_num))
return default_scale_status
def get_target_vdu_def_dict(extract_policy_infos, aspect_id, tosca):
vdu_def_dict = {}
tosca_node_tpls = tosca.topology_template.nodetemplates
if extract_policy_infos['aspect_vdu_dict'] is None:
return vdu_def_dict
vdus = extract_policy_infos['aspect_vdu_dict'].get(aspect_id)
if vdus is None:
return vdu_def_dict
for nt in tosca_node_tpls:
for node_name, node_value in nt.templates.items():
if node_name in vdus:
vdu_def_dict[node_name] = node_value
return vdu_def_dict

View File

@@ -29,6 +29,7 @@ from oslo_log import log as logging
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from oslo_utils import encodeutils from oslo_utils import encodeutils
from oslo_utils import excutils from oslo_utils import excutils
from toscaparser import tosca_template
from tacker.common import driver_manager from tacker.common import driver_manager
from tacker.common import exceptions from tacker.common import exceptions
@@ -92,6 +93,7 @@ def revert_to_error_scale(function):
"instance %(id)s. Error: %(error)s", "instance %(id)s. Error: %(error)s",
{"id": vnf_instance.id, "error": e}) {"id": vnf_instance.id, "error": e})
try: try:
vnf_instance.task_state = None
self._vnf_instance_update(context, vnf_instance) self._vnf_instance_update(context, vnf_instance)
except Exception as e: except Exception as e:
LOG.warning("Failed to revert instantiation info for vnf " LOG.warning("Failed to revert instantiation info for vnf "
@@ -386,6 +388,15 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
if vnf_dict['attributes'].get('scaling_group_names'): if vnf_dict['attributes'].get('scaling_group_names'):
vnf_instance.instantiated_vnf_info.scale_status = \ vnf_instance.instantiated_vnf_info.scale_status = \
vnf_dict['scale_status'] vnf_dict['scale_status']
elif vnf_instance.instantiated_vnf_info:
default_scale_status = vnflcm_utils.\
get_default_scale_status(
context=context,
vnf_instance=vnf_instance,
vnfd_dict=vnfd_dict)
if default_scale_status is not None:
vnf_instance.instantiated_vnf_info.scale_status = \
default_scale_status
try: try:
self._vnf_manager.invoke( self._vnf_manager.invoke(
@@ -815,16 +826,21 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
vnf_lcm_op_occ.error_point = 7 vnf_lcm_op_occ.error_point = 7
vnf_instance.instantiated_vnf_info.scale_level =\ vnf_instance.instantiated_vnf_info.scale_level =\
vnf_info['after_scale_level'] vnf_info['after_scale_level']
scaleGroupDict = \ if vim_connection_info.vim_type != 'kubernetes':
jsonutils.loads(vnf_info['attributes']['scale_group']) # NOTE(ueha): The logic of Scale for OpenStack VIM is widely hard
(scaleGroupDict # coded with `vnf_info`. This dependency is to be refactored in
['scaleGroupDict'][scale_vnf_request.aspect_id]['default']) =\ # future.
vnf_info['res_num'] scaleGroupDict = \
vnf_info['attributes']['scale_group'] =\ jsonutils.loads(vnf_info['attributes']['scale_group'])
jsonutils.dump_as_bytes(scaleGroupDict) (scaleGroupDict
['scaleGroupDict'][scale_vnf_request.aspect_id]['default']) =\
vnf_info['res_num']
vnf_info['attributes']['scale_group'] =\
jsonutils.dump_as_bytes(scaleGroupDict)
vnf_lcm_op_occ = vnf_info['vnf_lcm_op_occ'] vnf_lcm_op_occ = vnf_info['vnf_lcm_op_occ']
vnf_lcm_op_occ.operation_state = 'COMPLETED' vnf_lcm_op_occ.operation_state = 'COMPLETED'
vnf_lcm_op_occ.resource_changes = resource_changes vnf_lcm_op_occ.resource_changes = resource_changes
vnf_instance.task_state = None
self._vnfm_plugin._update_vnf_scaling(context, vnf_info, self._vnfm_plugin._update_vnf_scaling(context, vnf_info,
'PENDING_' + scale_vnf_request.type, 'PENDING_' + scale_vnf_request.type,
'ACTIVE', 'ACTIVE',
@@ -1084,10 +1100,33 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
LOG.debug( LOG.debug(
"is_reverse: %s", "is_reverse: %s",
scale_vnf_request.additional_params.get('is_reverse')) scale_vnf_request.additional_params.get('is_reverse'))
scale_json = vnf_info['attributes']['scale_group'] default = None
scaleGroupDict = jsonutils.loads(scale_json) if vim_connection_info.vim_type == 'kubernetes':
key_aspect = scale_vnf_request.aspect_id policy['vnf_instance_id'] = \
default = scaleGroupDict['scaleGroupDict'][key_aspect]['default'] vnf_info['vnf_lcm_op_occ'].get('vnf_instance_id')
vnf_instance = objects.VnfInstance.get_by_id(context,
policy['vnf_instance_id'])
vnfd_dict = vnflcm_utils._get_vnfd_dict(context,
vnf_instance.vnfd_id,
vnf_instance.instantiated_vnf_info.flavour_id)
tosca = tosca_template.ToscaTemplate(
parsed_params={}, a_file=False, yaml_dict_tpl=vnfd_dict)
extract_policy_infos = vnflcm_utils.get_extract_policy_infos(tosca)
policy['vdu_defs'] = vnflcm_utils.get_target_vdu_def_dict(
extract_policy_infos=extract_policy_infos,
aspect_id=scale_vnf_request.aspect_id,
tosca=tosca)
policy['delta_num'] = vnflcm_utils.get_scale_delta_num(
extract_policy_infos=extract_policy_infos,
aspect_id=scale_vnf_request.aspect_id)
else:
# NOTE(ueha): The logic of Scale for OpenStack VIM is widely hard
# coded with `vnf_info`. This dependency is to be refactored in
# future.
scale_json = vnf_info['attributes']['scale_group']
scaleGroupDict = jsonutils.loads(scale_json)
key_aspect = scale_vnf_request.aspect_id
default = scaleGroupDict['scaleGroupDict'][key_aspect]['default']
if (scale_vnf_request.type == 'SCALE_IN' and if (scale_vnf_request.type == 'SCALE_IN' and
scale_vnf_request.additional_params['is_reverse'] == 'True'): scale_vnf_request.additional_params['is_reverse'] == 'True'):
self._vnf_manager.invoke( self._vnf_manager.invoke(
@@ -1132,26 +1171,32 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
region_name=vim_connection_info.access_info.get('region_name') region_name=vim_connection_info.access_info.get('region_name')
) )
else: else:
heat_template = vnf_info['attributes']['heat_template'] cooldown = None
policy_in_name = scale_vnf_request.aspect_id + '_scale_in' if vim_connection_info.vim_type != 'kubernetes':
policy_out_name = scale_vnf_request.aspect_id + '_scale_out' # NOTE(ueha): The logic of Scale for OpenStack VIM is widely
# hard coded with `vnf_info`. This dependency is to be
# refactored in future.
heat_template = vnf_info['attributes']['heat_template']
policy_in_name = scale_vnf_request.aspect_id + '_scale_in'
policy_out_name = scale_vnf_request.aspect_id + '_scale_out'
heat_resource = yaml.safe_load(heat_template) heat_resource = yaml.safe_load(heat_template)
if scale_vnf_request.type == 'SCALE_IN': if scale_vnf_request.type == 'SCALE_IN':
policy['action'] = 'in' policy['action'] = 'in'
policy_temp = heat_resource['resources'][policy_in_name] policy_temp = heat_resource['resources'][policy_in_name]
policy_prop = policy_temp['properties'] policy_prop = policy_temp['properties']
cooldown = policy_prop.get('cooldown') cooldown = policy_prop.get('cooldown')
policy_name = policy_in_name policy_name = policy_in_name
else: else:
policy['action'] = 'out' policy['action'] = 'out'
policy_temp = heat_resource['resources'][policy_out_name] policy_temp = heat_resource['resources'][policy_out_name]
policy_prop = policy_temp['properties'] policy_prop = policy_temp['properties']
cooldown = policy_prop.get('cooldown') cooldown = policy_prop.get('cooldown')
policy_name = policy_out_name policy_name = policy_out_name
policy_temp = heat_resource['resources'][policy_name]
policy_prop = policy_temp['properties']
policy_temp = heat_resource['resources'][policy_name]
policy_prop = policy_temp['properties']
for i in range(scale_vnf_request.number_of_steps): for i in range(scale_vnf_request.number_of_steps):
last_event_id = self._vnf_manager.invoke( last_event_id = self._vnf_manager.invoke(
vim_connection_info.vim_type, vim_connection_info.vim_type,
@@ -1290,11 +1335,15 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
grp_id = None grp_id = None
self._update_vnf_rollback_pre(context, vnf_info) self._update_vnf_rollback_pre(context, vnf_info)
if vnf_lcm_op_occs.operation == 'SCALE': if vnf_lcm_op_occs.operation == 'SCALE':
scaleGroupDict = jsonutils.loads( if vim_connection_info.vim_type != 'kubernetes':
vnf_info['attributes']['scale_group']) # NOTE(ueha): The logic of Scale for OpenStack VIM is widely
cap_size = scaleGroupDict['scaleGroupDict'][operation_params # hard coded with `vnf_info`. This dependency is to be
['aspect_id']]['default'] # refactored in future.
vnf_info['res_num'] = cap_size scaleGroupDict = jsonutils.loads(
vnf_info['attributes']['scale_group'])
cap_size = scaleGroupDict['scaleGroupDict'][operation_params
['aspect_id']]['default']
vnf_info['res_num'] = cap_size
scale_vnf_request = objects.ScaleVnfRequest.obj_from_primitive( scale_vnf_request = objects.ScaleVnfRequest.obj_from_primitive(
operation_params, context=context) operation_params, context=context)
for scale in vnf_instance.instantiated_vnf_info.scale_status: for scale in vnf_instance.instantiated_vnf_info.scale_status:
@@ -1403,6 +1452,7 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
status = 'ACTIVE' status = 'ACTIVE'
else: else:
status = 'INACTIVE' status = 'INACTIVE'
vnf_instance.task_state = None
self._vnfm_plugin._update_vnf_rollback(context, vnf_info, self._vnfm_plugin._update_vnf_rollback(context, vnf_info,
'ERROR', 'ERROR',
status, status,

View File

@@ -23,6 +23,7 @@ from kubernetes import client
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from toscaparser import tosca_template
from tacker._i18n import _ from tacker._i18n import _
from tacker.common.container import kubernetes_utils from tacker.common.container import kubernetes_utils
@@ -34,6 +35,7 @@ from tacker import objects
from tacker.objects import vnf_package as vnf_package_obj from tacker.objects import vnf_package as vnf_package_obj
from tacker.objects import vnf_package_vnfd as vnfd_obj from tacker.objects import vnf_package_vnfd as vnfd_obj
from tacker.objects import vnf_resources as vnf_resource_obj from tacker.objects import vnf_resources as vnf_resource_obj
from tacker.vnflcm import utils as vnflcm_utils
from tacker.vnfm.infra_drivers import abstract_driver from tacker.vnfm.infra_drivers import abstract_driver
from tacker.vnfm.infra_drivers.kubernetes.k8s import translate_outputs from tacker.vnfm.infra_drivers.kubernetes.k8s import translate_outputs
from tacker.vnfm.infra_drivers.kubernetes import translate_template from tacker.vnfm.infra_drivers.kubernetes import translate_template
@@ -1019,6 +1021,96 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
finally: finally:
self.clean_authenticate_vim(auth_cred, file_descriptor) self.clean_authenticate_vim(auth_cred, file_descriptor)
def _scale_legacy(self, policy, auth_cred):
LOG.debug("VNF are scaled by updating instance of deployment")
app_v1_api_client = self.kubernetes.get_app_v1_api_client(
auth=auth_cred)
scaling_api_client = self.kubernetes.get_scaling_api_client(
auth=auth_cred)
deployment_names = policy['instance_id'].split(COMMA_CHARACTER)
policy_name = policy['name']
policy_action = policy['action']
for i in range(0, len(deployment_names), 2):
namespace = deployment_names[i]
deployment_name = deployment_names[i + 1]
deployment_info = app_v1_api_client.\
read_namespaced_deployment(namespace=namespace,
name=deployment_name)
scaling_info = scaling_api_client.\
read_namespaced_horizontal_pod_autoscaler(
namespace=namespace,
name=deployment_name)
replicas = deployment_info.status.replicas
scale_replicas = replicas
vnf_scaling_name = deployment_info.metadata.labels.\
get("scaling_name")
if vnf_scaling_name == policy_name:
if policy_action == 'out':
scale_replicas = replicas + 1
elif policy_action == 'in':
scale_replicas = replicas - 1
min_replicas = scaling_info.spec.min_replicas
max_replicas = scaling_info.spec.max_replicas
if (scale_replicas < min_replicas) or \
(scale_replicas > max_replicas):
LOG.debug("Scaling replicas is out of range. The number of"
" replicas keeps %(number)s replicas",
{'number': replicas})
scale_replicas = replicas
deployment_info.spec.replicas = scale_replicas
app_v1_api_client.patch_namespaced_deployment_scale(
namespace=namespace,
name=deployment_name,
body=deployment_info)
def _call_read_scale_api(self, app_v1_api_client, namespace, name, kind):
"""select kubernetes read scale api and call"""
def convert(name):
name_with_underscores = re.sub(
'(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2',
name_with_underscores).lower()
snake_case_kind = convert(kind)
try:
read_scale_api = eval('app_v1_api_client.'
'read_namespaced_%s_scale' % snake_case_kind)
response = read_scale_api(name=name, namespace=namespace)
except Exception as e:
error_reason = _("Failed the request to read a scale information."
" namespace: {namespace}, name: {name},"
" kind: {kind}, Reason: {exception}").format(
namespace=namespace, name=name, kind=kind, exception=e)
raise vnfm.CNFScaleFailed(reason=error_reason)
return response
def _call_patch_scale_api(self, app_v1_api_client, namespace, name,
kind, body):
"""select kubernetes patch scale api and call"""
def convert(name):
name_with_underscores = re.sub(
'(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2',
name_with_underscores).lower()
snake_case_kind = convert(kind)
try:
patch_scale_api = eval('app_v1_api_client.'
'patch_namespaced_%s_scale' % snake_case_kind)
response = patch_scale_api(name=name, namespace=namespace,
body=body)
except Exception as e:
error_reason = _("Failed the request to update a scale information"
". namespace: {namespace}, name: {name},"
" kind: {kind}, Reason: {exception}").format(
namespace=namespace, name=name, kind=kind, exception=e)
raise vnfm.CNFScaleFailed(reason=error_reason)
return response
@log.log @log.log
def scale(self, context, plugin, auth_attr, policy, region_name): def scale(self, context, plugin, auth_attr, policy, region_name):
"""Scale function """Scale function
@@ -1027,58 +1119,154 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
The min_replicas and max_replicas is limited by the number of replicas The min_replicas and max_replicas is limited by the number of replicas
of policy scaling when user define VNF descriptor. of policy scaling when user define VNF descriptor.
""" """
LOG.debug("VNF are scaled by updating instance of deployment")
# initialize Kubernetes APIs # initialize Kubernetes APIs
auth_cred, file_descriptor = self._get_auth_creds(auth_attr) auth_cred, file_descriptor = self._get_auth_creds(auth_attr)
vnf_resources = objects.VnfResourceList.get_by_vnf_instance_id(
context, policy['vnf_instance_id'])
try: try:
app_v1_api_client = self.kubernetes.get_app_v1_api_client( if not vnf_resources:
auth=auth_cred) # execute legacy scale method
scaling_api_client = self.kubernetes.get_scaling_api_client( self._scale_legacy(policy, auth_cred)
auth=auth_cred) else:
deployment_names = policy['instance_id'].split(COMMA_CHARACTER) app_v1_api_client = self.kubernetes.get_app_v1_api_client(
policy_name = policy['name'] auth=auth_cred)
policy_action = policy['action'] aspect_id = policy['name']
vdu_defs = policy['vdu_defs']
is_found = False
error_reason = None
for vnf_resource in vnf_resources:
# The resource that matches the following is the resource
# to be scaled:
# The `name` of the resource stored in vnf_resource (the
# name defined in `metadata.name` of Kubernetes object
# file) matches the value of `properties.name` of VDU
# defined in VNFD.
name = vnf_resource.resource_name.\
split(COMMA_CHARACTER)[1]
for vdu_id, vdu_def in vdu_defs.items():
vdu_properties = vdu_def.get('properties')
if name == vdu_properties.get('name'):
namespace = vnf_resource.resource_name.\
split(COMMA_CHARACTER)[0]
kind = vnf_resource.resource_type.\
split(COMMA_CHARACTER)[1]
is_found = True
break
if is_found:
break
else:
error_reason = _(
"Target VnfResource for aspectId"
" {aspect_id} is not found in DB").format(
aspect_id=aspect_id)
raise vnfm.CNFScaleFailed(reason=error_reason)
for i in range(0, len(deployment_names), 2): target_kinds = ["Deployment", "ReplicaSet", "StatefulSet"]
namespace = deployment_names[i] if kind not in target_kinds:
deployment_name = deployment_names[i + 1] error_reason = _(
deployment_info = app_v1_api_client.\ "Target kind {kind} is out of scale target").\
read_namespaced_deployment(namespace=namespace, format(kind=kind)
name=deployment_name) raise vnfm.CNFScaleFailed(reason=error_reason)
scaling_info = scaling_api_client.\
read_namespaced_horizontal_pod_autoscaler(
namespace=namespace,
name=deployment_name)
replicas = deployment_info.status.replicas scale_info = self._call_read_scale_api(
scale_replicas = replicas app_v1_api_client=app_v1_api_client,
vnf_scaling_name = deployment_info.metadata.labels.\ namespace=namespace,
get("scaling_name") name=name,
if vnf_scaling_name == policy_name: kind=kind)
if policy_action == 'out':
scale_replicas = replicas + 1
elif policy_action == 'in':
scale_replicas = replicas - 1
min_replicas = scaling_info.spec.min_replicas current_replicas = scale_info.status.replicas
max_replicas = scaling_info.spec.max_replicas vdu_profile = vdu_properties.get('vdu_profile')
if policy['action'] == 'out':
scale_replicas = current_replicas + policy['delta_num']
elif policy['action'] == 'in':
scale_replicas = current_replicas - policy['delta_num']
max_replicas = vdu_profile.get('max_number_of_instances')
min_replicas = vdu_profile.get('min_number_of_instances')
if (scale_replicas < min_replicas) or \ if (scale_replicas < min_replicas) or \
(scale_replicas > max_replicas): (scale_replicas > max_replicas):
LOG.debug("Scaling replicas is out of range. The number of" error_reason = _(
" replicas keeps %(number)s replicas", "The number of target replicas after"
{'number': replicas}) " scaling [{after_replicas}] is out of range").\
scale_replicas = replicas format(
deployment_info.spec.replicas = scale_replicas after_replicas=scale_replicas)
app_v1_api_client.patch_namespaced_deployment_scale( raise vnfm.CNFScaleFailed(reason=error_reason)
scale_info.spec.replicas = scale_replicas
self._call_patch_scale_api(
app_v1_api_client=app_v1_api_client,
namespace=namespace, namespace=namespace,
name=deployment_name, name=name,
body=deployment_info) kind=kind,
body=scale_info)
except Exception as e: except Exception as e:
LOG.error('Scaling VNF got an error due to %s', e) LOG.error('Scaling VNF got an error due to %s', e)
raise raise
finally: finally:
self.clean_authenticate_vim(auth_cred, file_descriptor) self.clean_authenticate_vim(auth_cred, file_descriptor)
def _scale_wait_legacy(self, policy, auth_cred):
core_v1_api_client = self.kubernetes.get_core_v1_api_client(
auth=auth_cred)
deployment_info = policy['instance_id'].split(",")
pods_information = self._get_pods_information(
core_v1_api_client=core_v1_api_client,
deployment_info=deployment_info)
status = self._get_pod_status(pods_information)
stack_retries = self.STACK_RETRIES
error_reason = None
while status == 'Pending' and stack_retries > 0:
time.sleep(self.STACK_RETRY_WAIT)
pods_information = self._get_pods_information(
core_v1_api_client=core_v1_api_client,
deployment_info=deployment_info)
status = self._get_pod_status(pods_information)
# LOG.debug('status: %s', status)
stack_retries = stack_retries - 1
LOG.debug('VNF initializing status: %(service_name)s %(status)s',
{'service_name': str(deployment_info), 'status': status})
if stack_retries == 0 and status != 'Running':
error_reason = _("Resource creation is not completed within"
" {wait} seconds as creation of stack {stack}"
" is not completed").format(
wait=(self.STACK_RETRIES *
self.STACK_RETRY_WAIT),
stack=policy['instance_id'])
LOG.error("VNF Creation failed: %(reason)s",
{'reason': error_reason})
raise vnfm.VNFCreateWaitFailed(reason=error_reason)
elif stack_retries != 0 and status != 'Running':
raise vnfm.VNFCreateWaitFailed(reason=error_reason)
def _is_match_pod_naming_rule(self, rsc_kind, rsc_name, pod_name):
match_result = None
if rsc_kind == 'Deployment':
# Expected example: name-012789abef-019az
match_result = re.match(
rsc_name + '-([0-9a-f]{10})-([0-9a-z]{5})+$',
pod_name)
elif rsc_kind == 'ReplicaSet':
# Expected example: name-019az
match_result = re.match(
rsc_name + '-([0-9a-z]{5})+$',
pod_name)
elif rsc_kind == 'StatefulSet':
# Expected example: name-0
match_result = re.match(
rsc_name + '-[0-9]+$',
pod_name)
if match_result:
return True
else:
return False
def scale_wait(self, context, plugin, auth_attr, policy, region_name, def scale_wait(self, context, plugin, auth_attr, policy, region_name,
last_event_id): last_event_id):
"""Scale wait function """Scale wait function
@@ -1088,47 +1276,87 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
""" """
# initialize Kubernetes APIs # initialize Kubernetes APIs
auth_cred, file_descriptor = self._get_auth_creds(auth_attr) auth_cred, file_descriptor = self._get_auth_creds(auth_attr)
vnf_resources = objects.VnfResourceList.get_by_vnf_instance_id(
context, policy['vnf_instance_id'])
try: try:
core_v1_api_client = self.kubernetes.get_core_v1_api_client( if not vnf_resources:
auth=auth_cred) # execute legacy scale_wait method
deployment_info = policy['instance_id'].split(",") self._scale_wait_legacy(policy, auth_cred)
else:
core_v1_api_client = self.kubernetes.get_core_v1_api_client(
auth=auth_cred)
app_v1_api_client = self.kubernetes.get_app_v1_api_client(
auth=auth_cred)
aspect_id = policy['name']
vdu_defs = policy['vdu_defs']
is_found = False
error_reason = None
for vnf_resource in vnf_resources:
name = vnf_resource.resource_name.\
split(COMMA_CHARACTER)[1]
for vdu_id, vdu_def in vdu_defs.items():
vdu_properties = vdu_def.get('properties')
if name == vdu_properties.get('name'):
namespace = vnf_resource.resource_name.\
split(COMMA_CHARACTER)[0]
kind = vnf_resource.resource_type.\
split(COMMA_CHARACTER)[1]
is_found = True
break
if is_found:
break
else:
error_reason = _(
"Target VnfResource for aspectId {aspect_id}"
" is not found in DB").format(
aspect_id=aspect_id)
raise vnfm.CNFScaleWaitFailed(reason=error_reason)
pods_information = self._get_pods_information( scale_info = self._call_read_scale_api(
core_v1_api_client=core_v1_api_client, app_v1_api_client=app_v1_api_client,
deployment_info=deployment_info) namespace=namespace,
status = self._get_pod_status(pods_information) name=name,
kind=kind)
status = 'Pending'
stack_retries = self.STACK_RETRIES
error_reason = None
while status == 'Pending' and stack_retries > 0:
pods_information = list()
respone = core_v1_api_client.list_namespaced_pod(
namespace=namespace)
for pod in respone.items:
match_result = self._is_match_pod_naming_rule(
kind, name, pod.metadata.name)
if match_result:
pods_information.append(pod)
stack_retries = self.STACK_RETRIES status = self._get_pod_status(pods_information)
error_reason = None if status == 'Running' and \
while status == 'Pending' and stack_retries > 0: scale_info.spec.replicas != len(pods_information):
time.sleep(self.STACK_RETRY_WAIT) status = 'Pending'
pods_information = self._get_pods_information( if status == 'Pending':
core_v1_api_client=core_v1_api_client, stack_retries = stack_retries - 1
deployment_info=deployment_info) time.sleep(self.STACK_RETRY_WAIT)
status = self._get_pod_status(pods_information) elif status == 'Unknown':
error_reason = _(
"CNF Scale failed caused by the Pod status"
" is Unknown")
raise vnfm.CNFScaleWaitFailed(reason=error_reason)
# LOG.debug('status: %s', status) if stack_retries == 0 and status != 'Running':
stack_retries = stack_retries - 1 error_reason = _(
"CNF Scale failed to complete within"
LOG.debug('VNF initializing status: %(service_name)s %(status)s', " {wait} seconds while waiting for the aspect_id"
{'service_name': str(deployment_info), 'status': status}) " {aspect_id} to be scaled").format(
wait=(self.STACK_RETRIES *
if stack_retries == 0 and status != 'Running': self.STACK_RETRY_WAIT),
error_reason = _("Resource creation is not completed within" aspect_id=aspect_id)
" {wait} seconds as creation of stack {stack}" LOG.error("CNF Scale failed: %(reason)s",
" is not completed").format( {'reason': error_reason})
wait=(self.STACK_RETRIES * raise vnfm.CNFScaleWaitFailed(reason=error_reason)
self.STACK_RETRY_WAIT),
stack=policy['instance_id'])
LOG.error("VNF Creation failed: %(reason)s",
{'reason': error_reason})
raise vnfm.VNFCreateWaitFailed(reason=error_reason)
elif stack_retries != 0 and status != 'Running':
raise vnfm.VNFCreateWaitFailed(reason=error_reason)
except Exception as e: except Exception as e:
LOG.error('Scaling wait VNF got an error due to %s', e) LOG.error('Scaling wait CNF got an error due to %s', e)
raise raise
finally: finally:
self.clean_authenticate_vim(auth_cred, file_descriptor) self.clean_authenticate_vim(auth_cred, file_descriptor)
@@ -1315,7 +1543,8 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
vnf_dict, vnf_dict,
auth_attr, auth_attr,
region_name): region_name):
pass return_id_list = []
return return_id_list
def get_scale_in_ids(self, def get_scale_in_ids(self,
plugin, plugin,
@@ -1325,7 +1554,11 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
auth_attr, auth_attr,
region_name, region_name,
number_of_steps): number_of_steps):
pass return_id_list = []
return_name_list = []
return_grp_id = None
return_res_num = None
return return_id_list, return_name_list, return_grp_id, return_res_num
def scale_resource_update(self, context, vnf_instance, def scale_resource_update(self, context, vnf_instance,
scale_vnf_request, scale_vnf_request,
@@ -1341,7 +1574,33 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
region_name, region_name,
scale_name_list, scale_name_list,
grp_id): grp_id):
pass # NOTE(ueha): The `is_reverse` option is not supported in kubernetes
# VIM, and returns an error response to the user if `is_reverse` is
# true. However, since this method is called in the sequence of
# rollback operation, implementation is required.
vnf_instance_id = vnf_info['vnf_lcm_op_occ'].vnf_instance_id
aspect_id = scale_vnf_request.aspect_id
vnf_instance = objects.VnfInstance.get_by_id(context, vnf_instance_id)
vnfd_dict = vnflcm_utils._get_vnfd_dict(context,
vnf_instance.vnfd_id,
vnf_instance.instantiated_vnf_info.flavour_id)
tosca = tosca_template.ToscaTemplate(parsed_params={}, a_file=False,
yaml_dict_tpl=vnfd_dict)
extract_policy_infos = vnflcm_utils.get_extract_policy_infos(tosca)
policy = dict()
policy['name'] = aspect_id
policy['action'] = 'in'
policy['vnf_instance_id'] = vnf_instance_id
policy['vdu_defs'] = vnflcm_utils.get_target_vdu_def_dict(
extract_policy_infos=extract_policy_infos,
aspect_id=scale_vnf_request.aspect_id,
tosca=tosca)
policy['delta_num'] = vnflcm_utils.get_scale_delta_num(
extract_policy_infos=extract_policy_infos,
aspect_id=scale_vnf_request.aspect_id)
self.scale(context, plugin, auth_attr, policy, region_name)
def scale_out_initial(self, def scale_out_initial(self,
context, context,
@@ -1358,7 +1617,30 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
auth_attr, auth_attr,
vnf_info, vnf_info,
region_name): region_name):
pass lcm_op_occ = vnf_info.get('vnf_lcm_op_occ')
vnf_instance_id = lcm_op_occ.get('vnf_instance_id')
operation_params = jsonutils.loads(lcm_op_occ.get('operation_params'))
scale_vnf_request = objects.ScaleVnfRequest.obj_from_primitive(
operation_params, context=context)
aspect_id = scale_vnf_request.aspect_id
vnf_instance = objects.VnfInstance.get_by_id(context, vnf_instance_id)
vnfd_dict = vnflcm_utils._get_vnfd_dict(context,
vnf_instance.vnfd_id,
vnf_instance.instantiated_vnf_info.flavour_id)
tosca = tosca_template.ToscaTemplate(parsed_params={}, a_file=False,
yaml_dict_tpl=vnfd_dict)
extract_policy_infos = vnflcm_utils.get_extract_policy_infos(tosca)
policy = dict()
policy['name'] = aspect_id
policy['vnf_instance_id'] = lcm_op_occ.get('vnf_instance_id')
policy['vdu_defs'] = vnflcm_utils.get_target_vdu_def_dict(
extract_policy_infos=extract_policy_infos,
aspect_id=scale_vnf_request.aspect_id,
tosca=tosca)
self.scale_wait(context, plugin, auth_attr, policy,
region_name, None)
def get_cinder_list(self, def get_cinder_list(self,
vnf_info): vnf_info):
@@ -1380,4 +1662,7 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
aspect_id, aspect_id,
auth_attr, auth_attr,
region_name): region_name):
pass return_id_list = []
return_name_list = []
return_grp_id = None
return return_id_list, return_name_list, return_grp_id