Fix Terraform infra-driver instantiate rollback

This patch fixes instantiate rollback operation fails for
Terraform infra-driver. The main fixes are as follows:

- Change it to use "InstantiateVnfRequest" instead of
  "instantiatedVnfInfo". This is for bug fixes.
- Organized the conditions for call of terminate function.
  This is because terminate operation fails depending on
  the timing of the failure.

Also, functional test for instantiate rollback operation is not
implemented in current Terraform infra-driver, so add it.

Closes-Bug: #2040338
Change-Id: Id22a77da5825edabfa5358ea68df0cf92bc9c17f
This commit is contained in:
Naoaki Horie 2023-10-25 01:47:11 +00:00
parent bd5dcda608
commit f40a63e49c
4 changed files with 134 additions and 25 deletions

View File

@ -99,7 +99,24 @@ class Terraform():
def instantiate_rollback(self, req, inst, grant_req, grant, vnfd):
'''Calls terminate'''
self.terminate(req, inst, grant_req, grant, vnfd)
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)
# NOTE: Checks the terraform.tfstate file state to call
# the terminate function. The reason is as follows.
# - For fails terraform apply command, the size of
# the terraform.tfstate file is 0.
# - For fails before terraform apply command,
# terraform.tfstate file does not exist.
tfstate_file = f"{working_dir}/terraform.tfstate"
if os.path.exists(tfstate_file):
if os.path.getsize(tfstate_file) != 0:
self._terminate(vim_conn_info, working_dir, tf_var_path)
else:
shutil.rmtree(working_dir)
def change_vnfpkg(self, req, inst, grant_req, grant, vnfd):
'''Calls Terraform Apply and replicates new files'''

View File

@ -13,12 +13,21 @@
# License for the specific language governing permissions and limitations
# under the License.
import functools
import os
import pickle
import sys
class SampleScript(object):
class FailScript(object):
"""Define error method for each operation
For example:
def instantiate_start(self):
if os.path.exists('/tmp/instantiate_start')
raise Exception('test instantiate_start error')
"""
def __init__(self, req, inst, grant_req, grant, csar_dir):
self.req = req
@ -27,17 +36,12 @@ class SampleScript(object):
self.grant = grant
self.csar_dir = csar_dir
def instantiate_start(self):
pass
def _fail(self, method):
if os.path.exists(f'/tmp/{method}'):
raise Exception(f'test {method} error')
def instantiate_end(self):
pass
def terminate_start(self):
pass
def terminate_end(self):
pass
def __getattr__(self, name):
return functools.partial(self._fail, name)
def main():
@ -50,11 +54,8 @@ def main():
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))
script = FailScript(req, inst, grant_req, grant, csar_dir)
getattr(script, operation)()
if __name__ == "__main__":

View File

@ -333,4 +333,55 @@ class VnfLcmTerraformTest(base_v2.BaseVnfLcmTerraformV2Test):
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.
def test_instantiate_rollback(self):
"""Test rollback operation for instantiation.
* About LCM operations:
This test includes the following operations.
- 1. Create VNF instance
- 2. Instantiate VNF => FAILED_TEMP
- 3. Show VNF instance
- 4. Rollback instantiate
- 5. Show VNF instance
- 6. Delete a VNF instance
"""
# 1. Create VNF instance
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)
inst_id = body['id']
# 2. Instantiate VNF => FAILED_TEMP
self.put_fail_file('instantiate_end')
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_failed_temp(lcmocc_id)
self.rm_fail_file('instantiate_end')
# 3. Show VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.assertEqual('NOT_INSTANTIATED', body['instantiationState'])
# 4. Rollback instantiate
resp, body = self.rollback_lcmocc(lcmocc_id)
self.assertEqual(202, resp.status_code)
self.wait_lcmocc_rolled_back(lcmocc_id)
# 5. Show VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.assertEqual('NOT_INSTANTIATED', body['instantiationState'])
# 6. 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)

View File

@ -166,8 +166,10 @@ class TestTerraform(base.BaseTestCase):
f"/var/lib/tacker/terraform/{inst.id}",
inst.instantiatedVnfInfo.metadata['tf_var_path'])
@mock.patch.object(terraform.Terraform, '_get_tf_vnfpkg')
@mock.patch.object(terraform.Terraform, '_terminate')
def test_instantiate_rollback(self, mock_instantiate_rollback):
def test_instantiate_rollback(self, mock_instantiate_rollback,
mock_working_dir):
'''Verifies instantiate_rollback is called once'''
req = objects.InstantiateVnfRequest.from_dict(_instantiate_req_example)
@ -190,19 +192,57 @@ class TestTerraform(base.BaseTestCase):
)
grant_req = objects.GrantRequestV1(
operation=fields.LcmOperationType.INSTANTIATE
operation=fields.LcmOperationType.INSTANTIATE,
vnfdId=SAMPLE_VNFD_ID
)
grant = objects.GrantV1()
# Execute
self.driver.instantiate_rollback(req, inst, grant_req,
grant, self.vnfd_2)
# 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_subnet",
"name": "hoge-subnet01",
"provider": provider
}
]
}
# Set the desired return value for _get_tf_vnfpkg
mock_working_dir.return_value = f"{temp_dir}/{inst.id}"
os.mkdir(mock_working_dir())
# Write the tfstate content to a temporary file
tfstate_file_path = os.path.join(mock_working_dir(),
'terraform.tfstate')
with open(tfstate_file_path, "w") as tfstate_file:
json.dump(tfstate_content, tfstate_file)
# Execute
self.driver.instantiate_rollback(req, inst, grant_req,
grant, self.vnfd_2)
# Verify _terminate is called once
mock_instantiate_rollback.assert_called_once_with(
req.vimConnectionInfo["vim1"],
f"/var/lib/tacker/terraform/{inst.id}",
inst.instantiatedVnfInfo.metadata['tf_var_path'])
f"{temp_dir}/{inst.id}",
req.additionalParams.get('tf_var_path'))
@mock.patch.object(terraform.Terraform, '_get_tf_vnfpkg')
@mock.patch.object(terraform.Terraform, '_generate_provider_tf')