Merge "Support CNF scale operations based on ETSI NFV"

This commit is contained in:
Zuul 2021-03-18 07:52:51 +00:00 committed by Gerrit Code Review
commit 680a0ea73a
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 uuidutils
from sqlalchemy import exc as sqlexc
from toscaparser import tosca_template
import ast
import functools
@ -53,6 +54,8 @@ from tacker.objects import fields
from tacker.objects import vnf_lcm_subscriptions as subscription_obj
from tacker.plugins.common import constants
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
from tacker.vnfm import vim_client
from tacker import wsgi
@ -977,7 +980,24 @@ class VnfLcmController(wsgi.Controller):
return self._make_problem_detail(
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)
scale_vnf_request = objects.ScaleVnfRequest.obj_from_primitive(
req_body, context=context)
@ -1005,18 +1025,34 @@ class VnfLcmController(wsgi.Controller):
if not scale_vnf_request.additional_params.get('is_auto'):
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 current_level == 0 or\
current_level < scale_vnf_request.number_of_steps:
return self._make_problem_detail(
'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
elif scale_vnf_request.type == 'SCALE_OUT':
scaleGroupDict = jsonutils.loads(
vnf_info['attributes']['scale_group'])
max_level = (scaleGroupDict['scaleGroupDict']
[scale_vnf_request.aspect_id]['maxLevel'])
if vim_type == "kubernetes":
max_level = self._get_scale_max_level_from_vnfd(
context=context,
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
if max_level < scale_level:
return self._make_problem_detail(
@ -1043,6 +1079,9 @@ class VnfLcmController(wsgi.Controller):
error_point=1)
vnf_lcm_op_occ.create()
vnf_instance.task_state = fields.VnfInstanceTaskState.SCALING
vnf_instance.save()
vnflcm_url = CONF.vnf_lcm.endpoint_url + \
"/vnflcm/v1/vnf_lcm_op_occs/" + vnf_lcm_op_occs_id
insta_url = CONF.vnf_lcm.endpoint_url + \
@ -1096,7 +1135,7 @@ class VnfLcmController(wsgi.Controller):
if not vnf_instance.instantiated_vnf_info.scale_status:
return self._make_problem_detail(
'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:
return self._make_problem_detail(
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)
vnfd_attribute.create()
break
@revert_upload_vnf_package
def upload_vnf_package_content(self, context, vnf_package):
vnf_package.onboarding_state = (

View File

@ -109,6 +109,14 @@ class CNFCreateWaitFailed(exceptions.TackerException):
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):
message = _('service type %(service_type_id)s could not be found')

View File

@ -137,9 +137,10 @@ class VnfInstanceTaskState(BaseTackerEnum):
INSTANTIATING = 'INSTANTIATING'
HEALING = 'HEALING'
TERMINATING = 'TERMINATING'
SCALING = 'SCALING'
ERROR = 'ERROR'
ALL = (INSTANTIATING, HEALING, TERMINATING, ERROR)
ALL = (INSTANTIATING, HEALING, TERMINATING, SCALING, ERROR)
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',
'attributes': {}}
def _update_vnf_scaling(self, *args, **kwargs):
pass
class TestNfvoPlugin(db_base.SqlTestCase):
def setUp(self):

View File

@ -184,8 +184,9 @@ def return_vnf_instance(
}
instantiated_vnf_info = get_instantiated_vnf_info
s_status = {"aspect_id": "SP1", "scale_level": 1}
scale_status = objects.ScaleInfo(**s_status)
if scale_status == "scale_status":
s_status = {"aspect_id": "SP1", "scale_level": 1}
scale_status = objects.ScaleInfo(**s_status)
instantiated_vnf_info.update(
{"ext_cp_info": [],
@ -792,10 +793,10 @@ def _get_vnf(**updates):
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 = {
'type': type,
'aspect_id': "SP1",
'aspect_id': aspect_id,
'number_of_steps': number_of_steps,
'scale_level': 1,
'additional_params': {"is_reverse": is_reverse},
@ -844,12 +845,41 @@ def vnf_scale():
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(
state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
start_time=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
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_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,
operation='SCALE',
operation_state='FAILED_TEMP',
@ -857,16 +887,14 @@ def vnflcm_rollback(error_point=7):
operation_params='{"type": "SCALE_OUT", "aspect_id": "SP1"}',
error_point=error_point,
id=constants.UUID,
created_at=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC))
created_at=dt)
def vnflcm_rollback_insta(error_point=7):
dt = datetime.datetime(2000, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
return objects.VnfLcmOpOcc(
state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
start_time=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
state_entered_time=dt,
start_time=dt,
vnf_instance_id=uuidsentinel.vnf_instance_id,
operation='INSTANTIATE',
operation_state='FAILED_TEMP',
@ -874,16 +902,14 @@ def vnflcm_rollback_insta(error_point=7):
operation_params='{}',
error_point=error_point,
id=constants.UUID,
created_at=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC))
created_at=dt)
def vnflcm_rollback_active():
dt = datetime.datetime(2000, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
return objects.VnfLcmOpOcc(
state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
start_time=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
state_entered_time=dt,
start_time=dt,
vnf_instance_id=uuidsentinel.vnf_instance_id,
operation='SCALE',
operation_state='ACTIVE',
@ -891,16 +917,14 @@ def vnflcm_rollback_active():
operation_params='{"type": "SCALE_OUT", "aspect_id": "SP1"}',
error_point=7,
id=constants.UUID,
created_at=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC))
created_at=dt)
def vnflcm_rollback_ope():
dt = datetime.datetime(2000, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
return objects.VnfLcmOpOcc(
state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
start_time=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
state_entered_time=dt,
start_time=dt,
vnf_instance_id=uuidsentinel.vnf_instance_id,
operation='HEAL',
operation_state='FAILED_TEMP',
@ -908,16 +932,14 @@ def vnflcm_rollback_ope():
operation_params='{}',
error_point=7,
id=constants.UUID,
created_at=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC))
created_at=dt)
def vnflcm_rollback_scale_in():
dt = datetime.datetime(2000, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
return objects.VnfLcmOpOcc(
state_entered_time=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
start_time=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
state_entered_time=dt,
start_time=dt,
vnf_instance_id=uuidsentinel.vnf_instance_id,
operation='SCALE',
operation_state='FAILED_TEMP',
@ -925,8 +947,7 @@ def vnflcm_rollback_scale_in():
operation_params='{"type": "SCALE_IN", "aspect_id": "SP1"}',
error_point=7,
id=constants.UUID,
created_at=datetime.datetime(2000, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC))
created_at=dt)
def vnf_rollback():
@ -1090,6 +1111,110 @@ def 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):
"""Add a 'tacker.context' to WSGI environ."""
@ -1294,13 +1419,12 @@ def fake_vnf_lcm_op_occs():
}
changed_info_obj = objects.VnfInfoModifications(**changed_info)
dt = datetime.datetime(1900, 1, 1, 1, 1, 1, tzinfo=iso8601.UTC)
vnf_lcm_op_occs = {
'id': constants.UUID,
'operation_state': 'COMPLETED',
'state_entered_time': datetime.datetime(1900, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
'start_time': datetime.datetime(1900, 1, 1, 1, 1, 1,
tzinfo=iso8601.UTC),
'state_entered_time': dt,
'start_time': dt,
'vnf_instance_id': constants.UUID,
'operation': 'MODIFY_INFO',
'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_vnf_id = '7168062e-9fa1-4203-8cb7-f5c99ff3ee1b'
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_update_id = 'a18c8bae-898a-4932-bff8-d5eac981a9b8'
@ -110,6 +112,8 @@ class FakeVNFMPlugin(mock.Mock):
return self.get_dummy_vnf_error()
elif self.vnf3_vnf_id in args:
return self.get_dummy_vnf_not_error()
elif self.vnf_for_cnf_vnf_id in args:
return fakes.vnf_dict_cnf()
else:
return self.get_dummy_vnf_active()
@ -2369,6 +2373,7 @@ class TestController(base.TestCase):
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")
@ -2379,13 +2384,18 @@ class TestController(base.TestCase):
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._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(
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(
"SCALE_IN", 1)
mock_create.return_value = 200
@ -2410,6 +2420,7 @@ class TestController(base.TestCase):
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")
@ -2420,13 +2431,18 @@ class TestController(base.TestCase):
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._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(
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(
"SCALE_OUT", 1)
mock_create.return_value = 200
@ -2451,6 +2467,7 @@ class TestController(base.TestCase):
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")
@ -2459,13 +2476,19 @@ class TestController(base.TestCase):
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._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(
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(
"SCALE_IN", 4)
mock_create.return_value = 200
@ -2505,8 +2528,12 @@ class TestController(base.TestCase):
mock_get_service_plugins):
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(
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(
"SCALE_OUT", 4)
mock_create.return_value = 200
@ -2533,6 +2560,7 @@ class TestController(base.TestCase):
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(objects.ScaleVnfRequest, "obj_from_primitive")
@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(vnf_lcm_rpc.VNFLcmRPCAPI, "send_notification")
@mock.patch.object(objects.VnfLcmOpOcc, "create")
@ -2541,6 +2569,7 @@ class TestController(base.TestCase):
mock_create,
mock_send_notification,
mock_vnf_instance,
mock_vnf_instance_save,
mock_get_vnf,
mock_obj_from_primitive,
get_service_plugins):
@ -2556,9 +2585,13 @@ class TestController(base.TestCase):
"SCALE_IN", 1)
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(
fields.VnfInstanceState.INSTANTIATED,
scale_status="scale_status")
scale_status="scale_status",
**update)
vnf_instance.instantiated_vnf_info.instance_id =\
uuidsentinel.instance_id
@ -2571,9 +2604,10 @@ class TestController(base.TestCase):
vnf_info = fakes._get_vnf()
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,
vnf_info, vnf_instance, body)
vnf_instance, vnf_info, body)
mock_send_notification.assert_called_once()
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(
'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',
return_value={'VNFM': FakeVNFMPlugin()})
@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")
self._mock_vnf_manager(fail_method_name='create_wait')
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,
driver.instantiate_vnf, self.context, vnf_instance_obj, vnf_dict,
instantiate_vnf_req_obj)
@ -750,7 +753,9 @@ class TestVnflcmDriver(db_base.SqlTestCase):
uuidsentinel.instance_id
self._mock_vnf_manager()
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 = {}
driver.heal_vnf(self.context, vnf_instance, vnf_dict, heal_vnf_req)
self.assertEqual(1, mock_save.call_count)
@ -1062,9 +1067,9 @@ class TestVnflcmDriver(db_base.SqlTestCase):
'{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \
'1, \"maxLevel\": 3, \"initialNum\": 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_type="fake_type")
vim_type="openstack")
scale_name_list = ["fake"]
grp_id = "fake_id"
driver = vnflcm_driver.VnfLcmDriver()
@ -1089,9 +1094,9 @@ class TestVnflcmDriver(db_base.SqlTestCase):
'{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \
'1, \"maxLevel\": 3, \"initialNum\": 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_type="fake_type")
vim_type="openstack")
scale_name_list = ["fake"]
grp_id = "fake_id"
with open(vnf_info["attributes"]["heat_template"], "r") as f:
@ -1119,9 +1124,9 @@ class TestVnflcmDriver(db_base.SqlTestCase):
'{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \
'1, \"maxLevel\": 3, \"initialNum\": 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_type="fake_type")
vim_type="openstack")
scale_name_list = ["fake"]
grp_id = "fake_id"
with open(vnf_info["attributes"]["heat_template"], "r") as f:
@ -1149,9 +1154,9 @@ class TestVnflcmDriver(db_base.SqlTestCase):
'{ \"SP1\": { \"vdu\": [\"VDU1\"], \"num\": ' + \
'1, \"maxLevel\": 3, \"initialNum\": 0, ' + \
'\"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_type="fake_type")
vim_type="openstack")
scale_name_list = ["fake"]
grp_id = "fake_id"
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,
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',
return_value={'VNFM': FakeVNFMPlugin()})
@mock.patch.object(VnfLcmDriver,
@ -2068,3 +2175,69 @@ class TestVnflcmDriver(db_base.SqlTestCase):
self.assertEqual(1, mock_scale.call_count)
self.assertEqual(1, mock_wait.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 tacker.db.db_sqlalchemy import models
from tacker.tests import uuidsentinel
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.tests.unit import base
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.openstack.fixture_data import \
fixture_data_utils as fd_utils
@ -1799,3 +1800,490 @@ class TestKubernetes(base.TestCase):
"{'namespace': 'test', 'name': " +
"'curry-test001', 'apiVersion': 'apps/v1', " +
"'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 = {}
# { vduId: initialDelta }
vdu_delta_dict = {}
# { aspectId: maxScaleLevel }
aspect_max_level_dict = {}
tosca_policies = tosca.topology_template.policies
default_inst_level_id = _extract_policy_info(
tosca_policies, inst_level_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:
# 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,
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
if 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_name = delta_names[0]
aspect_id_dict[aspect_id] = delta_name
aspect_max_level_dict[aspect_id] = \
aspect_val['max_scale_level']
elif p.type == ETSI_INITIAL_DELTA:
vdus = p.targets
initial_delta = \

View File

@ -994,6 +994,7 @@ def _convert_desired_capacity(inst_level_id, vnfd_dict, vdu):
inst_level_dict = {}
aspect_id_dict = {}
vdu_delta_dict = {}
aspect_max_level_dict = {}
desired_capacity = 1
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(
tosca_policies, inst_level_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:
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 nested_hot_dict: %s", 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_utils import encodeutils
from oslo_utils import excutils
from toscaparser import tosca_template
from tacker.common import driver_manager
from tacker.common import exceptions
@ -92,6 +93,7 @@ def revert_to_error_scale(function):
"instance %(id)s. Error: %(error)s",
{"id": vnf_instance.id, "error": e})
try:
vnf_instance.task_state = None
self._vnf_instance_update(context, vnf_instance)
except Exception as e:
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'):
vnf_instance.instantiated_vnf_info.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:
self._vnf_manager.invoke(
@ -815,16 +826,21 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
vnf_lcm_op_occ.error_point = 7
vnf_instance.instantiated_vnf_info.scale_level =\
vnf_info['after_scale_level']
scaleGroupDict = \
jsonutils.loads(vnf_info['attributes']['scale_group'])
(scaleGroupDict
['scaleGroupDict'][scale_vnf_request.aspect_id]['default']) =\
vnf_info['res_num']
vnf_info['attributes']['scale_group'] =\
jsonutils.dump_as_bytes(scaleGroupDict)
if vim_connection_info.vim_type != 'kubernetes':
# NOTE(ueha): The logic of Scale for OpenStack VIM is widely hard
# coded with `vnf_info`. This dependency is to be refactored in
# future.
scaleGroupDict = \
jsonutils.loads(vnf_info['attributes']['scale_group'])
(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.operation_state = 'COMPLETED'
vnf_lcm_op_occ.resource_changes = resource_changes
vnf_instance.task_state = None
self._vnfm_plugin._update_vnf_scaling(context, vnf_info,
'PENDING_' + scale_vnf_request.type,
'ACTIVE',
@ -1084,10 +1100,33 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
LOG.debug(
"is_reverse: %s",
scale_vnf_request.additional_params.get('is_reverse'))
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']
default = None
if vim_connection_info.vim_type == 'kubernetes':
policy['vnf_instance_id'] = \
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
scale_vnf_request.additional_params['is_reverse'] == 'True'):
self._vnf_manager.invoke(
@ -1132,26 +1171,32 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
region_name=vim_connection_info.access_info.get('region_name')
)
else:
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'
cooldown = None
if vim_connection_info.vim_type != 'kubernetes':
# 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)
if scale_vnf_request.type == 'SCALE_IN':
policy['action'] = 'in'
policy_temp = heat_resource['resources'][policy_in_name]
policy_prop = policy_temp['properties']
cooldown = policy_prop.get('cooldown')
policy_name = policy_in_name
else:
policy['action'] = 'out'
policy_temp = heat_resource['resources'][policy_out_name]
policy_prop = policy_temp['properties']
cooldown = policy_prop.get('cooldown')
policy_name = policy_out_name
heat_resource = yaml.safe_load(heat_template)
if scale_vnf_request.type == 'SCALE_IN':
policy['action'] = 'in'
policy_temp = heat_resource['resources'][policy_in_name]
policy_prop = policy_temp['properties']
cooldown = policy_prop.get('cooldown')
policy_name = policy_in_name
else:
policy['action'] = 'out'
policy_temp = heat_resource['resources'][policy_out_name]
policy_prop = policy_temp['properties']
cooldown = policy_prop.get('cooldown')
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):
last_event_id = self._vnf_manager.invoke(
vim_connection_info.vim_type,
@ -1290,11 +1335,15 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
grp_id = None
self._update_vnf_rollback_pre(context, vnf_info)
if vnf_lcm_op_occs.operation == 'SCALE':
scaleGroupDict = jsonutils.loads(
vnf_info['attributes']['scale_group'])
cap_size = scaleGroupDict['scaleGroupDict'][operation_params
['aspect_id']]['default']
vnf_info['res_num'] = cap_size
if vim_connection_info.vim_type != 'kubernetes':
# NOTE(ueha): The logic of Scale for OpenStack VIM is widely
# hard coded with `vnf_info`. This dependency is to be
# refactored in future.
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(
operation_params, context=context)
for scale in vnf_instance.instantiated_vnf_info.scale_status:
@ -1403,6 +1452,7 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
status = 'ACTIVE'
else:
status = 'INACTIVE'
vnf_instance.task_state = None
self._vnfm_plugin._update_vnf_rollback(context, vnf_info,
'ERROR',
status,

View File

@ -23,6 +23,7 @@ from kubernetes import client
from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
from toscaparser import tosca_template
from tacker._i18n import _
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_vnfd as vnfd_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.kubernetes.k8s import translate_outputs
from tacker.vnfm.infra_drivers.kubernetes import translate_template
@ -1019,6 +1021,96 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
finally:
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
def scale(self, context, plugin, auth_attr, policy, region_name):
"""Scale function
@ -1027,58 +1119,154 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
The min_replicas and max_replicas is limited by the number of replicas
of policy scaling when user define VNF descriptor.
"""
LOG.debug("VNF are scaled by updating instance of deployment")
# initialize Kubernetes APIs
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:
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']
if not vnf_resources:
# execute legacy scale method
self._scale_legacy(policy, auth_cred)
else:
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:
# 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):
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)
target_kinds = ["Deployment", "ReplicaSet", "StatefulSet"]
if kind not in target_kinds:
error_reason = _(
"Target kind {kind} is out of scale target").\
format(kind=kind)
raise vnfm.CNFScaleFailed(reason=error_reason)
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
scale_info = self._call_read_scale_api(
app_v1_api_client=app_v1_api_client,
namespace=namespace,
name=name,
kind=kind)
min_replicas = scaling_info.spec.min_replicas
max_replicas = scaling_info.spec.max_replicas
current_replicas = scale_info.status.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 \
(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(
error_reason = _(
"The number of target replicas after"
" scaling [{after_replicas}] is out of range").\
format(
after_replicas=scale_replicas)
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,
name=deployment_name,
body=deployment_info)
name=name,
kind=kind,
body=scale_info)
except Exception as e:
LOG.error('Scaling VNF got an error due to %s', e)
raise
finally:
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,
last_event_id):
"""Scale wait function
@ -1088,47 +1276,87 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
"""
# initialize Kubernetes APIs
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:
core_v1_api_client = self.kubernetes.get_core_v1_api_client(
auth=auth_cred)
deployment_info = policy['instance_id'].split(",")
if not vnf_resources:
# execute legacy scale_wait method
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(
core_v1_api_client=core_v1_api_client,
deployment_info=deployment_info)
status = self._get_pod_status(pods_information)
scale_info = self._call_read_scale_api(
app_v1_api_client=app_v1_api_client,
namespace=namespace,
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
error_reason = None
while status == 'Pending' and stack_retries > 0:
time.sleep(self.STACK_RETRY_WAIT)
status = self._get_pod_status(pods_information)
if status == 'Running' and \
scale_info.spec.replicas != len(pods_information):
status = 'Pending'
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)
if status == 'Pending':
stack_retries = stack_retries - 1
time.sleep(self.STACK_RETRY_WAIT)
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)
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)
if stack_retries == 0 and status != 'Running':
error_reason = _(
"CNF Scale failed to complete within"
" {wait} seconds while waiting for the aspect_id"
" {aspect_id} to be scaled").format(
wait=(self.STACK_RETRIES *
self.STACK_RETRY_WAIT),
aspect_id=aspect_id)
LOG.error("CNF Scale failed: %(reason)s",
{'reason': error_reason})
raise vnfm.CNFScaleWaitFailed(reason=error_reason)
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
finally:
self.clean_authenticate_vim(auth_cred, file_descriptor)
@ -1315,7 +1543,8 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
vnf_dict,
auth_attr,
region_name):
pass
return_id_list = []
return return_id_list
def get_scale_in_ids(self,
plugin,
@ -1325,7 +1554,11 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
auth_attr,
region_name,
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,
scale_vnf_request,
@ -1341,7 +1574,33 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
region_name,
scale_name_list,
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,
context,
@ -1358,7 +1617,30 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
auth_attr,
vnf_info,
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,
vnf_info):
@ -1380,4 +1662,7 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
aspect_id,
auth_attr,
region_name):
pass
return_id_list = []
return_name_list = []
return_grp_id = None
return return_id_list, return_name_list, return_grp_id