Terraform Infra-Driver
This patch provides terraform infra-driver with several unit and functional tests and is build an environment for terraform. The supported version of terraform is v1.4.0 or later. To build the terraform environment that need to install as follow components: - awscli - docker - localstack or moto server - terraform Implements: blueprint terraform-infra-driver Change-Id: I14414c42229dcdb8e0083d7c51d6be6b5f2fc841
This commit is contained in:
parent
1068aedc32
commit
5d59833b20
18
.zuul.yaml
18
.zuul.yaml
@ -749,6 +749,23 @@
|
||||
vars:
|
||||
config_enhanced_policy: true
|
||||
|
||||
- job:
|
||||
name: tacker-functional-devstack-multinode-sol-terraform-v2
|
||||
parent: tacker-functional-devstack-multinode-legacy
|
||||
description: |
|
||||
Multinodes job for SOL Terraform devstack-based functional tests
|
||||
attempts: 1
|
||||
host-vars:
|
||||
controller-tacker:
|
||||
tox_envlist: dsvm-functional-sol-terraform-v2
|
||||
devstack_local_conf:
|
||||
post-config:
|
||||
$TACKER_CONF:
|
||||
v2_vnfm:
|
||||
tf_file_dir: /tmp/tacker/terraform
|
||||
vars:
|
||||
terraform_setup: true
|
||||
|
||||
- job:
|
||||
name: tacker-compliance-devstack-multinode-sol
|
||||
parent: tacker-functional-devstack-multinode-legacy
|
||||
@ -791,3 +808,4 @@
|
||||
- tacker-functional-devstack-enhanced-policy-sol
|
||||
- tacker-functional-devstack-enhanced-policy-sol-kubernetes
|
||||
- tacker-compliance-devstack-multinode-sol
|
||||
- tacker-functional-devstack-multinode-sol-terraform-v2
|
||||
|
@ -7,6 +7,8 @@
|
||||
- setup-k8s-oidc
|
||||
- setup-default-vim
|
||||
- setup-helm
|
||||
- role: setup-terraform
|
||||
when: terraform_setup is defined and terraform_setup | bool
|
||||
- role: setup-fake-prometheus-server
|
||||
when: prometheus_setup is defined and prometheus_setup | bool
|
||||
- role: setup-multi-tenant-vim
|
||||
|
@ -0,0 +1,6 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Support Terraform infra-driver. Using Terraform as a backend tool enables
|
||||
Tacker to create virtual resources on several platforms that Terraform
|
||||
has already supported, such as AWS.
|
2
roles/setup-terraform/files/aws_config
Normal file
2
roles/setup-terraform/files/aws_config
Normal file
@ -0,0 +1,2 @@
|
||||
[profile localstack_profile]
|
||||
region = us-east-1
|
3
roles/setup-terraform/files/aws_credentials
Normal file
3
roles/setup-terraform/files/aws_credentials
Normal file
@ -0,0 +1,3 @@
|
||||
[localstack_profile]
|
||||
aws_access_key_id = mock_access_key
|
||||
aws_secret_access_key = mock_secret_key
|
61
roles/setup-terraform/tasks/main.yaml
Normal file
61
roles/setup-terraform/tasks/main.yaml
Normal file
@ -0,0 +1,61 @@
|
||||
- block:
|
||||
- name: Install docker from ubuntu official repository
|
||||
package:
|
||||
name: docker.io
|
||||
state: present
|
||||
become: yes
|
||||
|
||||
- name: Make the user belong to a Docker group
|
||||
user:
|
||||
name: stack
|
||||
groups: docker
|
||||
append: yes
|
||||
become: yes
|
||||
|
||||
- name: Get gpg key of hashicorp
|
||||
get_url:
|
||||
url: https://apt.releases.hashicorp.com/gpg
|
||||
dest: /usr/share/keyrings/hashicorp-archive-keyring.asc
|
||||
mode: 0644
|
||||
force: true
|
||||
become: yes
|
||||
|
||||
- name: Add hashicorp repository
|
||||
apt_repository:
|
||||
repo: deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.asc] https://apt.releases.hashicorp.com {{ ansible_distribution_release }} main
|
||||
filename: hashicorp
|
||||
state: present
|
||||
become: yes
|
||||
|
||||
- name: Install terraform
|
||||
apt:
|
||||
name: terraform
|
||||
state: present
|
||||
update_cache: yes
|
||||
become: yes
|
||||
|
||||
- name: Install awscli, LocalStack CLI
|
||||
pip:
|
||||
name:
|
||||
- awscli>=1.29.0
|
||||
- localstack>=2.0.0
|
||||
state: present
|
||||
become: yes
|
||||
become_user: stack
|
||||
|
||||
- name: Check terraform version
|
||||
shell: terraform --version
|
||||
register: terraform_version
|
||||
become: yes
|
||||
become_user: stack
|
||||
|
||||
- name: Start LocalStack
|
||||
shell: localstack start -d
|
||||
register: localstack_start
|
||||
environment:
|
||||
PATH: "{{ ansible_env.PATH }}:/opt/stack/.local/bin"
|
||||
become: yes
|
||||
become_user: stack
|
||||
|
||||
when:
|
||||
- inventory_hostname == 'controller-tacker'
|
@ -130,7 +130,11 @@ VNFM_OPTS = [
|
||||
cfg.StrOpt('heat_ca_cert_file',
|
||||
default='',
|
||||
help=_('Specifies the root CA certificate to use when the '
|
||||
'heat_verify_cert option is True.'))
|
||||
'heat_verify_cert option is True.')),
|
||||
cfg.StrOpt('tf_file_dir',
|
||||
default='/var/lib/tacker/terraform/',
|
||||
help=_('Temporary directory for Terraform infra-driver to '
|
||||
'store terraform config files'))
|
||||
]
|
||||
|
||||
CONF.register_opts(VNFM_OPTS, 'v2_vnfm')
|
||||
|
@ -384,6 +384,11 @@ class HelmOperationFailed(SolHttpError422):
|
||||
# detail set in the code
|
||||
|
||||
|
||||
class TerraformOperationFailed(SolHttpError422):
|
||||
title = 'Terraform operation failed'
|
||||
# detail set in the code
|
||||
|
||||
|
||||
class HelmParameterNotFound(SolHttpError400):
|
||||
message = _("Helm parameter for scale vdu %(vdu_name)s is not found.")
|
||||
|
||||
|
@ -28,6 +28,7 @@ from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import helm
|
||||
from tacker.sol_refactored.infra_drivers.kubernetes import kubernetes
|
||||
from tacker.sol_refactored.infra_drivers.openstack import openstack
|
||||
from tacker.sol_refactored.infra_drivers.terraform import terraform
|
||||
from tacker.sol_refactored.nfvo import nfvo_client
|
||||
from tacker.sol_refactored import objects
|
||||
from tacker.sol_refactored.objects.v2 import fields as v2fields
|
||||
@ -428,6 +429,9 @@ class VnfLcmDriverV2(object):
|
||||
elif vim_info.vimType == 'ETSINFV.HELM.V_3':
|
||||
driver = helm.Helm()
|
||||
driver.instantiate(req, inst, grant_req, grant, vnfd)
|
||||
elif vim_info.vimType == 'TERRAFORM.V1':
|
||||
driver = terraform.Terraform()
|
||||
driver.instantiate(req, inst, grant_req, grant, vnfd)
|
||||
else:
|
||||
# should not occur
|
||||
raise sol_ex.SolException(sol_detail='not support vim type')
|
||||
@ -447,6 +451,9 @@ class VnfLcmDriverV2(object):
|
||||
elif vim_info.vimType == 'ETSINFV.HELM.V_3':
|
||||
driver = helm.Helm()
|
||||
driver.instantiate_rollback(req, inst, grant_req, grant, vnfd)
|
||||
elif vim_info.vimType == 'TERRAFORM.V1':
|
||||
driver = terraform.Terraform()
|
||||
driver.instantiate_rollback(req, inst, grant_req, grant, vnfd)
|
||||
else:
|
||||
# should not occur
|
||||
raise sol_ex.SolException(sol_detail='not support vim type')
|
||||
@ -592,6 +599,9 @@ class VnfLcmDriverV2(object):
|
||||
elif vim_info.vimType == 'ETSINFV.HELM.V_3':
|
||||
driver = helm.Helm()
|
||||
driver.terminate(req, inst, grant_req, grant, vnfd)
|
||||
elif vim_info.vimType == 'TERRAFORM.V1':
|
||||
driver = terraform.Terraform()
|
||||
driver.terminate(req, inst, grant_req, grant, vnfd)
|
||||
else:
|
||||
# should not occur
|
||||
raise sol_ex.SolException(sol_detail='not support vim type')
|
||||
|
289
tacker/sol_refactored/infra_drivers/terraform/terraform.py
Normal file
289
tacker/sol_refactored/infra_drivers/terraform/terraform.py
Normal file
@ -0,0 +1,289 @@
|
||||
# Copyright (C) 2023 Nippon Telegraph and Telephone Corporation
|
||||
# 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.
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
from tacker.common import exceptions
|
||||
from tacker.objects import vnf_package_vnfd
|
||||
from tacker.sol_refactored.common import config
|
||||
from tacker.sol_refactored.common import exceptions as sol_ex
|
||||
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
|
||||
from tacker.sol_refactored import objects
|
||||
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tacker.conf
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class Terraform():
|
||||
'''Implements Terraform in Tacker'''
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def instantiate(self, req, inst, grant_req, grant, vnfd):
|
||||
'''Implements instantiate using Terraform commands'''
|
||||
|
||||
vim_conn_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
|
||||
tf_dir_path = req.additionalParams.get('tf_dir_path')
|
||||
tf_var_path = req.additionalParams.get('tf_var_path')
|
||||
working_dir = self._get_tf_vnfpkg(
|
||||
inst.id, grant_req.vnfdId, tf_dir_path)
|
||||
|
||||
self._generate_provider_tf(vim_conn_info, working_dir)
|
||||
self._instantiate(vim_conn_info, working_dir, tf_var_path)
|
||||
self._make_instantiated_vnf_info(req, inst, grant_req,
|
||||
grant, vnfd, working_dir,
|
||||
tf_var_path)
|
||||
|
||||
def _instantiate(self, vim_conn_info, working_dir, tf_var_path):
|
||||
'''Executes terraform init, terraform plan, and terraform apply'''
|
||||
|
||||
access_info = vim_conn_info.get('accessInfo', {})
|
||||
|
||||
try:
|
||||
init_cmd = self._gen_tf_cmd("init")
|
||||
self._exec_cmd(init_cmd, cwd=working_dir)
|
||||
LOG.info("Terraform init completed successfully.")
|
||||
|
||||
plan_cmd = self._gen_tf_cmd('plan', access_info, tf_var_path)
|
||||
self._exec_cmd(plan_cmd, cwd=working_dir)
|
||||
LOG.info("Terraform plan completed successfully.")
|
||||
|
||||
apply_cmd = self._gen_tf_cmd('apply', access_info, tf_var_path)
|
||||
self._exec_cmd(apply_cmd, cwd=working_dir)
|
||||
LOG.info("Terraform apply completed successfully.")
|
||||
|
||||
except subprocess.CalledProcessError as error:
|
||||
raise sol_ex.TerraformOperationFailed(sol_detail=str(error))
|
||||
|
||||
def terminate(self, req, inst, grant_req, grant, vnfd):
|
||||
'''Terminates the terraform resources managed by the current project'''
|
||||
|
||||
vim_conn_info = inst_utils.select_vim_info(inst.vimConnectionInfo)
|
||||
working_dir = f"{CONF.v2_vnfm.tf_file_dir}/{inst.id}"
|
||||
tf_var_path = inst.instantiatedVnfInfo.metadata['tf_var_path']
|
||||
self._terminate(vim_conn_info, working_dir, tf_var_path)
|
||||
|
||||
def _terminate(self, vim_conn_info, working_dir, tf_var_path):
|
||||
'''Executes Terraform Destroy and removes the working_dir'''
|
||||
|
||||
access_info = vim_conn_info.get('accessInfo', {})
|
||||
|
||||
try:
|
||||
# Execute the terraform destroy command (auto-approve)
|
||||
destroy_cmd = self._gen_tf_cmd('destroy', access_info, tf_var_path)
|
||||
self._exec_cmd(destroy_cmd, cwd=working_dir)
|
||||
LOG.info("Terraform destroy completed successfully.")
|
||||
|
||||
except subprocess.CalledProcessError as error:
|
||||
failed_process = error.cmd[0].capitalize()
|
||||
LOG.error(f"Error running {failed_process}: {error}")
|
||||
# raise error and leave working_dir for retry
|
||||
raise sol_ex.TerraformOperationFailed(sol_detail=str(error))
|
||||
|
||||
try:
|
||||
# Remove the working directory and its contents
|
||||
shutil.rmtree(working_dir)
|
||||
LOG.info(f"Working directory {working_dir} destroyed successfully")
|
||||
except OSError as error:
|
||||
LOG.error(f"Error destroying working directory: {error}")
|
||||
raise
|
||||
|
||||
def instantiate_rollback(self, req, inst, grant_req, grant, vnfd):
|
||||
'''Calls terminate'''
|
||||
self.terminate(req, inst, grant_req, grant, vnfd)
|
||||
|
||||
def _make_instantiated_vnf_info(self, req, inst, grant_req,
|
||||
grant, vnfd, working_dir, tf_var_path):
|
||||
'''Updates Tacker with information on the VNF state'''
|
||||
|
||||
# Define inst_vnf_info
|
||||
flavour_id = req.flavourId
|
||||
inst.instantiatedVnfInfo = objects.VnfInstanceV2_InstantiatedVnfInfo(
|
||||
flavourId=flavour_id,
|
||||
vnfState='STARTED',
|
||||
metadata={
|
||||
'tf_var_path': tf_var_path
|
||||
}
|
||||
)
|
||||
|
||||
# Specify the path to the terraform.tfstate file
|
||||
tfstate_file = f"{working_dir}/terraform.tfstate"
|
||||
|
||||
# Read the contents of the file
|
||||
with open(tfstate_file, "r", encoding="utf-8") as file:
|
||||
tfstate_data = json.load(file)
|
||||
|
||||
vdu_nodes = vnfd.get_vdu_nodes(flavour_id)
|
||||
vdu_ids = {value.get('properties').get('name'): key
|
||||
for key, value in vdu_nodes.items()}
|
||||
|
||||
# Extract the required fields from the tfstate data
|
||||
resources = tfstate_data["resources"]
|
||||
|
||||
# Define vnfcResourceInfo and vnfcInfo in a single iteration
|
||||
vnfc_resource_info_list = [
|
||||
objects.VnfcResourceInfoV2(
|
||||
id=resource['name'],
|
||||
vduId=vdu_ids.get(resource['name']),
|
||||
computeResource=objects.ResourceHandle(
|
||||
resourceId=resource['name'],
|
||||
vimLevelResourceType=resource['type']
|
||||
),
|
||||
metadata={}
|
||||
)
|
||||
for resource in resources
|
||||
if (resource["type"] == "aws_instance" and
|
||||
vdu_ids.get(resource['name']))
|
||||
]
|
||||
|
||||
vnfc_info_list = [
|
||||
objects.VnfcInfoV2(
|
||||
id=f"{vnfc_res_info.vduId}-{vnfc_res_info.id}",
|
||||
vduId=vnfc_res_info.vduId,
|
||||
vnfcResourceInfoId=vnfc_res_info.id,
|
||||
vnfcState='STARTED'
|
||||
)
|
||||
for vnfc_res_info in vnfc_resource_info_list
|
||||
]
|
||||
|
||||
inst.instantiatedVnfInfo.vnfcResourceInfo = vnfc_resource_info_list
|
||||
inst.instantiatedVnfInfo.vnfcInfo = vnfc_info_list
|
||||
|
||||
def _get_tf_vnfpkg(self, vnf_instance_id, vnfd_id, tf_dir_path):
|
||||
"""Create a VNF package with given IDs
|
||||
|
||||
A path of created package is returned, or failed if vnfd_id is invalid.
|
||||
"""
|
||||
|
||||
# Define variables
|
||||
context = tacker.context.get_admin_context()
|
||||
try:
|
||||
pkg_vnfd = vnf_package_vnfd.VnfPackageVnfd().get_by_id(
|
||||
context, vnfd_id)
|
||||
except exceptions.VnfPackageVnfdNotFound as exc:
|
||||
raise sol_ex.VnfdIdNotFound(vnfd_id=vnfd_id) from exc
|
||||
csar_path = os.path.join(CONF.vnf_package.vnf_package_csar_path,
|
||||
pkg_vnfd.package_uuid)
|
||||
|
||||
# Assemble paths and copy recursively
|
||||
vnf_package_path = f"{csar_path}/{tf_dir_path}"
|
||||
new_tf_dir_path = f"{CONF.v2_vnfm.tf_file_dir}/{vnf_instance_id}"
|
||||
os.makedirs(new_tf_dir_path, exist_ok=True)
|
||||
# NOTE: the creation of the directory /var/lib/tacker/terraform
|
||||
# should be completed during the installation of Tacker.
|
||||
shutil.copytree(vnf_package_path, new_tf_dir_path, dirs_exist_ok=True)
|
||||
|
||||
return new_tf_dir_path
|
||||
|
||||
def _generate_provider_tf(self, vim_conn_info, main_tf_path):
|
||||
'''Creates provider.tf beside main.tf'''
|
||||
|
||||
# Read vimConnectionInfo for information
|
||||
access_info = vim_conn_info.get('accessInfo', {})
|
||||
interface_info = vim_conn_info.get('interfaceInfo', {})
|
||||
provider_type = interface_info.get('providerType')
|
||||
provider_version = interface_info.get('providerVersion')
|
||||
|
||||
# Create provider.tf content using the above information
|
||||
content = {
|
||||
"variable": {},
|
||||
"terraform": {
|
||||
"required_version": ">=0.13",
|
||||
"required_providers": {
|
||||
provider_type: {
|
||||
"source": f"hashicorp/{provider_type}",
|
||||
"version": f"~> {provider_version}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"provider": {
|
||||
provider_type: {}
|
||||
}
|
||||
}
|
||||
|
||||
# Add accessInfo keys as variables and provider arguments
|
||||
for key, value in access_info.items():
|
||||
if key == "endpoints":
|
||||
content["provider"][provider_type][key] = value
|
||||
continue
|
||||
content["variable"][key] = {
|
||||
"type": "string"
|
||||
}
|
||||
content["provider"][provider_type][key] = f"${{var.{key}}}"
|
||||
|
||||
# Save the provider.tf file
|
||||
provider_tf_path = os.path.join(main_tf_path, 'provider.tf.json')
|
||||
with open(provider_tf_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(content, f, indent=4)
|
||||
|
||||
return provider_tf_path
|
||||
|
||||
def _exec_cmd(self, cmd, cwd,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
check=True, text=True):
|
||||
"""A helper for subprocess.run()
|
||||
|
||||
Exec a command through subprocess.run() with common options among
|
||||
commands used in this package. All the args other than self and cmd
|
||||
the same as for subprocess.run().
|
||||
"""
|
||||
res = subprocess.run(cmd, cwd=cwd, stdout=stdout, stderr=stderr,
|
||||
check=check, text=text)
|
||||
if res.returncode != 0:
|
||||
raise
|
||||
return res
|
||||
|
||||
def _gen_tf_cmd(self, subcmd, access_info=None, tf_var_path=None,
|
||||
auto_approve=True):
|
||||
"""Return terraform command of given subcommand as a list
|
||||
|
||||
The result is intended to be an arg of supprocess.run().
|
||||
"""
|
||||
|
||||
# NOTE(yasufum): Only following subcommands are supported.
|
||||
allowed_subcmds = ["init", "plan", "apply", "destroy"]
|
||||
if subcmd not in allowed_subcmds:
|
||||
return []
|
||||
|
||||
if subcmd == "init":
|
||||
return ["terraform", "init"]
|
||||
|
||||
def _gen_tf_cmd_args(access_info, tf_var_path):
|
||||
args = []
|
||||
for key, value in access_info.items():
|
||||
if key == "endpoints":
|
||||
continue
|
||||
args.extend(['-var', f'{key}={value}'])
|
||||
if tf_var_path:
|
||||
args.extend(['-var-file', tf_var_path])
|
||||
return args
|
||||
|
||||
# list of subcommands accept "-auto-approve" option.
|
||||
accept_ap = ["apply", "destroy"]
|
||||
if auto_approve is True and subcmd in accept_ap:
|
||||
cmd = ["terraform", subcmd, "-auto-approve"]
|
||||
else:
|
||||
cmd = ["terraform", subcmd]
|
||||
args = _gen_tf_cmd_args(access_info, tf_var_path)
|
||||
return cmd + args
|
119
tacker/tests/functional/sol_terraform_v2/base_v2.py
Normal file
119
tacker/tests/functional/sol_terraform_v2/base_v2.py
Normal file
@ -0,0 +1,119 @@
|
||||
# Copyright (C) 2023 Nippon Telegraph and Telephone Corporation
|
||||
# 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 shutil
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from tacker.sol_refactored.common import http_client
|
||||
from tacker.sol_refactored import objects
|
||||
from tacker.tests.functional import base_v2
|
||||
from tacker.tests.functional.common.fake_server import FakeServerManager
|
||||
from tacker.tests.functional.sol_v2_common import utils
|
||||
from tacker import version
|
||||
|
||||
FAKE_SERVER_MANAGER = FakeServerManager()
|
||||
|
||||
VNF_PACKAGE_UPLOAD_TIMEOUT = 300
|
||||
|
||||
|
||||
class BaseVnfLcmTerraformV2Test(base_v2.BaseTackerTestV2):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(BaseVnfLcmTerraformV2Test, cls).setUpClass()
|
||||
"""Base test case class for SOL v2 terraform functional tests."""
|
||||
|
||||
cfg.CONF(args=['--config-file', '/etc/tacker/tacker.conf'],
|
||||
project='tacker',
|
||||
version='%%prog %s' % version.version_info.release_string())
|
||||
objects.register_all()
|
||||
|
||||
vim_info = cls.get_vim_info()
|
||||
auth = http_client.KeystonePasswordAuthHandle(
|
||||
auth_url=vim_info.interfaceInfo['endpoint'],
|
||||
username=vim_info.accessInfo['username'],
|
||||
password=vim_info.accessInfo['password'],
|
||||
project_name=vim_info.accessInfo['project'],
|
||||
user_domain_name=vim_info.accessInfo['userDomain'],
|
||||
project_domain_name=vim_info.accessInfo['projectDomain']
|
||||
)
|
||||
cls.tacker_client = http_client.HttpClient(auth)
|
||||
|
||||
def assert_notification_get(self, callback_url):
|
||||
notify_mock_responses = FAKE_SERVER_MANAGER.get_history(
|
||||
callback_url)
|
||||
FAKE_SERVER_MANAGER.clear_history(
|
||||
callback_url)
|
||||
self.assertEqual(1, len(notify_mock_responses))
|
||||
self.assertEqual(204, notify_mock_responses[0].status_code)
|
||||
|
||||
def _check_notification(self, callback_url, notify_type):
|
||||
notify_mock_responses = FAKE_SERVER_MANAGER.get_history(
|
||||
callback_url)
|
||||
FAKE_SERVER_MANAGER.clear_history(
|
||||
callback_url)
|
||||
self.assertEqual(1, len(notify_mock_responses))
|
||||
self.assertEqual(204, notify_mock_responses[0].status_code)
|
||||
self.assertEqual(notify_type, notify_mock_responses[0].request_body[
|
||||
'notificationType'])
|
||||
|
||||
@classmethod
|
||||
def create_vnf_package(cls, sample_path, user_data=None, image_path=None):
|
||||
vnfd_id = uuidutils.generate_uuid()
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
|
||||
utils.make_zip(sample_path, tmp_dir, vnfd_id, image_path)
|
||||
|
||||
zip_file_name = os.path.basename(os.path.abspath(sample_path)) + ".zip"
|
||||
zip_file_path = os.path.join(tmp_dir, zip_file_name)
|
||||
|
||||
path = "/vnfpkgm/v1/vnf_packages"
|
||||
if user_data is not None:
|
||||
req_body = {'userDefinedData': user_data}
|
||||
else:
|
||||
req_body = {}
|
||||
resp, body = cls.tacker_client.do_request(
|
||||
path, "POST", expected_status=[201], body=req_body)
|
||||
|
||||
pkg_id = body['id']
|
||||
|
||||
with open(zip_file_path, 'rb') as fp:
|
||||
path = f"/vnfpkgm/v1/vnf_packages/{pkg_id}/package_content"
|
||||
resp, body = cls.tacker_client.do_request(
|
||||
path, "PUT", body=fp, content_type='application/zip',
|
||||
expected_status=[202])
|
||||
|
||||
# wait for onboard
|
||||
timeout = VNF_PACKAGE_UPLOAD_TIMEOUT
|
||||
start_time = int(time.time())
|
||||
path = f"/vnfpkgm/v1/vnf_packages/{pkg_id}"
|
||||
while True:
|
||||
resp, body = cls.tacker_client.do_request(
|
||||
path, "GET", expected_status=[200])
|
||||
if body['onboardingState'] == "ONBOARDED":
|
||||
break
|
||||
|
||||
if (int(time.time()) - start_time) > timeout:
|
||||
raise Exception("Failed to onboard vnf package")
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
shutil.rmtree(tmp_dir)
|
||||
|
||||
return pkg_id, vnfd_id
|
79
tacker/tests/functional/sol_terraform_v2/paramgen.py
Normal file
79
tacker/tests/functional/sol_terraform_v2/paramgen.py
Normal file
@ -0,0 +1,79 @@
|
||||
# Copyright (C) 2023 Nippon Telegraph and Telephone Corporation
|
||||
# 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.
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
|
||||
def create_req_by_vnfd_id(vnfd_id):
|
||||
return {
|
||||
"vnfdId": vnfd_id,
|
||||
"vnfInstanceName": "test_terraform_instantiate",
|
||||
"vnfInstanceDescription": "test_terraform_instantiate",
|
||||
"metadata": {"dummy-key": "dummy-val"}
|
||||
}
|
||||
|
||||
|
||||
def instantiate_req():
|
||||
# All attributes are set.
|
||||
# NOTE: All of the following cardinality attributes are set.
|
||||
# In addition, 0..N or 1..N attributes are set to 2 or more.
|
||||
# - 0..1 (1)
|
||||
# - 0..N (2 or more)
|
||||
# - 1
|
||||
# - 1..N (2 or more)
|
||||
auth_url = "http://localhost:4566"
|
||||
|
||||
vim = {
|
||||
"vimId": uuidutils.generate_uuid(),
|
||||
"vimType": "TERRAFORM.V1",
|
||||
"interfaceInfo": {
|
||||
"providerType": "aws",
|
||||
"providerVersion": "4.0"
|
||||
},
|
||||
"accessInfo": {
|
||||
"region": "ap-northeast-1",
|
||||
"access_key": "mock_access_key",
|
||||
"secret_key": "mock_secret_key",
|
||||
"skip_credentials_validation": "true",
|
||||
"skip_metadata_api_check": "true",
|
||||
"skip_requesting_account_id": "true",
|
||||
"endpoints": {
|
||||
"ec2": auth_url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
"flavourId": "simple",
|
||||
"vimConnectionInfo": {
|
||||
"vim1": vim
|
||||
},
|
||||
"additionalParams": {"tf_dir_path": "Files/terraform"}
|
||||
}
|
||||
|
||||
|
||||
def terminate_req():
|
||||
# All attributes are set.
|
||||
# NOTE: All of the following cardinality attributes are set.
|
||||
# In addition, 0..N or 1..N attributes are set to 2 or more.
|
||||
# - 0..1 (1)
|
||||
# - 0..N (2 or more)
|
||||
# - 1
|
||||
# - 1..N (2 or more)
|
||||
return {
|
||||
"terminationType": "GRACEFUL",
|
||||
"gracefulTerminationTimeout": 5,
|
||||
"additionalParams": {"dummy-key": "dummy-val"}
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
tosca_definitions_version: tosca_simple_yaml_1_2
|
||||
description: ETSI NFV SOL 001 common types definitions version 2.6.1
|
||||
metadata:
|
||||
template_name: etsi_nfv_sol001_common_types
|
||||
template_author: ETSI_NFV
|
||||
template_version: 2.6.1
|
||||
|
||||
data_types:
|
||||
tosca.datatypes.nfv.L2AddressData:
|
||||
derived_from: tosca.datatypes.Root
|
||||
description: Describes the information on the MAC addresses to be assigned to a connection point.
|
||||
properties:
|
||||
mac_address_assignment:
|
||||
type: boolean
|
||||
description: Specifies if the address assignment is the responsibility of management and orchestration function or not. If it is set to True, it is the management and orchestration function responsibility
|
||||
required: true
|
||||
|
||||
tosca.datatypes.nfv.L3AddressData:
|
||||
derived_from: tosca.datatypes.Root
|
||||
description: Provides information about Layer 3 level addressing scheme and parameters applicable to a CP
|
||||
properties:
|
||||
ip_address_assignment:
|
||||
type: boolean
|
||||
description: Specifies if the address assignment is the responsibility of management and orchestration function or not. If it is set to True, it is the management and orchestration function responsibility
|
||||
required: true
|
||||
floating_ip_activated:
|
||||
type: boolean
|
||||
description: Specifies if the floating IP scheme is activated on the Connection Point or not
|
||||
required: true
|
||||
ip_address_type:
|
||||
type: string
|
||||
description: Defines address type. The address type should be aligned with the address type supported by the layer_protocols properties of the parent VnfExtCp
|
||||
required: false
|
||||
constraints:
|
||||
- valid_values: [ ipv4, ipv6 ]
|
||||
number_of_ip_address:
|
||||
type: integer
|
||||
description: Minimum number of IP addresses to be assigned
|
||||
required: false
|
||||
constraints:
|
||||
- greater_than: 0
|
||||
|
||||
tosca.datatypes.nfv.AddressData:
|
||||
derived_from: tosca.datatypes.Root
|
||||
description: Describes information about the addressing scheme and parameters applicable to a CP
|
||||
properties:
|
||||
address_type:
|
||||
type: string
|
||||
description: Describes the type of the address to be assigned to a connection point. The content type shall be aligned with the address type supported by the layerProtocol property of the connection point
|
||||
required: true
|
||||
constraints:
|
||||
- valid_values: [ mac_address, ip_address ]
|
||||
l2_address_data:
|
||||
type: tosca.datatypes.nfv.L2AddressData
|
||||
description: Provides the information on the MAC addresses to be assigned to a connection point.
|
||||
required: false
|
||||
l3_address_data:
|
||||
type: tosca.datatypes.nfv.L3AddressData
|
||||
description: Provides the information on the IP addresses to be assigned to a connection point
|
||||
required: false
|
||||
|
||||
tosca.datatypes.nfv.ConnectivityType:
|
||||
derived_from: tosca.datatypes.Root
|
||||
description: describes additional connectivity information of a virtualLink
|
||||
properties:
|
||||
layer_protocols:
|
||||
type: list
|
||||
description: Identifies the protocol a virtualLink gives access to (ethernet, mpls, odu2, ipv4, ipv6, pseudo-wire).The top layer protocol of the virtualLink protocol stack shall always be provided. The lower layer protocols may be included when there are specific requirements on these layers.
|
||||
required: true
|
||||
entry_schema:
|
||||
type: string
|
||||
constraints:
|
||||
- valid_values: [ ethernet, mpls, odu2, ipv4, ipv6, pseudo-wire ]
|
||||
flow_pattern:
|
||||
type: string
|
||||
description: Identifies the flow pattern of the connectivity
|
||||
required: false
|
||||
constraints:
|
||||
- valid_values: [ line, tree, mesh ]
|
||||
|
||||
tosca.datatypes.nfv.LinkBitrateRequirements:
|
||||
derived_from: tosca.datatypes.Root
|
||||
description: describes the requirements in terms of bitrate for a virtual link
|
||||
properties:
|
||||
root:
|
||||
type: integer # in bits per second
|
||||
description: Specifies the throughput requirement in bits per second of the link (e.g. bitrate of E-Line, root bitrate of E-Tree, aggregate capacity of E-LAN).
|
||||
required: true
|
||||
constraints:
|
||||
- greater_or_equal: 0
|
||||
leaf:
|
||||
type: integer # in bits per second
|
||||
description: Specifies the throughput requirement in bits per second of leaf connections to the link when applicable to the connectivity type (e.g. for E-Tree and E LAN branches).
|
||||
required: false
|
||||
constraints:
|
||||
- greater_or_equal: 0
|
||||
|
||||
tosca.datatypes.nfv.CpProtocolData:
|
||||
derived_from: tosca.datatypes.Root
|
||||
description: Describes and associates the protocol layer that a CP uses together with other protocol and connection point information
|
||||
properties:
|
||||
associated_layer_protocol:
|
||||
type: string
|
||||
required: true
|
||||
description: One of the values of the property layer_protocols of the CP
|
||||
constraints:
|
||||
- valid_values: [ ethernet, mpls, odu2, ipv4, ipv6, pseudo-wire ]
|
||||
address_data:
|
||||
type: list
|
||||
description: Provides information on the addresses to be assigned to the CP
|
||||
entry_schema:
|
||||
type: tosca.datatypes.nfv.AddressData
|
||||
required: false
|
||||
|
||||
tosca.datatypes.nfv.VnfProfile:
|
||||
derived_from: tosca.datatypes.Root
|
||||
description: describes a profile for instantiating VNFs of a particular NS DF according to a specific VNFD and VNF DF.
|
||||
properties:
|
||||
instantiation_level:
|
||||
type: string
|
||||
description: Identifier of the instantiation level of the VNF DF to be used for instantiation. If not present, the default instantiation level as declared in the VNFD shall be used.
|
||||
required: false
|
||||
min_number_of_instances:
|
||||
type: integer
|
||||
description: Minimum number of instances of the VNF based on this VNFD that is permitted to exist for this VnfProfile.
|
||||
required: true
|
||||
constraints:
|
||||
- greater_or_equal: 0
|
||||
max_number_of_instances:
|
||||
type: integer
|
||||
description: Maximum number of instances of the VNF based on this VNFD that is permitted to exist for this VnfProfile.
|
||||
required: true
|
||||
constraints:
|
||||
- greater_or_equal: 0
|
||||
|
||||
tosca.datatypes.nfv.Qos:
|
||||
derived_from: tosca.datatypes.Root
|
||||
description: describes QoS data for a given VL used in a VNF deployment flavour
|
||||
properties:
|
||||
latency:
|
||||
type: scalar-unit.time #Number
|
||||
description: Specifies the maximum latency
|
||||
required: true
|
||||
constraints:
|
||||
- greater_than: 0 s
|
||||
packet_delay_variation:
|
||||
type: scalar-unit.time #Number
|
||||
description: Specifies the maximum jitter
|
||||
required: true
|
||||
constraints:
|
||||
- greater_or_equal: 0 s
|
||||
packet_loss_ratio:
|
||||
type: float
|
||||
description: Specifies the maximum packet loss ratio
|
||||
required: false
|
||||
constraints:
|
||||
- in_range: [ 0.0, 1.0 ]
|
||||
|
||||
capability_types:
|
||||
tosca.capabilities.nfv.VirtualLinkable:
|
||||
derived_from: tosca.capabilities.Node
|
||||
description: A node type that includes the VirtualLinkable capability indicates that it can be pointed by tosca.relationships.nfv.VirtualLinksTo relationship type
|
||||
|
||||
relationship_types:
|
||||
tosca.relationships.nfv.VirtualLinksTo:
|
||||
derived_from: tosca.relationships.DependsOn
|
||||
description: Represents an association relationship between the VduCp and VnfVirtualLink node types
|
||||
valid_target_types: [ tosca.capabilities.nfv.VirtualLinkable ]
|
||||
|
||||
node_types:
|
||||
tosca.nodes.nfv.Cp:
|
||||
derived_from: tosca.nodes.Root
|
||||
description: Provides information regarding the purpose of the connection point
|
||||
properties:
|
||||
layer_protocols:
|
||||
type: list
|
||||
description: Identifies which protocol the connection point uses for connectivity purposes
|
||||
required: true
|
||||
entry_schema:
|
||||
type: string
|
||||
constraints:
|
||||
- valid_values: [ ethernet, mpls, odu2, ipv4, ipv6, pseudo-wire ]
|
||||
role: #Name in ETSI NFV IFA011 v0.7.3: cpRole
|
||||
type: string
|
||||
description: Identifies the role of the port in the context of the traffic flow patterns in the VNF or parent NS
|
||||
required: false
|
||||
constraints:
|
||||
- valid_values: [ root, leaf ]
|
||||
description:
|
||||
type: string
|
||||
description: Provides human-readable information on the purpose of the connection point
|
||||
required: false
|
||||
protocol:
|
||||
type: list
|
||||
description: Provides information on the addresses to be assigned to the connection point(s) instantiated from this Connection Point Descriptor
|
||||
required: false
|
||||
entry_schema:
|
||||
type: tosca.datatypes.nfv.CpProtocolData
|
||||
trunk_mode:
|
||||
type: boolean
|
||||
description: Provides information about whether the CP instantiated from this Cp is in Trunk mode (802.1Q or other), When operating in "trunk mode", the Cp is capable of carrying traffic for several VLANs. Absence of this property implies that trunkMode is not configured for the Cp i.e. It is equivalent to boolean value "false".
|
||||
required: false
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,177 @@
|
||||
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
|
||||
- sample_tf_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
|
||||
interfaces:
|
||||
Vnflcm:
|
||||
instantiate_start:
|
||||
implementation: sample-script
|
||||
instantiate_end:
|
||||
implementation: sample-script
|
||||
terminate_start:
|
||||
implementation: sample-script
|
||||
terminate_end:
|
||||
implementation: sample-script
|
||||
scale_start:
|
||||
implementation: sample-script
|
||||
scale_end:
|
||||
implementation: sample-script
|
||||
heal_start:
|
||||
implementation: sample-script
|
||||
heal_end:
|
||||
implementation: sample-script
|
||||
modify_information_start:
|
||||
implementation: sample-script
|
||||
modify_information_end:
|
||||
implementation: sample-script
|
||||
artifacts:
|
||||
sample-script:
|
||||
description: Sample script
|
||||
type: tosca.artifacts.Implementation.Python
|
||||
file: ../Scripts/sample_script.py
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
vdu1_aspect:
|
||||
name: vdu1_aspect
|
||||
description: vdu1 scaling aspect
|
||||
max_scale_level: 2
|
||||
step_deltas:
|
||||
- delta_1
|
||||
vdu2_aspect:
|
||||
name: vdu2_aspect
|
||||
description: vdu2 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 ]
|
||||
|
||||
- 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:
|
||||
vdu1_aspect:
|
||||
scale_level: 0
|
||||
vdu2_aspect:
|
||||
scale_level: 0
|
||||
instantiation_level_2:
|
||||
description: Largest size
|
||||
scale_info:
|
||||
vdu1_aspect:
|
||||
scale_level: 2
|
||||
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: 3
|
||||
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 ]
|
@ -0,0 +1,31 @@
|
||||
tosca_definitions_version: tosca_simple_yaml_1_2
|
||||
|
||||
description: Sample Terraform VNF
|
||||
|
||||
imports:
|
||||
- etsi_nfv_sol001_common_types.yaml
|
||||
- etsi_nfv_sol001_vnfd_types.yaml
|
||||
- sample_tf_types.yaml
|
||||
- sample_tf_df_simple.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-4840d7000000
|
||||
provider: Company
|
||||
product_name: Sample Terraform 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-4840d7000000 ] ]
|
||||
default: b1bb0ce7-ebca-4fa7-95ed-4840d7000000
|
||||
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 Terraform VNF' ] ]
|
||||
default: 'Sample Terraform 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,8 @@
|
||||
resource "aws_instance" "vdu1"{
|
||||
ami = "ami-785db401"
|
||||
instance_type = "t2.micro"
|
||||
|
||||
tags = {
|
||||
Name = "hoge01"
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
# Copyright (C) 2023 Nippon Telegraph and Telephone Corporation
|
||||
# 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 pickle
|
||||
import sys
|
||||
|
||||
|
||||
class SampleScript(object):
|
||||
|
||||
def __init__(self, req, inst, grant_req, grant, csar_dir):
|
||||
self.req = req
|
||||
self.inst = inst
|
||||
self.grant_req = grant_req
|
||||
self.grant = grant
|
||||
self.csar_dir = csar_dir
|
||||
|
||||
def instantiate_start(self):
|
||||
pass
|
||||
|
||||
def instantiate_end(self):
|
||||
pass
|
||||
|
||||
def terminate_start(self):
|
||||
pass
|
||||
|
||||
def terminate_end(self):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
script_dict = pickle.load(sys.stdin.buffer)
|
||||
|
||||
operation = script_dict['operation']
|
||||
req = script_dict['request']
|
||||
inst = script_dict['vnf_instance']
|
||||
grant_req = script_dict['grant_request']
|
||||
grant = script_dict['grant_response']
|
||||
csar_dir = script_dict['tmp_csar_dir']
|
||||
|
||||
script = SampleScript(req, inst, grant_req, grant, csar_dir)
|
||||
try:
|
||||
getattr(script, operation)()
|
||||
except AttributeError:
|
||||
raise Exception("{} is not included in the script.".format(operation))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
os._exit(0)
|
||||
except Exception as ex:
|
||||
sys.stderr.write(str(ex))
|
||||
sys.stderr.flush()
|
||||
os._exit(1)
|
@ -0,0 +1,9 @@
|
||||
TOSCA-Meta-File-Version: 1.0
|
||||
Created-by: dummy_user
|
||||
CSAR-Version: 1.1
|
||||
Entry-Definitions: Definitions/sample_tf_top.vnfd.yaml
|
||||
|
||||
Name: Files/terraform/main.tf
|
||||
Content-Type: test-data
|
||||
Algorithm: SHA-256
|
||||
Hash: 8b781c33326f6383316f24464371de38724898215bc04653e1985e3c9514c87d
|
@ -0,0 +1,48 @@
|
||||
# Copyright (C) 2023 Nippon Telegraph and Telephone Corporation
|
||||
# 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 json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from tacker.tests.functional.sol_terraform_v2 import paramgen as tf_paramgen
|
||||
from tacker.tests.functional.sol_v2_common import utils
|
||||
|
||||
|
||||
zip_file_name = os.path.basename(os.path.abspath(".")) + '.zip'
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
vnfd_id = uuidutils.generate_uuid()
|
||||
|
||||
# tacker/tests/functional/sol_terraform_v2/samples/{package_name}
|
||||
utils.make_zip(".", tmp_dir, vnfd_id)
|
||||
|
||||
shutil.move(os.path.join(tmp_dir, zip_file_name), ".")
|
||||
shutil.rmtree(tmp_dir)
|
||||
|
||||
create_req = tf_paramgen.create_req_by_vnfd_id(vnfd_id)
|
||||
instantiate_req = tf_paramgen.instantiate_req()
|
||||
terminate_req = tf_paramgen.terminate_req()
|
||||
|
||||
with open("create_req", "w", encoding='utf-8') as f:
|
||||
f.write(json.dumps(create_req, indent=2))
|
||||
|
||||
with open("instantiate_req", "w", encoding='utf-8') as f:
|
||||
f.write(json.dumps(instantiate_req, indent=2))
|
||||
|
||||
with open("terminate_req", "w", encoding='utf-8') as f:
|
||||
f.write(json.dumps(terminate_req, indent=2))
|
153
tacker/tests/functional/sol_terraform_v2/test_terraform.py
Normal file
153
tacker/tests/functional/sol_terraform_v2/test_terraform.py
Normal file
@ -0,0 +1,153 @@
|
||||
# Copyright (C) 2023 Nippon Telegraph and Telephone Corporation
|
||||
# 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 tacker.conf
|
||||
|
||||
from tacker.objects import fields
|
||||
from tacker.tests.functional.sol_terraform_v2 import base_v2
|
||||
from tacker.tests.functional.sol_terraform_v2 import paramgen as tf_paramgen
|
||||
|
||||
CONF = tacker.conf.CONF
|
||||
|
||||
|
||||
class VnfLcmTerraformTest(base_v2.BaseVnfLcmTerraformV2Test):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(VnfLcmTerraformTest, cls).setUpClass()
|
||||
|
||||
cur_dir = os.path.dirname(__file__)
|
||||
sample_pkg = "samples/test_terraform_basic"
|
||||
pkg_dir_path = os.path.join(cur_dir, sample_pkg)
|
||||
cls.basic_pkg, cls.basic_vnfd_id = cls.create_vnf_package(pkg_dir_path)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super(VnfLcmTerraformTest, cls).tearDownClass()
|
||||
|
||||
cls.delete_vnf_package(cls.basic_pkg)
|
||||
|
||||
def setUp(self):
|
||||
super(VnfLcmTerraformTest, self).setUp()
|
||||
|
||||
def instantiate_vnf_instance(self, inst_id, req_body):
|
||||
path = "/vnflcm/v2/vnf_instances/{}/instantiate".format(inst_id)
|
||||
return self.tacker_client.do_request(
|
||||
path, "POST", body=req_body, version="2.0.0")
|
||||
|
||||
def terminate_vnf_instance(self, inst_id, req_body):
|
||||
path = "/vnflcm/v2/vnf_instances/{}/terminate".format(inst_id)
|
||||
return self.tacker_client.do_request(
|
||||
path, "POST", body=req_body, version="2.0.0")
|
||||
|
||||
def test_basic_lcms(self):
|
||||
self._get_basic_lcms_procedure()
|
||||
|
||||
def _get_basic_lcms_procedure(self, use_register_vim=False):
|
||||
"""Test basic LCM operations
|
||||
|
||||
* About LCM operations:
|
||||
This test includes the following operations.
|
||||
- 1. Create VNF instance
|
||||
- 2. Instantiate VNF
|
||||
- 3. Show VNF instance
|
||||
- 4. Terminate VNF
|
||||
- 5. Delete a VNF instance
|
||||
"""
|
||||
|
||||
# 1. Create a new VNF instance resource
|
||||
# NOTE: extensions and vnfConfigurableProperties are omitted
|
||||
# because they are commented out in etsi_nfv_sol001.
|
||||
expected_inst_attrs = [
|
||||
'id',
|
||||
'vnfInstanceName',
|
||||
'vnfInstanceDescription',
|
||||
'vnfdId',
|
||||
'vnfProvider',
|
||||
'vnfProductName',
|
||||
'vnfSoftwareVersion',
|
||||
'vnfdVersion',
|
||||
# 'vnfConfigurableProperties', # omitted
|
||||
# 'vimConnectionInfo', # omitted
|
||||
'instantiationState',
|
||||
# 'instantiatedVnfInfo', # omitted
|
||||
'metadata',
|
||||
# 'extensions', # omitted
|
||||
'_links'
|
||||
]
|
||||
create_req = tf_paramgen.create_req_by_vnfd_id(self.basic_vnfd_id)
|
||||
resp, body = self.create_vnf_instance(create_req)
|
||||
self.assertEqual(201, resp.status_code)
|
||||
self.check_resp_headers_in_create(resp)
|
||||
self.check_resp_body(body, expected_inst_attrs)
|
||||
inst_id = body['id']
|
||||
self.check_package_usage(self.basic_pkg, state='IN_USE')
|
||||
|
||||
# 2. Instantiate VNF
|
||||
instantiate_req = tf_paramgen.instantiate_req()
|
||||
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
|
||||
self.assertEqual(202, resp.status_code)
|
||||
self.check_resp_headers_in_operation_task(resp)
|
||||
|
||||
lcmocc_id = os.path.basename(resp.headers['Location'])
|
||||
self.wait_lcmocc_complete(lcmocc_id)
|
||||
|
||||
# 3. Show VNF instance
|
||||
additional_inst_attrs = [
|
||||
'vimConnectionInfo',
|
||||
'instantiatedVnfInfo'
|
||||
]
|
||||
expected_inst_attrs.extend(additional_inst_attrs)
|
||||
resp, body = self.show_vnf_instance(inst_id)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.check_resp_headers_in_get(resp)
|
||||
self.check_resp_body(body, expected_inst_attrs)
|
||||
|
||||
# check instantiationState of VNF
|
||||
self.assertEqual(fields.VnfInstanceState.INSTANTIATED,
|
||||
body['instantiationState'])
|
||||
|
||||
# check vnfState of VNF
|
||||
self.assertEqual(fields.VnfOperationalStateType.STARTED,
|
||||
body['instantiatedVnfInfo']['vnfState'])
|
||||
|
||||
# 4. Terminate VNF instance
|
||||
terminate_req = tf_paramgen.terminate_req()
|
||||
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
|
||||
self.assertEqual(202, resp.status_code)
|
||||
self.check_resp_headers_in_operation_task(resp)
|
||||
|
||||
lcmocc_id = os.path.basename(resp.headers['Location'])
|
||||
self.wait_lcmocc_complete(lcmocc_id)
|
||||
|
||||
# check instantiationState of VNF
|
||||
resp, body = self.show_vnf_instance(inst_id)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual(fields.VnfInstanceState.NOT_INSTANTIATED,
|
||||
body['instantiationState'])
|
||||
|
||||
# 5. Delete a VNF instance
|
||||
resp, body = self.delete_vnf_instance(inst_id)
|
||||
self.assertEqual(204, resp.status_code)
|
||||
self.check_resp_headers_in_delete(resp)
|
||||
|
||||
# check deletion of VNF instance
|
||||
resp, body = self.show_vnf_instance(inst_id)
|
||||
self.assertEqual(404, resp.status_code)
|
||||
self.check_package_usage(self.basic_pkg, state='NOT_IN_USE')
|
||||
|
||||
# TODO(yasufum) consider to add a test for instantiate_rollback here.
|
@ -0,0 +1,236 @@
|
||||
# Copyright (C) 2023 Nippon Telegraph and Telephone Corporation
|
||||
# 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 json
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from oslo_utils import uuidutils
|
||||
from unittest import mock
|
||||
|
||||
from tacker import context
|
||||
from tacker.sol_refactored.common import vnfd_utils
|
||||
from tacker.sol_refactored.infra_drivers.terraform import terraform
|
||||
from tacker.sol_refactored import objects
|
||||
from tacker.sol_refactored.objects.v2 import fields
|
||||
from tacker.tests import base
|
||||
|
||||
SAMPLE_VNFD_ID = "65b62a2a-c207-423f-9b01-f399c9ab5629"
|
||||
SAMPLE_FLAVOUR_ID = "simple"
|
||||
|
||||
_vim_connection_info_example = {
|
||||
"vimId": "terraform_provider_aws_v4_tokyo",
|
||||
"vimType": "ETSINFV.TERRAFORM.V_1",
|
||||
"interfaceInfo": {
|
||||
"providerType": "aws",
|
||||
"providerVersion": "4.0"
|
||||
},
|
||||
"accessInfo": {
|
||||
"region": "ap-northeast-1",
|
||||
"access_key": "example_access_key",
|
||||
"secret_key": "example_secret_key"
|
||||
}
|
||||
}
|
||||
|
||||
_instantiate_req_example = {
|
||||
# instantiateVnfRequest example
|
||||
"flavourId": SAMPLE_FLAVOUR_ID,
|
||||
"vimConnectionInfo": {
|
||||
"vim1": _vim_connection_info_example
|
||||
},
|
||||
"additionalParams": {
|
||||
"tf_dir_path": "Files/terraform",
|
||||
"tf_var_path": "Files/terraform/variables.tf"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TestTerraform(base.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(TestTerraform, self).setUp()
|
||||
objects.register_all()
|
||||
self.driver = terraform.Terraform()
|
||||
self.context = context.get_admin_context()
|
||||
|
||||
cur_dir = os.path.dirname(__file__)
|
||||
sample_dir = os.path.join(cur_dir, "../..", "samples")
|
||||
|
||||
self.vnfd_2 = vnfd_utils.Vnfd(SAMPLE_VNFD_ID)
|
||||
self.vnfd_2.init_from_csar_dir(os.path.join(sample_dir, "sample2"))
|
||||
|
||||
@mock.patch.object(terraform.Terraform, '_get_tf_vnfpkg')
|
||||
@mock.patch.object(terraform.Terraform, '_generate_provider_tf')
|
||||
@mock.patch.object(terraform.Terraform, '_make_instantiated_vnf_info')
|
||||
@mock.patch.object(terraform.Terraform, '_instantiate')
|
||||
def test_instantiate(self, mock_instantiate,
|
||||
mock_make_instantiated_vnf_info,
|
||||
mock_generate_provider_tf,
|
||||
mock_tf_files):
|
||||
'''Verifies instantiate is called once'''
|
||||
|
||||
req = objects.InstantiateVnfRequest.from_dict(_instantiate_req_example)
|
||||
|
||||
inst = objects.VnfInstanceV2(
|
||||
# Required fields
|
||||
id=uuidutils.generate_uuid(),
|
||||
vnfdId=SAMPLE_VNFD_ID,
|
||||
vnfProvider='provider',
|
||||
vnfProductName='product name',
|
||||
vnfSoftwareVersion='software version',
|
||||
vnfdVersion='vnfd version',
|
||||
instantiationState='INSTANTIATED',
|
||||
vimConnectionInfo=req['vimConnectionInfo']
|
||||
)
|
||||
|
||||
grant_req = objects.GrantRequestV1(
|
||||
operation=fields.LcmOperationType.INSTANTIATE,
|
||||
vnfdId=SAMPLE_VNFD_ID
|
||||
)
|
||||
|
||||
grant = objects.GrantV1()
|
||||
|
||||
# Set the desired return value for _get_tf_vnfpkg
|
||||
mock_tf_files.return_value = f"/var/lib/tacker/terraform/{inst.id}"
|
||||
|
||||
# Execute
|
||||
self.driver.instantiate(req, inst, grant_req, grant, self.vnfd_2)
|
||||
|
||||
# TODO(yasufum) Test _instantiate mock subprocess
|
||||
# Verify _instantiate is called once
|
||||
mock_instantiate.assert_called_once_with(
|
||||
req.vimConnectionInfo['vim1'],
|
||||
f"/var/lib/tacker/terraform/{inst.id}",
|
||||
req.additionalParams.get('tf_var_path'))
|
||||
|
||||
def test_make_instantiated_vnf_info(self):
|
||||
'''Verifies instantiated vnf info is correct'''
|
||||
|
||||
req = objects.InstantiateVnfRequest.from_dict(_instantiate_req_example)
|
||||
|
||||
inst = objects.VnfInstanceV2(
|
||||
# Required fields
|
||||
id=uuidutils.generate_uuid(),
|
||||
vnfdId=SAMPLE_VNFD_ID,
|
||||
vnfProvider='provider',
|
||||
vnfProductName='product name',
|
||||
vnfSoftwareVersion='software version',
|
||||
vnfdVersion='vnfd version',
|
||||
instantiationState='INSTANTIATED',
|
||||
vimConnectionInfo=req['vimConnectionInfo']
|
||||
)
|
||||
|
||||
grant_req = objects.GrantRequestV1(
|
||||
operation='INSTANTIATE'
|
||||
)
|
||||
|
||||
grant = objects.GrantV1()
|
||||
|
||||
# Expected results
|
||||
_expected_inst_info = {
|
||||
"flavourId": "simple",
|
||||
"vnfState": "STARTED",
|
||||
"vnfcResourceInfo": [
|
||||
{
|
||||
"id": "vdu1",
|
||||
"vduId": "VDU1",
|
||||
"computeResource": {
|
||||
"resourceId": "vdu1",
|
||||
"vimLevelResourceType": "aws_instance"
|
||||
},
|
||||
"metadata": {}
|
||||
},
|
||||
{
|
||||
"id": "vdu2",
|
||||
"vduId": "VDU2",
|
||||
"computeResource": {
|
||||
"resourceId": "vdu2",
|
||||
"vimLevelResourceType": "aws_instance"
|
||||
},
|
||||
"metadata": {}
|
||||
}
|
||||
],
|
||||
"vnfcInfo": [
|
||||
{
|
||||
"id": "VDU1-vdu1",
|
||||
"vduId": "VDU1",
|
||||
"vnfcResourceInfoId": "vdu1",
|
||||
"vnfcState": "STARTED"
|
||||
},
|
||||
{
|
||||
"id": "VDU2-vdu2",
|
||||
"vduId": "VDU2",
|
||||
"vnfcResourceInfoId": "vdu2",
|
||||
"vnfcState": "STARTED"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Create a temporary directory
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Create the tfstate content
|
||||
provider = "provider[\"registry.terraform.io/hashicorp/aws\"]"
|
||||
tfstate_content = {
|
||||
"version": 4,
|
||||
"terraform_version": "1.4.4",
|
||||
"serial": 4,
|
||||
"lineage": "5745b992-04a2-5811-2e02-19d64f6f4b44",
|
||||
"outputs": {},
|
||||
"resources": [
|
||||
{
|
||||
"mode": "managed",
|
||||
"type": "aws_instance",
|
||||
"name": "vdu1",
|
||||
"provider": provider
|
||||
},
|
||||
{
|
||||
"mode": "managed",
|
||||
"type": "aws_instance",
|
||||
"name": "vdu2",
|
||||
"provider": provider
|
||||
},
|
||||
{
|
||||
"mode": "managed",
|
||||
"type": "aws_subnet",
|
||||
"name": "hoge-subnet01",
|
||||
"provider": provider
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Write the tfstate content to a temporary file
|
||||
tfstate_file_path = f"{temp_dir}/terraform.tfstate"
|
||||
with open(tfstate_file_path, "w") as tfstate_file:
|
||||
json.dump(tfstate_content, tfstate_file)
|
||||
|
||||
# Execute the test with the temporary tfstate_file
|
||||
self.driver._make_instantiated_vnf_info(req, inst,
|
||||
grant_req, grant,
|
||||
self.vnfd_2, temp_dir,
|
||||
req.additionalParams.get(
|
||||
'tf_var_path')
|
||||
)
|
||||
|
||||
# check
|
||||
result = inst.to_dict()["instantiatedVnfInfo"]
|
||||
expected = _expected_inst_info
|
||||
|
||||
# vnfcResourceInfo is sorted by creation_time (reverse)
|
||||
self.assertIn("vnfcResourceInfo", result)
|
||||
self.assertEqual(expected["vnfcResourceInfo"],
|
||||
result["vnfcResourceInfo"])
|
||||
|
||||
# order of vnfcInfo is same as vnfcResourceInfo
|
||||
self.assertIn("vnfcInfo", result)
|
||||
self.assertEqual(expected["vnfcInfo"], result["vnfcInfo"])
|
6
tox.ini
6
tox.ini
@ -145,6 +145,12 @@ commands_post =
|
||||
allowlist_externals =
|
||||
sudo
|
||||
|
||||
[testenv:dsvm-functional-sol-terraform-v2]
|
||||
setenv = {[testenv]setenv}
|
||||
|
||||
commands =
|
||||
stestr --test-path=./tacker/tests/functional/sol_terraform_v2 run --slowest --concurrency 1 {posargs}
|
||||
|
||||
[testenv:dsvm-compliance-sol-api]
|
||||
passenv =
|
||||
{[testenv]passenv}
|
||||
|
Loading…
Reference in New Issue
Block a user