Support CNF heal operations based on ETSI NFV
Implements Container based VNF heal operation support with ETSI NFV-SOL002 and SOL003 v2.6.1 VNF Lifecycle Management. For "Heal VNFC with SOL002", users can heal Pod (mapped as VNFC) that is singleton or created using controller resources such as Kubernetes Deployment, DaemonSet, StatefulSet and ReplicaSet. For "Heal VNF instance with SOL003", users can heal entire VNF instance by termination and instantiation of the VNF. Other changes: * Store Pod information to vnfcResourceInfo after instantiation. * Update vnfcResourceInfo after scale operation. Implements: blueprint support-cnf-heal Spec: https://specs.openstack.org/openstack/tacker-specs/specs/wallaby/support-cnf-heal.html Change-Id: I62bf8d67eb9b3889f2ae69be9bb45363d083f48e
This commit is contained in:
parent
7e8a7351fa
commit
b05a83db76
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
Add Container based VNF heal operation support with ETSI NFV-SOL002 and
|
||||
SOL003 v2.6.1 VNF Lifecycle Management. For "Heal VNFC with SOL002", users
|
||||
can heal Pod (mapped as VNFC) that is singleton or created using controller
|
||||
resources such as Kubernetes Deployment, DaemonSet, StatefulSet, and
|
||||
ReplicaSet. For "Heal VNF instance with SOL003", users can heal entire VNF
|
||||
instance by termination and instantiation of the VNF. And the VNFC resource
|
||||
information are stored and updated for the heal operation of the Pod.
|
|
@ -737,7 +737,8 @@ class Conductor(manager.Manager):
|
|||
self.vnf_manager.invoke(vim_connection_info.vim_type,
|
||||
'post_vnf_instantiation', context=context,
|
||||
vnf_instance=vnf_instance,
|
||||
vim_connection_info=vim_connection_info)
|
||||
vim_connection_info=vim_connection_info,
|
||||
instantiate_vnf_req=instantiate_vnf_req)
|
||||
|
||||
except Exception as ex:
|
||||
try:
|
||||
|
|
|
@ -117,6 +117,14 @@ class CNFScaleWaitFailed(exceptions.TackerException):
|
|||
message = _('CNF Scale Wait Failed with reason: %(reason)s')
|
||||
|
||||
|
||||
class CNFHealFailed(exceptions.TackerException):
|
||||
message = _('%(reason)s')
|
||||
|
||||
|
||||
class CNFHealWaitFailed(exceptions.TackerException):
|
||||
message = _('%(reason)s')
|
||||
|
||||
|
||||
class ServiceTypeNotFound(exceptions.NotFound):
|
||||
message = _('service type %(service_type_id)s could not be found')
|
||||
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
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: complex
|
||||
requirements:
|
||||
virtual_link_external: []
|
||||
|
||||
node_templates:
|
||||
VNF:
|
||||
type: company.provider.VNF
|
||||
properties:
|
||||
flavour_description: A flavour for multiple resources
|
||||
|
||||
VDU1:
|
||||
type: tosca.nodes.nfv.Vdu.Compute
|
||||
properties:
|
||||
name: vdu1-heal-complex
|
||||
description: kubernetes resource as VDU1
|
||||
vdu_profile:
|
||||
min_number_of_instances: 2
|
||||
max_number_of_instances: 3
|
||||
|
||||
VDU2:
|
||||
type: tosca.nodes.nfv.Vdu.Compute
|
||||
properties:
|
||||
name: vdu2-heal
|
||||
description: kubernetes resource as VDU2
|
||||
vdu_profile:
|
||||
min_number_of_instances: 1
|
||||
max_number_of_instances: 1
|
||||
|
||||
policies:
|
||||
- scaling_aspects:
|
||||
type: tosca.policies.nfv.ScalingAspects
|
||||
properties:
|
||||
aspects:
|
||||
vdu1_aspect:
|
||||
name: vdu1_aspect
|
||||
description: vdu1 scaling aspect
|
||||
max_scale_level: 1
|
||||
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: 1
|
||||
default_level: instantiation_level_1
|
||||
|
||||
- vdu1_instantiation_levels:
|
||||
type: tosca.policies.nfv.VduInstantiationLevels
|
||||
properties:
|
||||
levels:
|
||||
instantiation_level_1:
|
||||
number_of_instances: 2
|
||||
instantiation_level_2:
|
||||
number_of_instances: 3
|
||||
targets: [ VDU1 ]
|
|
@ -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-heal-simple
|
||||
description: kubernetes controller resource as VDU
|
||||
vdu_profile:
|
||||
min_number_of_instances: 2
|
||||
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: 1
|
||||
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: 1
|
||||
default_level: instantiation_level_1
|
||||
|
||||
- vdu1_instantiation_levels:
|
||||
type: tosca.policies.nfv.VduInstantiationLevels
|
||||
properties:
|
||||
levels:
|
||||
instantiation_level_1:
|
||||
number_of_instances: 2
|
||||
instantiation_level_2:
|
||||
number_of_instances: 3
|
||||
targets: [ VDU1 ]
|
|
@ -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_complex.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
|
|
@ -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,complex ] ]
|
||||
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
|
|
@ -0,0 +1,22 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: vdu1-heal-complex
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: webserver
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: webserver
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 80
|
||||
protocol: TCP
|
|
@ -0,0 +1,22 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: vdu1-heal-simple
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: webserver
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: webserver
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 80
|
||||
protocol: TCP
|
|
@ -0,0 +1,12 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: vdu2-heal
|
||||
namespace: default
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: webserver2
|
||||
ports:
|
||||
- containerPort: 8080
|
|
@ -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/pod_heal.yaml
|
||||
Content-Type: application/yaml
|
||||
Algorithm: SHA-256
|
||||
Hash: 08fabdd52e8a386669f177c0a7a8a351b036bcde3bf399ca1816455d81dd191c
|
||||
|
||||
Name: Files/kubernetes/deployment_heal_simple.yaml
|
||||
Content-Type: application/yaml
|
||||
Algorithm: SHA-256
|
||||
Hash: 39c9b301d04714c6b124b333057a22d316835c3cb340c4e2ebfadc296c3fbfbc
|
||||
|
||||
Name: Files/kubernetes/deployment_heal_complex.yaml
|
||||
Content-Type: application/yaml
|
||||
Algorithm: SHA-256
|
||||
Hash: 06c018b9f4b231a604a6cd223a2552fecc4c6dc8bedf9325e84f7fe2b6fe8492
|
|
@ -0,0 +1,124 @@
|
|||
tosca_definitions_version: tosca_simple_yaml_1_2
|
||||
|
||||
description: Simple deployment flavour for 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: VDU1 compute node
|
||||
vdu_profile:
|
||||
min_number_of_instances: 1
|
||||
max_number_of_instances: 1
|
||||
|
||||
VDU2:
|
||||
type: tosca.nodes.nfv.Vdu.Compute
|
||||
properties:
|
||||
name: vdu2
|
||||
description: VDU2 compute node
|
||||
vdu_profile:
|
||||
min_number_of_instances: 1
|
||||
max_number_of_instances: 3
|
||||
|
||||
policies:
|
||||
- scaling_aspects:
|
||||
type: tosca.policies.nfv.ScalingAspects
|
||||
properties:
|
||||
aspects:
|
||||
vdu2_aspect:
|
||||
name: vdu2_aspect
|
||||
description: vdu2 scaling aspect
|
||||
max_scale_level: 2
|
||||
step_deltas:
|
||||
- delta_1
|
||||
|
||||
- VDU2_initial_delta:
|
||||
type: tosca.policies.nfv.VduInitialDelta
|
||||
properties:
|
||||
initial_delta:
|
||||
number_of_instances: 1
|
||||
targets: [ VDU2 ]
|
||||
|
||||
- VDU2_scaling_aspect_deltas:
|
||||
type: tosca.policies.nfv.VduScalingAspectDeltas
|
||||
properties:
|
||||
aspect: vdu2_aspect
|
||||
deltas:
|
||||
delta_1:
|
||||
number_of_instances: 1
|
||||
targets: [ VDU2 ]
|
||||
|
||||
- instantiation_levels:
|
||||
type: tosca.policies.nfv.InstantiationLevels
|
||||
properties:
|
||||
levels:
|
||||
instantiation_level_1:
|
||||
description: Smallest size
|
||||
scale_info:
|
||||
vdu2_aspect:
|
||||
scale_level: 0
|
||||
instantiation_level_2:
|
||||
description: Largest size
|
||||
scale_info:
|
||||
vdu2_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: 1
|
||||
targets: [ VDU1 ]
|
||||
|
||||
- VDU2_instantiation_levels:
|
||||
type: tosca.policies.nfv.VduInstantiationLevels
|
||||
properties:
|
||||
levels:
|
||||
instantiation_level_1:
|
||||
number_of_instances: 1
|
||||
instantiation_level_2:
|
||||
number_of_instances: 3
|
||||
targets: [ VDU2 ]
|
|
@ -1,7 +1,7 @@
|
|||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: nginx
|
||||
name: vdu1
|
||||
namespace: default
|
||||
spec:
|
||||
selector:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: curry-probe-test001
|
||||
name: vdu2
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 1
|
||||
|
|
|
@ -2,7 +2,7 @@ apiVersion: v1
|
|||
kind: Pod
|
||||
metadata:
|
||||
namespace: default
|
||||
name: curry-endpoint-test001
|
||||
name: vdu1
|
||||
spec:
|
||||
containers:
|
||||
- image: celebdor/kuryr-demo
|
||||
|
|
|
@ -28,7 +28,7 @@ metadata:
|
|||
apiVersion: apps/v1
|
||||
kind: ReplicaSet
|
||||
metadata:
|
||||
name: curry-replicaset-multiple
|
||||
name: vdu2
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 2
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: curry-ns-statefulset
|
||||
name: vdu2
|
||||
namespace: default
|
||||
spec:
|
||||
selector:
|
||||
|
|
|
@ -26,12 +26,12 @@ Hash: 4042352e0de6aa0ad28d44354bd8e0d62fc8e753c8f52b7edf69d2a7a25d8f8d
|
|||
Name: Files/kubernetes/daemon-set.yaml
|
||||
Content-Type: test-data
|
||||
Algorithm: SHA-256
|
||||
Hash: c0750df79c9ba2824b032b6a485764486b014021aa6dade5ef61f1c10569412f
|
||||
Hash: f8ed04536a8795af4828b2f731225abc34986f9ea30237d9652669ca57d9d217
|
||||
|
||||
Name: Files/kubernetes/deployment.yaml
|
||||
Content-Type: test-data
|
||||
Algorithm: SHA-256
|
||||
Hash: 6a40dfb06764394fb604ae807d1198bc2e2ee8aece3b9483dfde48e53f316a58
|
||||
Hash: 80f160c9bdd9daa6d0111c8d40b5575946b8c0f23696aa8d91d20f313adae087
|
||||
|
||||
Name: Files/kubernetes/horizontal-pod-autoscaler.yaml
|
||||
Content-Type: test-data
|
||||
|
@ -91,12 +91,12 @@ Hash: 5d4d3d399e04cdba1f9c691ac7e690e295ff02b7c935abae873b68a83a858c50
|
|||
Name: Files/kubernetes/pod.yaml
|
||||
Content-Type: test-data
|
||||
Algorithm: SHA-256
|
||||
Hash: a708dcf5ba4d3a7c675f18b71484a32b7e4446e80e57dcc3035b8a921c3f659d
|
||||
Hash: 6c97b1a8fc8d21a6a9e7ab1c383b49d3ec31f79a83de218f5537d18531ddfbd8
|
||||
|
||||
Name: Files/kubernetes/replicaset_service_secret.yaml
|
||||
Content-Type: test-data
|
||||
Algorithm: SHA-256
|
||||
Hash: 8ed52e5e167890efd7fba29c748f717dff01d68b60ff9a06af178cbafdfdc765
|
||||
Hash: 7d83ba61def65be3203b164b496057e4d062249804df82eba1831111cc4614a0
|
||||
|
||||
Name: Files/kubernetes/resource-quota.yaml
|
||||
Content-Type: test-data
|
||||
|
@ -116,7 +116,7 @@ Hash: 83bd9c40db8c798d0cab0e793a4b40a4ac7eca4fec4fba89ab4257d0f397db40
|
|||
Name: Files/kubernetes/statefulset.yaml
|
||||
Content-Type: test-data
|
||||
Algorithm: SHA-256
|
||||
Hash: d0beddd39f6808cb62094146778961b068871393df3474e0787145639a94f649
|
||||
Hash: 6829939e8b30a36c69d0e84c65b36701712c89bfbe827536cba8c0cdb15a816b
|
||||
|
||||
Name: Files/kubernetes/storage-class.yaml
|
||||
Content-Type: test-data
|
||||
|
|
|
@ -0,0 +1,403 @@
|
|||
# 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
|
||||
|
||||
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_HEAL_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
|
||||
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) > VNF_PACKAGE_UPLOAD_TIMEOUT):
|
||||
raise Exception("Failed to onboard vnf package, process could not"
|
||||
" be completed within %d seconds", VNF_PACKAGE_UPLOAD_TIMEOUT)
|
||||
|
||||
time.sleep(RETRY_WAIT_TIME)
|
||||
|
||||
# remove temporarily created CSAR file
|
||||
os.remove(file_path)
|
||||
return vnf_package['id'], vnfd_id
|
||||
|
||||
|
||||
def _delete_wait_vnf_instance(tacker_client, id):
|
||||
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) > VNF_TERMINATE_TIMEOUT):
|
||||
raise Exception("Failed to delete vnf instance, process could not"
|
||||
" be completed within %d seconds", VNF_TERMINATE_TIMEOUT)
|
||||
|
||||
time.sleep(RETRY_WAIT_TIME)
|
||||
|
||||
|
||||
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, process could not"
|
||||
" be completed within %d seconds", timeout)
|
||||
|
||||
time.sleep(RETRY_WAIT_TIME)
|
||||
|
||||
|
||||
class VnfLcmKubernetesHealTest(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_heal",
|
||||
{"key": "sample_heal_functional"})
|
||||
cls.vnf_instance_ids = []
|
||||
super(VnfLcmKubernetesHealTest, cls).setUpClass()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
# 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(VnfLcmKubernetesHealTest, cls).tearDownClass()
|
||||
|
||||
def setUp(self):
|
||||
super(VnfLcmKubernetesHealTest, 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_heal-%s" % \
|
||||
uuidutils.generate_uuid()
|
||||
vnf_instance_description = "vnf instance for cnf heal 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 _terminate_vnf_instance(self, id):
|
||||
# Terminate vnf forcefully
|
||||
request_body = {
|
||||
"terminationType": fields.VnfInstanceTerminationType.FORCEFUL,
|
||||
}
|
||||
url = os.path.join(self.base_vnf_instances_url, id, "terminate")
|
||||
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,
|
||||
instantiation_state=fields.VnfInstanceState.NOT_INSTANTIATED,
|
||||
timeout=VNF_TERMINATE_TIMEOUT)
|
||||
|
||||
def _delete_vnf_instance(self, id):
|
||||
_delete_wait_vnf_instance(self.tacker_client, id)
|
||||
|
||||
# verify vnf instance is deleted
|
||||
url = os.path.join(self.base_vnf_instances_url, id)
|
||||
resp, body = self.http_client.do_request(url, "GET")
|
||||
self.assertEqual(404, resp.status_code)
|
||||
|
||||
def _heal_vnf_instance(self, id, vnfc_instance_id):
|
||||
url = os.path.join(self.base_vnf_instances_url, id, "heal")
|
||||
# generate body
|
||||
request_body = {
|
||||
"vnfcInstanceId": vnfc_instance_id}
|
||||
resp, body = self.http_client.do_request(
|
||||
url, "POST", body=jsonutils.dumps(request_body))
|
||||
self.assertEqual(202, resp.status_code)
|
||||
|
||||
@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'):
|
||||
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) > VNF_HEAL_TIMEOUT):
|
||||
raise Exception("Failed to wait heal instance")
|
||||
|
||||
time.sleep(RETRY_WAIT_TIME)
|
||||
|
||||
def _get_vnfc_resource_info(self, vnf_instance):
|
||||
inst_vnf_info = vnf_instance['instantiatedVnfInfo']
|
||||
vnfc_resource_info = inst_vnf_info['vnfcResourceInfo']
|
||||
return vnfc_resource_info
|
||||
|
||||
def test_heal_cnf_with_sol002(self):
|
||||
"""Test heal as per SOL002 for CNF
|
||||
|
||||
This test will instantiate cnf. Heal API will be invoked as per SOL002
|
||||
i.e. with vnfcInstanceId, so that the specified vnfc instance is healed
|
||||
which includes Kubernetes resources (Pod and Deployment).
|
||||
"""
|
||||
# use def-files of singleton Pod and Deployment (replicas=2)
|
||||
inst_additional_param = {
|
||||
"lcm-kubernetes-def-files": [
|
||||
"Files/kubernetes/deployment_heal_complex.yaml",
|
||||
"Files/kubernetes/pod_heal.yaml"]}
|
||||
vnf_instance = self._create_and_instantiate_vnf_instance(
|
||||
"complex", inst_additional_param)
|
||||
before_vnfc_rscs = self._get_vnfc_resource_info(vnf_instance)
|
||||
|
||||
# get vnfc_instance_id of heal target
|
||||
deployment_target_vnfc = None
|
||||
for vnfc_rsc in before_vnfc_rscs:
|
||||
compute_resource = vnfc_rsc['computeResource']
|
||||
rsc_kind = compute_resource['vimLevelResourceType']
|
||||
if rsc_kind == 'Pod':
|
||||
# target 1: Singleton Pod
|
||||
pod_target_vnfc = vnfc_rsc
|
||||
elif not deployment_target_vnfc:
|
||||
# target 2: Deployment's Pod
|
||||
deployment_target_vnfc = vnfc_rsc
|
||||
else:
|
||||
# not target: Deployment's remianing one
|
||||
deployment_not_target_vnfc = vnfc_rsc
|
||||
|
||||
# test heal SOL-002 (partial heal)
|
||||
vnfc_instance_id = \
|
||||
[pod_target_vnfc['id'], deployment_target_vnfc['id']]
|
||||
self._heal_vnf_instance(vnf_instance['id'], vnfc_instance_id)
|
||||
# wait vnflcm_op_occs.operation_state become COMPLETE
|
||||
self._wait_vnflcm_op_occs(self.context, vnf_instance['id'])
|
||||
# check vnfcResourceInfo after heal operation
|
||||
vnf_instance = _show_vnf_instance(
|
||||
self.tacker_client, vnf_instance['id'])
|
||||
after_vnfc_rscs = self._get_vnfc_resource_info(vnf_instance)
|
||||
self.assertEqual(len(before_vnfc_rscs), len(after_vnfc_rscs))
|
||||
for vnfc_rsc in after_vnfc_rscs:
|
||||
after_pod_name = vnfc_rsc['computeResource']['resourceId']
|
||||
if vnfc_rsc['id'] == pod_target_vnfc['id']:
|
||||
# check stored pod name is not changed (Pod)
|
||||
after_resource = pod_target_vnfc
|
||||
compute_resource = after_resource['computeResource']
|
||||
before_pod_name = compute_resource['resourceId']
|
||||
self.assertEqual(after_pod_name, before_pod_name)
|
||||
elif vnfc_rsc['id'] == deployment_target_vnfc['id']:
|
||||
# check stored pod name is changed (Deployment)
|
||||
after_resource = deployment_target_vnfc
|
||||
compute_resource = after_resource['computeResource']
|
||||
before_pod_name = compute_resource['resourceId']
|
||||
self.assertNotEqual(after_pod_name, before_pod_name)
|
||||
else:
|
||||
# check stored pod name is not changed (not target)
|
||||
after_resource = deployment_not_target_vnfc
|
||||
compute_resource = after_resource['computeResource']
|
||||
before_pod_name = compute_resource['resourceId']
|
||||
self.assertEqual(after_pod_name, before_pod_name)
|
||||
self._terminate_vnf_instance(vnf_instance['id'])
|
||||
self._delete_vnf_instance(vnf_instance['id'])
|
||||
|
||||
def test_heal_cnf_with_sol003(self):
|
||||
"""Test heal as per SOL003 for CNF
|
||||
|
||||
This test will instantiate cnf. Heal API will be invoked as per SOL003
|
||||
i.e. without passing vnfcInstanceId, so that the entire vnf is healed
|
||||
which includes Kubernetes resource (Deployment).
|
||||
"""
|
||||
# use def-files of Deployment (replicas=2)
|
||||
inst_additional_param = {
|
||||
"lcm-kubernetes-def-files": [
|
||||
"Files/kubernetes/deployment_heal_simple.yaml"]}
|
||||
vnf_instance = self._create_and_instantiate_vnf_instance(
|
||||
"simple", inst_additional_param)
|
||||
before_vnfc_rscs = self._get_vnfc_resource_info(vnf_instance)
|
||||
|
||||
# test heal SOL-003 (entire heal)
|
||||
vnfc_instance_id = []
|
||||
self._heal_vnf_instance(vnf_instance['id'], vnfc_instance_id)
|
||||
# wait vnflcm_op_occs.operation_state become COMPLETE
|
||||
self._wait_vnflcm_op_occs(self.context, vnf_instance['id'])
|
||||
# check vnfcResourceInfo after heal operation
|
||||
vnf_instance = _show_vnf_instance(
|
||||
self.tacker_client, vnf_instance['id'])
|
||||
after_vnfc_rscs = self._get_vnfc_resource_info(vnf_instance)
|
||||
self.assertEqual(len(before_vnfc_rscs), len(after_vnfc_rscs))
|
||||
# check id and pod name (as computeResource.resourceId) is changed
|
||||
for before_vnfc_rsc in before_vnfc_rscs:
|
||||
for after_vnfc_rsc in after_vnfc_rscs:
|
||||
self.assertNotEqual(
|
||||
before_vnfc_rsc['id'], after_vnfc_rsc['id'])
|
||||
self.assertNotEqual(
|
||||
before_vnfc_rsc['computeResource']['resourceId'],
|
||||
after_vnfc_rsc['computeResource']['resourceId'])
|
||||
# terminate vnf instance
|
||||
self._terminate_vnf_instance(vnf_instance['id'])
|
||||
self._delete_vnf_instance(vnf_instance['id'])
|
|
@ -13,9 +13,14 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
|
||||
from kubernetes import client
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import uuidutils
|
||||
from tacker.db.db_sqlalchemy import models
|
||||
from tacker import objects
|
||||
from tacker.objects import vim_connection
|
||||
from tacker.tests import uuidsentinel
|
||||
|
||||
CREATE_K8S_FALSE_VALUE = None
|
||||
|
@ -485,6 +490,17 @@ def fake_v1_deployment():
|
|||
status=client.V1DeploymentStatus(
|
||||
replicas=1,
|
||||
ready_replicas=1
|
||||
),
|
||||
spec=client.V1DeploymentSpec(
|
||||
replicas=2,
|
||||
selector=client.V1LabelSelector(
|
||||
match_labels={'app': 'webserver'}
|
||||
),
|
||||
template=client.V1PodTemplateSpec(
|
||||
metadata=client.V1ObjectMeta(
|
||||
labels={'app': 'webserver'}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -515,6 +531,17 @@ def fake_v1_replica_set():
|
|||
status=client.V1ReplicaSetStatus(
|
||||
replicas=1,
|
||||
ready_replicas=1
|
||||
),
|
||||
spec=client.V1ReplicaSetSpec(
|
||||
replicas=2,
|
||||
selector=client.V1LabelSelector(
|
||||
match_labels={'app': 'webserver'}
|
||||
),
|
||||
template=client.V1PodTemplateSpec(
|
||||
metadata=client.V1ObjectMeta(
|
||||
labels={'app': 'webserver'}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -923,6 +950,16 @@ def fake_daemon_set():
|
|||
desired_number_scheduled=13,
|
||||
current_number_scheduled=4,
|
||||
number_misscheduled=2,
|
||||
),
|
||||
spec=client.V1DaemonSetSpec(
|
||||
selector=client.V1LabelSelector(
|
||||
match_labels={'app': 'webserver'}
|
||||
),
|
||||
template=client.V1PodTemplateSpec(
|
||||
metadata=client.V1ObjectMeta(
|
||||
labels={'app': 'webserver'}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -1015,13 +1052,65 @@ def get_vnf_resource_list(kind, name='fake_name'):
|
|||
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)
|
||||
def get_fake_pod_info(kind, name='fake_name', pod_status='Running',
|
||||
pod_name=None):
|
||||
if not pod_name:
|
||||
if kind == 'Deployment':
|
||||
pod_name = _('{name}-1234567890-abcde').format(name=name)
|
||||
elif kind == 'ReplicaSet' or kind == 'DaemonSet':
|
||||
pod_name = _('{name}-12345').format(name=name)
|
||||
elif kind == 'StatefulSet':
|
||||
pod_name = _('{name}-1').format(name=name)
|
||||
elif kind == 'Pod':
|
||||
pod_name = name
|
||||
return client.V1Pod(
|
||||
metadata=client.V1ObjectMeta(name=pod_name),
|
||||
metadata=client.V1ObjectMeta(name=pod_name,
|
||||
creation_timestamp=datetime.datetime.now().isoformat('T')),
|
||||
status=client.V1PodStatus(phase=pod_status))
|
||||
|
||||
|
||||
def fake_vnfc_resource_info(vdu_id='VDU1', rsc_kind='Deployment',
|
||||
rsc_name='fake_name', pod_name=None,
|
||||
namespace=None):
|
||||
def _get_metadata_str(name, namespace="fake_namespace"):
|
||||
if namespace == "brank":
|
||||
namespace = ""
|
||||
metadata = {
|
||||
'name': name,
|
||||
'namespace': namespace}
|
||||
return jsonutils.dumps(metadata)
|
||||
|
||||
vnfc_obj = objects.VnfcResourceInfo()
|
||||
vnfc_obj.id = uuidutils.generate_uuid()
|
||||
vnfc_obj.vdu_id = vdu_id
|
||||
if not pod_name:
|
||||
v1_pod = get_fake_pod_info(rsc_kind, rsc_name)
|
||||
pod_name = v1_pod.metadata.name
|
||||
compute_resource = objects.ResourceHandle(
|
||||
resource_id=pod_name,
|
||||
vim_level_resource_type=rsc_kind)
|
||||
vnfc_obj.compute_resource = compute_resource
|
||||
metadata = {}
|
||||
if namespace:
|
||||
metadata['Pod'] = _get_metadata_str(
|
||||
name=pod_name, namespace=namespace)
|
||||
if rsc_kind != 'Pod':
|
||||
metadata[rsc_kind] = _get_metadata_str(
|
||||
name=rsc_name, namespace=namespace)
|
||||
else:
|
||||
metadata['Pod'] = _get_metadata_str(name=pod_name)
|
||||
if rsc_kind != 'Pod':
|
||||
metadata[rsc_kind] = _get_metadata_str(name=rsc_name)
|
||||
vnfc_obj.metadata = metadata
|
||||
|
||||
return vnfc_obj
|
||||
|
||||
|
||||
def fake_vim_connection_info():
|
||||
access_info = {
|
||||
'auth_url': 'http://fake_url:6443',
|
||||
'ssl_ca_cert': None}
|
||||
|
||||
return vim_connection.VimConnectionInfo(
|
||||
vim_type="kubernetes",
|
||||
access_info=access_info)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1382,10 +1382,11 @@ class TestOpenStack(base.FixturedTestCase):
|
|||
vnf_link_ports[0].resource_handle.vim_level_resource_type,
|
||||
'physical_resource_id': uuidsentinel.cp1_resource_id}]
|
||||
|
||||
inst_req_info = fd_utils.get_instantiate_vnf_request()
|
||||
self._responses_in_stack_list(inst_vnf_info.instance_id,
|
||||
resources=resources)
|
||||
self.openstack.post_vnf_instantiation(
|
||||
self.context, vnf_instance, vim_connection_info)
|
||||
self.context, vnf_instance, vim_connection_info, inst_req_info)
|
||||
self.assertEqual(vnf_instance.instantiated_vnf_info.
|
||||
vnfc_resource_info[0].metadata['stack_id'],
|
||||
inst_vnf_info.instance_id)
|
||||
|
@ -1453,8 +1454,9 @@ class TestOpenStack(base.FixturedTestCase):
|
|||
'physical_resource_id': uuidsentinel.v_l_resource_info_id}]
|
||||
self._responses_in_stack_list(inst_vnf_info.instance_id,
|
||||
resources=resources)
|
||||
inst_req_info = fd_utils.get_instantiate_vnf_request()
|
||||
self.openstack.post_vnf_instantiation(
|
||||
self.context, vnf_instance, vim_connection_info)
|
||||
self.context, vnf_instance, vim_connection_info, inst_req_info)
|
||||
self.assertEqual(vnf_instance.instantiated_vnf_info.
|
||||
vnfc_resource_info[0].metadata['stack_id'],
|
||||
inst_vnf_info.instance_id)
|
||||
|
@ -1623,7 +1625,7 @@ class TestOpenStack(base.FixturedTestCase):
|
|||
"UPDATE_COMPLETE"])
|
||||
|
||||
stack = self.openstack.heal_vnf_wait(
|
||||
self.context, vnf_instance, vim_connection_info)
|
||||
self.context, vnf_instance, vim_connection_info, None)
|
||||
self.assertEqual('UPDATE_COMPLETE', stack.stack_status)
|
||||
|
||||
def test_heal_vnf_wait_fail(self):
|
||||
|
@ -1640,7 +1642,7 @@ class TestOpenStack(base.FixturedTestCase):
|
|||
self.openstack.STACK_RETRIES = 1
|
||||
result = self.assertRaises(vnfm.VNFHealWaitFailed,
|
||||
self.openstack.heal_vnf_wait, self.context, vnf_instance,
|
||||
vim_connection_info)
|
||||
vim_connection_info, None)
|
||||
|
||||
expected_msg = ("VNF Heal action is not completed within 10 seconds "
|
||||
"on stack %s") % inst_vnf_info.instance_id
|
||||
|
|
|
@ -611,7 +611,8 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
|
|||
self._vnf_manager.invoke(
|
||||
vim_connection_info.vim_type, 'heal_vnf_wait',
|
||||
context=context, vnf_instance=vnf_instance,
|
||||
vim_connection_info=vim_connection_info)
|
||||
vim_connection_info=vim_connection_info,
|
||||
heal_vnf_request=heal_vnf_request)
|
||||
except Exception as exp:
|
||||
LOG.error("Failed to update vnf %(id)s resources for instance "
|
||||
"%(instance)s. Error: %(error)s",
|
||||
|
@ -675,7 +676,8 @@ class VnfLcmDriver(abstract_driver.VnfInstanceAbstractDriver):
|
|||
self._vnf_manager.invoke(
|
||||
vim_connection_info.vim_type, 'post_vnf_instantiation',
|
||||
context=context, vnf_instance=vnf_instance,
|
||||
vim_connection_info=vim_connection_info)
|
||||
vim_connection_info=vim_connection_info,
|
||||
instantiate_vnf_req=instantiate_vnf_request)
|
||||
|
||||
except Exception as exc:
|
||||
with excutils.save_and_reraise_exception() as exc_ctxt:
|
||||
|
|
|
@ -97,7 +97,7 @@ class VnfAbstractDriver(extensions.PluginInterface, metaclass=abc.ABCMeta):
|
|||
|
||||
@abc.abstractmethod
|
||||
def post_vnf_instantiation(self, context, vnf_instance,
|
||||
vim_connection_info):
|
||||
vim_connection_info, instantiate_vnf_req):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
|
@ -114,7 +114,8 @@ class VnfAbstractDriver(extensions.PluginInterface, metaclass=abc.ABCMeta):
|
|||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def heal_vnf_wait(self, context, vnf_instance, vim_connection_info):
|
||||
def heal_vnf_wait(self, context, vnf_instance, vim_connection_info,
|
||||
heal_vnf_request):
|
||||
"""Check vnf is healed successfully"""
|
||||
pass
|
||||
|
||||
|
|
|
@ -486,6 +486,12 @@ class Transformer(object):
|
|||
|
||||
return sorted_k8s_objs
|
||||
|
||||
def get_object_meta(self, content):
|
||||
must_param = {}
|
||||
v1_object_meta = client.V1ObjectMeta()
|
||||
self._init_k8s_obj(v1_object_meta, content, must_param)
|
||||
return v1_object_meta
|
||||
|
||||
# config_labels configures label
|
||||
def config_labels(self, deployment_name=None, scaling_name=None):
|
||||
label = dict()
|
||||
|
|
|
@ -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 oslo_utils import uuidutils
|
||||
from toscaparser import tosca_template
|
||||
|
||||
from tacker._i18n import _
|
||||
|
@ -46,6 +47,7 @@ from urllib.parse import urlparse
|
|||
CNF_TARGET_FILES_KEY = 'lcm-kubernetes-def-files'
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
VNFC_POD_NOT_FOUND = "POD_NOT_FOUND"
|
||||
|
||||
OPTS = [
|
||||
cfg.IntOpt('stack_retries',
|
||||
|
@ -1247,12 +1249,16 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
|
|||
|
||||
def _is_match_pod_naming_rule(self, rsc_kind, rsc_name, pod_name):
|
||||
match_result = None
|
||||
if rsc_kind == 'Deployment':
|
||||
if rsc_kind == 'Pod':
|
||||
# Expected example: name
|
||||
if rsc_name == pod_name:
|
||||
match_result = True
|
||||
elif 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':
|
||||
elif rsc_kind == 'ReplicaSet' or rsc_kind == 'DaemonSet':
|
||||
# Expected example: name-019az
|
||||
match_result = re.match(
|
||||
rsc_name + '-([0-9a-z]{5})+$',
|
||||
|
@ -1523,19 +1529,501 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
|
|||
return resource_info_str
|
||||
|
||||
def post_vnf_instantiation(self, context, vnf_instance,
|
||||
vim_connection_info):
|
||||
pass
|
||||
vim_connection_info, instantiate_vnf_req):
|
||||
"""Initially store VnfcResourceInfo after instantiation
|
||||
|
||||
After instantiation, this function gets pods information from
|
||||
Kubernetes VIM and store information such as pod name and resource kind
|
||||
and metadata, and vdu id.
|
||||
"""
|
||||
auth_attr = vim_connection_info.access_info
|
||||
auth_cred, file_descriptor = self._get_auth_creds(auth_attr)
|
||||
try:
|
||||
# get Kubernetes object files
|
||||
target_k8s_files = self._get_target_k8s_files(instantiate_vnf_req)
|
||||
vnf_package_path = vnflcm_utils._get_vnf_package_path(
|
||||
context, vnf_instance.vnfd_id)
|
||||
# initialize Transformer
|
||||
transformer = translate_outputs.Transformer(
|
||||
None, None, None, None)
|
||||
# get Kubernetes object
|
||||
k8s_objs = transformer.get_k8s_objs_from_yaml(
|
||||
target_k8s_files, vnf_package_path)
|
||||
# get TOSCA node templates
|
||||
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_node_tpls = tosca.topology_template.nodetemplates
|
||||
# get vdu_ids dict {vdu_name(as pod_name): vdu_id}
|
||||
vdu_ids = {}
|
||||
for node_tpl in tosca_node_tpls:
|
||||
for node_name, node_value in node_tpl.templates.items():
|
||||
if node_value.get('type') == "tosca.nodes.nfv.Vdu.Compute":
|
||||
vdu_id = node_name
|
||||
vdu_name = node_value.get('properties').get('name')
|
||||
vdu_ids[vdu_name] = vdu_id
|
||||
# initialize Kubernetes APIs
|
||||
core_v1_api_client = self.kubernetes.get_core_v1_api_client(
|
||||
auth=auth_cred)
|
||||
target_kinds = ["Pod", "Deployment", "DaemonSet", "StatefulSet",
|
||||
"ReplicaSet"]
|
||||
pod_list_dict = {}
|
||||
vnfc_resource_list = []
|
||||
for k8s_obj in k8s_objs:
|
||||
rsc_kind = k8s_obj.get('object').kind
|
||||
if rsc_kind not in target_kinds:
|
||||
# Skip if rsc_kind is not target kind
|
||||
continue
|
||||
rsc_name = k8s_obj.get('object').metadata.name
|
||||
namespace = k8s_obj.get('object').metadata.namespace
|
||||
if not namespace:
|
||||
namespace = "default"
|
||||
# get V1PodList by namespace
|
||||
if namespace in pod_list_dict.keys():
|
||||
pod_list = pod_list_dict.get(namespace)
|
||||
else:
|
||||
pod_list = core_v1_api_client.list_namespaced_pod(
|
||||
namespace=namespace)
|
||||
pod_list_dict[namespace] = pod_list
|
||||
# get initially store VnfcResourceInfo after instantiation
|
||||
for pod in pod_list.items:
|
||||
pod_name = pod.metadata.name
|
||||
match_result = self._is_match_pod_naming_rule(
|
||||
rsc_kind, rsc_name, pod_name)
|
||||
if match_result:
|
||||
# get metadata
|
||||
metadata = {}
|
||||
metadata[rsc_kind] = jsonutils.dumps(
|
||||
k8s_obj.get('object').metadata.to_dict())
|
||||
if rsc_kind != 'Pod':
|
||||
metadata['Pod'] = jsonutils.dumps(
|
||||
k8s_obj.get('object').spec.template.metadata.
|
||||
to_dict())
|
||||
# generate VnfcResourceInfo
|
||||
vnfc_resource = objects.VnfcResourceInfo()
|
||||
vnfc_resource.id = uuidutils.generate_uuid()
|
||||
vnfc_resource.vdu_id = vdu_ids.get(rsc_name)
|
||||
resource = objects.ResourceHandle()
|
||||
resource.resource_id = pod_name
|
||||
resource.vim_level_resource_type = rsc_kind
|
||||
vnfc_resource.compute_resource = resource
|
||||
vnfc_resource.metadata = metadata
|
||||
vnfc_resource_list.append(vnfc_resource)
|
||||
|
||||
if vnfc_resource_list:
|
||||
inst_vnf_info = vnf_instance.instantiated_vnf_info
|
||||
inst_vnf_info.vnfc_resource_info = vnfc_resource_list
|
||||
except Exception as e:
|
||||
LOG.error('Update vnfc resource info got an error due to %s', e)
|
||||
raise
|
||||
finally:
|
||||
self.clean_authenticate_vim(auth_cred, file_descriptor)
|
||||
|
||||
def _get_vnfc_rscs_with_vnfc_id(self, inst_vnf_info, heal_vnf_request):
|
||||
if not heal_vnf_request.vnfc_instance_id:
|
||||
# include all vnfc resources
|
||||
return [resource for resource in inst_vnf_info.vnfc_resource_info]
|
||||
|
||||
vnfc_resources = []
|
||||
for vnfc_resource in inst_vnf_info.vnfc_resource_info:
|
||||
if vnfc_resource.id in heal_vnf_request.vnfc_instance_id:
|
||||
vnfc_resources.append(vnfc_resource)
|
||||
return vnfc_resources
|
||||
|
||||
def _get_added_pod_names(self, core_v1_api_client, inst_vnf_info, vdu_id,
|
||||
vnfc_resource, pod_list_dict):
|
||||
compute_resource = vnfc_resource.compute_resource
|
||||
rsc_kind = compute_resource.vim_level_resource_type
|
||||
rsc_metadata = jsonutils.loads(
|
||||
vnfc_resource.metadata.get(rsc_kind))
|
||||
namespace = rsc_metadata.get('namespace')
|
||||
if not namespace:
|
||||
namespace = "default"
|
||||
rsc_name = rsc_metadata.get('name')
|
||||
# Get pod list from kubernetes
|
||||
if namespace in pod_list_dict.keys():
|
||||
pod_list = pod_list_dict.get(namespace)
|
||||
else:
|
||||
pod_list = core_v1_api_client.list_namespaced_pod(
|
||||
namespace=namespace)
|
||||
pod_list_dict[namespace] = pod_list
|
||||
# Sort by newest creation_timestamp
|
||||
sorted_pod_list = sorted(pod_list.items, key=lambda x:
|
||||
x.metadata.creation_timestamp, reverse=True)
|
||||
# Get the associated pod name that runs with the actual kubernetes
|
||||
actual_pod_names = list()
|
||||
for pod in sorted_pod_list:
|
||||
match_result = self._is_match_pod_naming_rule(
|
||||
rsc_kind, rsc_name, pod.metadata.name)
|
||||
if match_result:
|
||||
actual_pod_names.append(pod.metadata.name)
|
||||
# Get the associated pod name stored in vnfcResourceInfo
|
||||
stored_pod_names = []
|
||||
for vnfc_rsc_info in inst_vnf_info.vnfc_resource_info:
|
||||
if vnfc_rsc_info.vdu_id == vnfc_resource.vdu_id:
|
||||
stored_pod_names.append(
|
||||
vnfc_rsc_info.compute_resource.resource_id)
|
||||
# Get the added pod name that does not exist in vnfcResourceInfo
|
||||
added_pod_names = [
|
||||
actl_pn for actl_pn in actual_pod_names
|
||||
if actl_pn not in stored_pod_names
|
||||
]
|
||||
return actual_pod_names, added_pod_names
|
||||
|
||||
def heal_vnf(self, context, vnf_instance, vim_connection_info,
|
||||
heal_vnf_request):
|
||||
raise NotImplementedError()
|
||||
"""Heal function
|
||||
|
||||
def heal_vnf_wait(self, context, vnf_instance, vim_connection_info):
|
||||
raise NotImplementedError()
|
||||
This function heals vnfc instances (mapped as Pod),
|
||||
and update vnfcResourceInfo which are not the target of healing
|
||||
before healing operation.
|
||||
|
||||
"""
|
||||
# initialize Kubernetes APIs
|
||||
auth_attr = vim_connection_info.access_info
|
||||
auth_cred, file_descriptor = self._get_auth_creds(auth_attr)
|
||||
inst_vnf_info = vnf_instance.instantiated_vnf_info
|
||||
try:
|
||||
core_v1_api_client = self.kubernetes.get_core_v1_api_client(
|
||||
auth=auth_cred)
|
||||
# get vnfc_resource_info list for healing
|
||||
vnfc_resources = self._get_vnfc_rscs_with_vnfc_id(
|
||||
inst_vnf_info=inst_vnf_info,
|
||||
heal_vnf_request=heal_vnf_request
|
||||
)
|
||||
# Updates resource_id in vnfc_resource_info which are not the
|
||||
# target of healing before heal operation because they may have
|
||||
# been re-created by kubelet of Kubernetes automatically and their
|
||||
# resource_id (as Pod name) have been already changed
|
||||
updated_vdu_ids = []
|
||||
pod_list_dict = {}
|
||||
for vnfc_resource in vnfc_resources:
|
||||
vdu_id = vnfc_resource.vdu_id
|
||||
if vdu_id in updated_vdu_ids:
|
||||
# For updated vdu_id, go to the next Loop
|
||||
continue
|
||||
actual_pod_names, added_pod_names = self._get_added_pod_names(
|
||||
core_v1_api_client, inst_vnf_info, vdu_id, vnfc_resource,
|
||||
pod_list_dict)
|
||||
|
||||
if added_pod_names:
|
||||
heal_target_ids = heal_vnf_request.vnfc_instance_id
|
||||
for vnfc_rsc in inst_vnf_info.vnfc_resource_info:
|
||||
stored_pod_name = vnfc_rsc.compute_resource.resource_id
|
||||
# Updated vnfcResourceInfo of the same vdu_id other
|
||||
# than heal target
|
||||
if (vnfc_rsc.id not in heal_target_ids) and\
|
||||
(vdu_id == vnfc_rsc.vdu_id) and\
|
||||
(stored_pod_name not in actual_pod_names):
|
||||
pod_name = added_pod_names.pop()
|
||||
vnfc_rsc.compute_resource.resource_id = pod_name
|
||||
LOG.warning("Update resource_id before healing,"
|
||||
" vnfc_resource_info.id:%(vnfc_id)s,"
|
||||
" pod_name:%(pod_name)s",
|
||||
{'vnfc_id': vnfc_rsc.id,
|
||||
'pod_name': pod_name})
|
||||
if not added_pod_names:
|
||||
break
|
||||
updated_vdu_ids.append(vdu_id)
|
||||
|
||||
for vnfc_resource in vnfc_resources:
|
||||
body = client.V1DeleteOptions(propagation_policy='Foreground')
|
||||
compute_resource = vnfc_resource.compute_resource
|
||||
rsc_kind = compute_resource.vim_level_resource_type
|
||||
pod_name = compute_resource.resource_id
|
||||
rsc_metadata = jsonutils.loads(
|
||||
vnfc_resource.metadata.get(rsc_kind))
|
||||
namespace = rsc_metadata.get('namespace')
|
||||
if not namespace:
|
||||
namespace = "default"
|
||||
|
||||
if rsc_kind == 'Pod':
|
||||
rsc_name = rsc_metadata.get('name')
|
||||
# Get pod information for re-creation before deletion
|
||||
pod_info = core_v1_api_client.read_namespaced_pod(
|
||||
namespace=namespace,
|
||||
name=rsc_name
|
||||
)
|
||||
# Delete Pod
|
||||
core_v1_api_client.delete_namespaced_pod(
|
||||
namespace=namespace,
|
||||
name=pod_name,
|
||||
body=body
|
||||
)
|
||||
# Check and wait that the Pod is deleted
|
||||
stack_retries = self.STACK_RETRIES
|
||||
for cnt in range(self.STACK_RETRIES):
|
||||
try:
|
||||
core_v1_api_client.read_namespaced_pod(
|
||||
namespace=namespace,
|
||||
name=pod_name
|
||||
)
|
||||
except Exception as e:
|
||||
if e.status == 404:
|
||||
break
|
||||
else:
|
||||
error_reason = _("Failed the request to read a"
|
||||
" Pod information. namespace: {namespace},"
|
||||
" pod_name: {name}, kind: {kind}, Reason: "
|
||||
"{exception}").format(
|
||||
namespace=namespace, name=pod_name,
|
||||
kind=rsc_kind, exception=e)
|
||||
raise vnfm.CNFHealFailed(reason=error_reason)
|
||||
stack_retries = stack_retries - 1
|
||||
time.sleep(self.STACK_RETRY_WAIT)
|
||||
|
||||
# Number of retries exceeded retry count
|
||||
if stack_retries == 0:
|
||||
error_reason = _("Resource healing is not completed"
|
||||
"within {wait} seconds").format(wait=(
|
||||
self.STACK_RETRIES * self.STACK_RETRY_WAIT))
|
||||
LOG.error("CNF Healing failed: %(reason)s",
|
||||
{'reason': error_reason})
|
||||
raise vnfm.CNFHealFailed(reason=error_reason)
|
||||
|
||||
# Recreate pod using retained pod_info
|
||||
transformer = translate_outputs.Transformer(
|
||||
None, None, None, None)
|
||||
metadata = transformer.get_object_meta(rsc_metadata)
|
||||
body = client.V1Pod(metadata=metadata, spec=pod_info.spec)
|
||||
core_v1_api_client.create_namespaced_pod(
|
||||
namespace=namespace,
|
||||
body=body
|
||||
)
|
||||
elif (rsc_kind in ['Deployment', 'DaemonSet', 'StatefulSet',
|
||||
'ReplicaSet']):
|
||||
try:
|
||||
# Delete Pod (Pod is automatically re-created)
|
||||
core_v1_api_client.delete_namespaced_pod(
|
||||
namespace=namespace,
|
||||
name=pod_name,
|
||||
body=body
|
||||
)
|
||||
except Exception as e:
|
||||
if e.status == 404:
|
||||
# If when the pod to be deleted does not exist,
|
||||
# change resource_id to "POD_NOT_FOUND"
|
||||
compute_resource = vnfc_resource.compute_resource
|
||||
compute_resource.resource_id = VNFC_POD_NOT_FOUND
|
||||
LOG.warning("Target pod to delete is not found,"
|
||||
" vnfc_resource_info.id:%(vnfc_id)s,"
|
||||
" pod_name:%(pod_name)s",
|
||||
{'vnfc_id': vnfc_resource.id,
|
||||
'pod_name': pod_name})
|
||||
else:
|
||||
error_reason = _("Failed the request to delete a "
|
||||
"Pod. namespace: {namespace}, pod_name: {name}"
|
||||
", kind: {kind}, Reason: {exception}").format(
|
||||
namespace=namespace, name=pod_name,
|
||||
kind=rsc_kind, exception=e)
|
||||
raise vnfm.CNFHealFailed(reason=error_reason)
|
||||
else:
|
||||
error_reason = _(
|
||||
"{vnfc_instance_id} is a kind of Kubertnetes"
|
||||
" resource that is not covered").format(
|
||||
vnfc_instance_id=vnfc_resource.id)
|
||||
LOG.error("CNF Heal failed: %(reason)s",
|
||||
{'reason': error_reason})
|
||||
raise vnfm.CNFHealFailed(reason=error_reason)
|
||||
except Exception as e:
|
||||
LOG.error('Healing CNF got an error due to %s', e)
|
||||
raise
|
||||
finally:
|
||||
self.clean_authenticate_vim(auth_cred, file_descriptor)
|
||||
|
||||
def heal_vnf_wait(self, context, vnf_instance,
|
||||
vim_connection_info, heal_vnf_request):
|
||||
"""heal wait function
|
||||
|
||||
Wait until all status from Pod objects is RUNNING.
|
||||
"""
|
||||
# initialize Kubernetes APIs
|
||||
auth_attr = vim_connection_info.access_info
|
||||
auth_cred, file_descriptor = self._get_auth_creds(auth_attr)
|
||||
try:
|
||||
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)
|
||||
vnfc_resources = self._get_vnfc_rscs_with_vnfc_id(
|
||||
inst_vnf_info=vnf_instance.instantiated_vnf_info,
|
||||
heal_vnf_request=heal_vnf_request)
|
||||
# Exclude entries where pods were not found when heal
|
||||
vnfc_resources = [rsc for rsc in vnfc_resources
|
||||
if rsc.compute_resource.
|
||||
resource_id != VNFC_POD_NOT_FOUND]
|
||||
|
||||
if not vnfc_resources:
|
||||
# If heal is not running, wait is no need
|
||||
return
|
||||
|
||||
# Get kubernetes resource information from target vnfcResourceInfo
|
||||
k8s_resources = list()
|
||||
for vnfc_resource in vnfc_resources:
|
||||
info = {}
|
||||
compute_resource = vnfc_resource.compute_resource
|
||||
info['kind'] = compute_resource.vim_level_resource_type
|
||||
rsc_metadata = jsonutils.loads(
|
||||
vnfc_resource.metadata.get(info['kind']))
|
||||
info['name'] = rsc_metadata.get('name')
|
||||
info['namespace'] = rsc_metadata.get('namespace')
|
||||
k8s_resources.append(info)
|
||||
# exclude duplicate entries
|
||||
k8s_resources = list(map(jsonutils.loads,
|
||||
set(map(jsonutils.dumps, k8s_resources))))
|
||||
# get replicas of scalable resources for checking number of pod
|
||||
scalable_kinds = ["Deployment", "ReplicaSet", "StatefulSet"]
|
||||
for k8s_resource in k8s_resources:
|
||||
if k8s_resource.get('kind') in scalable_kinds:
|
||||
scale_info = self._call_read_scale_api(
|
||||
app_v1_api_client=app_v1_api_client,
|
||||
namespace=k8s_resource.get('namespace'),
|
||||
name=k8s_resource.get('name'),
|
||||
kind=k8s_resource.get('kind'))
|
||||
k8s_resource['replicas'] = scale_info.spec.replicas
|
||||
stack_retries = self.STACK_RETRIES
|
||||
status = 'Pending'
|
||||
while status == 'Pending' and stack_retries > 0:
|
||||
pods_information = []
|
||||
pod_list_dict = {}
|
||||
is_unmatch_pods_num = False
|
||||
# Get related pod information and check status
|
||||
for k8s_resource in k8s_resources:
|
||||
namespace = k8s_resource.get('namespace')
|
||||
if namespace in pod_list_dict.keys():
|
||||
pod_list = pod_list_dict.get(namespace)
|
||||
else:
|
||||
pod_list = core_v1_api_client.list_namespaced_pod(
|
||||
namespace=k8s_resource.get('namespace'))
|
||||
pod_list_dict[namespace] = pod_list
|
||||
tmp_pods_info = list()
|
||||
for pod in pod_list.items:
|
||||
match_result = self._is_match_pod_naming_rule(
|
||||
k8s_resource.get('kind'),
|
||||
k8s_resource.get('name'),
|
||||
pod.metadata.name)
|
||||
if match_result:
|
||||
tmp_pods_info.append(pod)
|
||||
# NOTE(ueha): The status of pod being deleted is retrieved
|
||||
# as "Running", which cause incorrect information to be
|
||||
# stored in vnfcResouceInfo. Therefore, for the scalable
|
||||
# kinds, by comparing the actual number of pods with the
|
||||
# replicas, it can wait until the pod deletion is complete
|
||||
# and store correct information to vnfcResourceInfo.
|
||||
if k8s_resource.get('kind') in scalable_kinds and \
|
||||
k8s_resource.get('replicas') != len(tmp_pods_info):
|
||||
LOG.warning("Unmatch number of pod. (kind: %(kind)s,"
|
||||
" name: %(name)s, replicas: %(replicas)s,"
|
||||
" actual_pod_num: %(actual_pod_num)s)", {
|
||||
'kind': k8s_resource.get('kind'),
|
||||
'name': k8s_resource.get('name'),
|
||||
'replicas': str(k8s_resource.get('replicas')),
|
||||
'actual_pod_num': str(len(tmp_pods_info))})
|
||||
is_unmatch_pods_num = True
|
||||
pods_information.extend(tmp_pods_info)
|
||||
status = self._get_pod_status(pods_information)
|
||||
|
||||
if status == 'Unknown':
|
||||
error_reason = _("Pod status is found Unknown")
|
||||
LOG.warning("CNF Healing failed: %(reason)s",
|
||||
{'reason': error_reason})
|
||||
raise vnfm.CNFHealWaitFailed(reason=error_reason)
|
||||
elif status == 'Pending' or is_unmatch_pods_num:
|
||||
time.sleep(self.STACK_RETRY_WAIT)
|
||||
stack_retries = stack_retries - 1
|
||||
status = 'Pending'
|
||||
|
||||
if stack_retries == 0 and status != 'Running':
|
||||
error_reason = _("Resource healing is not completed within"
|
||||
" {wait} seconds").format(
|
||||
wait=(self.STACK_RETRIES *
|
||||
self.STACK_RETRY_WAIT))
|
||||
LOG.error("CNF Healing failed: %(reason)s",
|
||||
{'reason': error_reason})
|
||||
raise vnfm.CNFHealWaitFailed(reason=error_reason)
|
||||
except Exception as e:
|
||||
LOG.error('Healing wait CNF got an error due to %s', e)
|
||||
raise
|
||||
finally:
|
||||
self.clean_authenticate_vim(auth_cred, file_descriptor)
|
||||
|
||||
def post_heal_vnf(self, context, vnf_instance, vim_connection_info,
|
||||
heal_vnf_request):
|
||||
raise NotImplementedError()
|
||||
"""Update VnfcResourceInfo after healing"""
|
||||
# initialize Kubernetes APIs
|
||||
auth_attr = vim_connection_info.access_info
|
||||
auth_cred, file_descriptor = self._get_auth_creds(auth_attr)
|
||||
inst_vnf_info = vnf_instance.instantiated_vnf_info
|
||||
try:
|
||||
core_v1_api_client = self.kubernetes.get_core_v1_api_client(
|
||||
auth=auth_cred)
|
||||
vnfc_resources = self._get_vnfc_rscs_with_vnfc_id(
|
||||
inst_vnf_info=inst_vnf_info,
|
||||
heal_vnf_request=heal_vnf_request
|
||||
)
|
||||
# initialize
|
||||
updated_vdu_ids = []
|
||||
pod_list_dict = {}
|
||||
for vnfc_resource in vnfc_resources:
|
||||
vdu_id = vnfc_resource.vdu_id
|
||||
if vdu_id in updated_vdu_ids:
|
||||
# For updated vdu_id, go to the next Loop
|
||||
continue
|
||||
compute_resource = vnfc_resource.compute_resource
|
||||
rsc_kind = compute_resource.vim_level_resource_type
|
||||
pod_name = compute_resource.resource_id
|
||||
|
||||
if rsc_kind == 'Pod' or rsc_kind == 'StatefulSet':
|
||||
# No update required as the pod name does not change
|
||||
continue
|
||||
|
||||
# Update vnfcResourceInfo when other rsc_kind
|
||||
# (Deployment, DaemonSet, ReplicaSet)
|
||||
actual_pod_names, added_pod_names = self._get_added_pod_names(
|
||||
core_v1_api_client, inst_vnf_info, vdu_id, vnfc_resource,
|
||||
pod_list_dict)
|
||||
|
||||
updated_vnfc_ids = []
|
||||
# Update entries that pod was not found when heal_vnf method
|
||||
if added_pod_names:
|
||||
for vnfc_rsc in vnfc_resources:
|
||||
rsc_id = vnfc_rsc.compute_resource.resource_id
|
||||
if vdu_id == vnfc_rsc.vdu_id and \
|
||||
rsc_id == VNFC_POD_NOT_FOUND:
|
||||
pod_name = added_pod_names.pop()
|
||||
vnfc_rsc.compute_resource.resource_id = pod_name
|
||||
LOG.warning("Update resource_id of the"
|
||||
" entry where the pod was not found,"
|
||||
" vnfc_resource_info.id:%(vnfc_id)s,"
|
||||
" new podname:%(pod_name)s",
|
||||
{'vnfc_id': vnfc_rsc.id,
|
||||
'pod_name': pod_name})
|
||||
updated_vnfc_ids.append(vnfc_rsc.id)
|
||||
if not added_pod_names:
|
||||
break
|
||||
# Update entries that was healed successful
|
||||
if added_pod_names:
|
||||
for vnfc_rsc_id in heal_vnf_request.vnfc_instance_id:
|
||||
if vnfc_rsc_id in updated_vnfc_ids:
|
||||
# If the entry has already been updated,
|
||||
# go to the next loop
|
||||
continue
|
||||
for vnfc_rsc in vnfc_resources:
|
||||
if vdu_id == vnfc_rsc.vdu_id and \
|
||||
vnfc_rsc_id == vnfc_rsc.id:
|
||||
pod_name = added_pod_names.pop()
|
||||
compute_resource = vnfc_rsc.compute_resource
|
||||
compute_resource.resource_id = pod_name
|
||||
if not added_pod_names:
|
||||
break
|
||||
updated_vdu_ids.append(vdu_id)
|
||||
except Exception as e:
|
||||
LOG.error('Post healing CNF got an error due to %s', e)
|
||||
raise
|
||||
finally:
|
||||
self.clean_authenticate_vim(auth_cred, file_descriptor)
|
||||
|
||||
def get_scale_ids(self,
|
||||
plugin,
|
||||
|
@ -1563,7 +2051,90 @@ class Kubernetes(abstract_driver.VnfAbstractDriver,
|
|||
def scale_resource_update(self, context, vnf_instance,
|
||||
scale_vnf_request,
|
||||
vim_connection_info):
|
||||
pass
|
||||
"""Update VnfcResourceInfo after scaling"""
|
||||
auth_attr = vim_connection_info.access_info
|
||||
auth_cred, file_descriptor = self._get_auth_creds(auth_attr)
|
||||
inst_vnf_info = vnf_instance.instantiated_vnf_info
|
||||
try:
|
||||
# initialize Kubernetes APIs
|
||||
core_v1_api_client = self.kubernetes.get_core_v1_api_client(
|
||||
auth=auth_cred)
|
||||
vnf_resources = objects.VnfResourceList.get_by_vnf_instance_id(
|
||||
context, vnf_instance.id)
|
||||
# get scale target informations
|
||||
vnfd_dict = vnflcm_utils._get_vnfd_dict(context,
|
||||
vnf_instance.vnfd_id,
|
||||
inst_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)
|
||||
vdu_defs = vnflcm_utils.get_target_vdu_def_dict(
|
||||
extract_policy_infos=extract_policy_infos,
|
||||
aspect_id=scale_vnf_request.aspect_id,
|
||||
tosca=tosca)
|
||||
is_found = False
|
||||
for vnf_resource in vnf_resources:
|
||||
# For CNF operations, Kubernetes resource information is
|
||||
# stored in vnfc_resource as follows:
|
||||
# - resource_name : "namespace,name"
|
||||
# - resource_type : "api_version,kind"
|
||||
rsc_name = vnf_resource.resource_name.split(',')[1]
|
||||
for vdu_id, vdu_def in vdu_defs.items():
|
||||
vdu_properties = vdu_def.get('properties')
|
||||
if rsc_name == vdu_properties.get('name'):
|
||||
is_found = True
|
||||
namespace = vnf_resource.resource_name.split(',')[0]
|
||||
rsc_kind = vnf_resource.resource_type.split(',')[1]
|
||||
target_vdu_id = vdu_id
|
||||
break
|
||||
if is_found:
|
||||
break
|
||||
# extract stored Pod names by vdu_id
|
||||
stored_pod_list = []
|
||||
metadata = None
|
||||
for vnfc_resource in inst_vnf_info.vnfc_resource_info:
|
||||
if vnfc_resource.vdu_id == target_vdu_id:
|
||||
stored_pod_list.append(
|
||||
vnfc_resource.compute_resource.resource_id)
|
||||
if not metadata:
|
||||
# get metadata for new VnfcResourceInfo entry
|
||||
metadata = vnfc_resource.metadata
|
||||
# get actual Pod name list
|
||||
pod_list = core_v1_api_client.list_namespaced_pod(
|
||||
namespace=namespace)
|
||||
actual_pod_list = []
|
||||
for pod in pod_list.items:
|
||||
match_result = self._is_match_pod_naming_rule(
|
||||
rsc_kind, rsc_name, pod.metadata.name)
|
||||
if match_result:
|
||||
actual_pod_list.append(pod.metadata.name)
|
||||
# Remove the reduced pods from VnfcResourceInfo
|
||||
del_index = []
|
||||
for index, vnfc in enumerate(inst_vnf_info.vnfc_resource_info):
|
||||
if vnfc.compute_resource.resource_id not in actual_pod_list \
|
||||
and vnfc.vdu_id == target_vdu_id:
|
||||
del_index.append(index)
|
||||
for ind in reversed(del_index):
|
||||
inst_vnf_info.vnfc_resource_info.pop(ind)
|
||||
# Add the increased pods to VnfcResourceInfo
|
||||
for actual_pod_name in actual_pod_list:
|
||||
if actual_pod_name not in stored_pod_list:
|
||||
add_vnfc_resource = objects.VnfcResourceInfo()
|
||||
add_vnfc_resource.id = uuidutils.generate_uuid()
|
||||
add_vnfc_resource.vdu_id = target_vdu_id
|
||||
resource = objects.ResourceHandle()
|
||||
resource.resource_id = actual_pod_name
|
||||
resource.vim_level_resource_type = rsc_kind
|
||||
add_vnfc_resource.compute_resource = resource
|
||||
add_vnfc_resource.metadata = metadata
|
||||
inst_vnf_info.vnfc_resource_info.append(
|
||||
add_vnfc_resource)
|
||||
except Exception as e:
|
||||
LOG.error('Update vnfc resource info got an error due to %s', e)
|
||||
raise
|
||||
finally:
|
||||
self.clean_authenticate_vim(auth_cred, file_descriptor)
|
||||
|
||||
def scale_in_reverse(self,
|
||||
context,
|
||||
|
|
|
@ -91,7 +91,7 @@ class VnfNoop(abstract_driver.VnfAbstractDriver):
|
|||
pass
|
||||
|
||||
def post_vnf_instantiation(self, context, vnf_instance,
|
||||
vim_connection_info):
|
||||
vim_connection_info, instantiate_vnf_req):
|
||||
pass
|
||||
|
||||
def heal_vnf(self, context, vnf_instance, vim_connection_info,
|
||||
|
|
|
@ -907,7 +907,7 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
|
|||
|
||||
@log.log
|
||||
def post_vnf_instantiation(self, context, vnf_instance,
|
||||
vim_connection_info):
|
||||
vim_connection_info, instantiate_vnf_req):
|
||||
inst_vnf_info = vnf_instance.instantiated_vnf_info
|
||||
access_info = vim_connection_info.access_info
|
||||
|
||||
|
@ -1221,7 +1221,8 @@ class OpenStack(abstract_driver.VnfAbstractDriver,
|
|||
heatclient.update(stack_id=inst_vnf_info.instance_id, existing=True)
|
||||
|
||||
@log.log
|
||||
def heal_vnf_wait(self, context, vnf_instance, vim_connection_info):
|
||||
def heal_vnf_wait(self, context, vnf_instance, vim_connection_info,
|
||||
heal_vnf_request):
|
||||
"""Check vnf is healed successfully"""
|
||||
|
||||
access_info = vim_connection_info.access_info
|
||||
|
|
Loading…
Reference in New Issue