From 66efce28d45f35ad661fb789b5d88a4c2c8b48e4 Mon Sep 17 00:00:00 2001
From: Shubham Potale <shubham.potale@nttdata.com>
Date: Tue, 10 Dec 2019 15:39:20 +0530
Subject: [PATCH] OSC support to create vnf using vnflcm API

Added a new command ``openstack vnflcm create`` to create a new vnf.

Blueprint: support-etsi-nfv-specs
Change-Id: Ia90955df6ac141661c3d58e4de4e098c4cb51aab
---
 lower-constraints.txt                         |  2 +-
 requirements.txt                              |  2 +-
 setup.cfg                                     |  2 +-
 tackerclient/osc/v1/vnflcm/__init__.py        |  0
 tackerclient/osc/v1/vnflcm/vnflcm.py          | 82 ++++++++++++++++++
 tackerclient/tests/unit/osc/v1/test_vnflcm.py | 84 +++++++++++++++++++
 .../tests/unit/osc/v1/vnflcm_fakes.py         | 53 ++++++++++++
 tackerclient/v1_0/client.py                   | 23 +++++
 8 files changed, 245 insertions(+), 3 deletions(-)
 create mode 100644 tackerclient/osc/v1/vnflcm/__init__.py
 create mode 100644 tackerclient/osc/v1/vnflcm/vnflcm.py
 create mode 100644 tackerclient/tests/unit/osc/v1/test_vnflcm.py
 create mode 100644 tackerclient/tests/unit/osc/v1/vnflcm_fakes.py

diff --git a/lower-constraints.txt b/lower-constraints.txt
index da5af261..9d8a4655 100644
--- a/lower-constraints.txt
+++ b/lower-constraints.txt
@@ -34,7 +34,7 @@ oslo.context==2.19.2
 oslo.i18n==3.15.3
 oslo.log==3.36.0
 oslo.serialization==2.18.0
-oslo.utils==3.33.0
+oslo.utils==3.40.0
 pbr==2.0.0
 pep8==1.5.7
 positional==1.2.1
diff --git a/requirements.txt b/requirements.txt
index 621b07f6..d5d0aced 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -14,5 +14,5 @@ Babel!=2.4.0,>=2.3.4 # BSD
 oslo.i18n>=3.15.3 # Apache-2.0
 osc-lib>=1.8.0 # Apache-2.0
 oslo.log>=3.36.0 # Apache-2.0
-oslo.utils>=3.33.0 # Apache-2.0
+oslo.utils>=3.40.0 # Apache-2.0
 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index e6ac5fcd..cb9121b0 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -83,7 +83,7 @@ openstack.tackerclient.v1 =
      vnf_package_show = tackerclient.osc.v1.vnfpkgm.vnf_package:ShowVnfPackage
      vnf_package_upload = tackerclient.osc.v1.vnfpkgm.vnf_package:UploadVnfPackage
      vnf_package_delete = tackerclient.osc.v1.vnfpkgm.vnf_package:DeleteVnfPackage
-
+     vnflcm_create = tackerclient.osc.v1.vnflcm.vnflcm:CreateVnfLcm
 
 [build_releasenotes]
 all_files = 1
diff --git a/tackerclient/osc/v1/vnflcm/__init__.py b/tackerclient/osc/v1/vnflcm/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tackerclient/osc/v1/vnflcm/vnflcm.py b/tackerclient/osc/v1/vnflcm/vnflcm.py
new file mode 100644
index 00000000..efc20682
--- /dev/null
+++ b/tackerclient/osc/v1/vnflcm/vnflcm.py
@@ -0,0 +1,82 @@
+# Copyright (C) 2020 NTT DATA
+# 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 osc_lib.command import command
+from osc_lib import utils
+
+from tackerclient.i18n import _
+from tackerclient.osc import sdk_utils
+
+_mixed_case_fields = ('vnfInstanceName', 'vnfInstanceDescription', 'vnfdId',
+                      'vnfProvider', 'vnfProductName', 'vnfSoftwareVersion',
+                      'vnfdVersion', 'instantiationState')
+
+
+def _get_columns(item):
+    column_map = {
+        'id': 'ID',
+        'vnfInstanceName': 'VNF Instance Name',
+        'vnfInstanceDescription': 'VNF Instance Description',
+        'vnfdId': 'VNFD ID',
+        'vnfProvider': 'VNF Provider',
+        'vnfProductName': 'VNF Product Name',
+        'vnfSoftwareVersion': 'VNF Software Version',
+        'vnfdVersion': 'VNFD Version',
+        'instantiationState': 'Instantiation State',
+        'links': 'Links',
+    }
+    return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
+
+
+class CreateVnfLcm(command.ShowOne):
+    _description = _("Create a new VNF Instance")
+
+    def get_parser(self, prog_name):
+        parser = super(CreateVnfLcm, self).get_parser(prog_name)
+        parser.add_argument(
+            'vnfd_id',
+            metavar="<vnfd-id>",
+            help=_('Identifier that identifies the VNFD which defines the '
+                   'VNF instance to be created.'))
+        parser.add_argument(
+            '--name',
+            metavar="<vnf-instance-name>",
+            help=_('Name of the VNF instance to be created.'))
+        parser.add_argument(
+            '--description',
+            metavar="<vnf-instance-description>",
+            help=_('Description of the VNF instance to be created.'))
+        return parser
+
+    def args2body(self, parsed_args):
+        body = {}
+        body['vnfdId'] = parsed_args.vnfd_id
+
+        if parsed_args.description:
+            body['vnfInstanceDescription'] = parsed_args.description
+
+        if parsed_args.name:
+            body['vnfInstanceName'] = parsed_args.name
+
+        return body
+
+    def take_action(self, parsed_args):
+        client = self.app.client_manager.tackerclient
+        vnf = client.create_vnf_instance(self.args2body(parsed_args))
+        display_columns, columns = _get_columns(vnf)
+        data = utils.get_item_properties(
+            sdk_utils.DictModel(vnf),
+            columns, mixed_case_fields=_mixed_case_fields)
+        return (display_columns, data)
diff --git a/tackerclient/tests/unit/osc/v1/test_vnflcm.py b/tackerclient/tests/unit/osc/v1/test_vnflcm.py
new file mode 100644
index 00000000..25670483
--- /dev/null
+++ b/tackerclient/tests/unit/osc/v1/test_vnflcm.py
@@ -0,0 +1,84 @@
+# Copyright (C) 2020 NTT DATA
+# 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 ddt
+import mock
+import os
+
+from oslo_utils.fixture import uuidsentinel
+
+from tackerclient.osc.v1.vnflcm import vnflcm
+from tackerclient.tests.unit.osc import base
+from tackerclient.tests.unit.osc.v1.fixture_data import client
+from tackerclient.tests.unit.osc.v1 import vnflcm_fakes
+
+
+class TestVnfLcm(base.FixturedTestCase):
+    client_fixture_class = client.ClientFixture
+
+    def setUp(self):
+        super(TestVnfLcm, self).setUp()
+        self.url = client.TACKER_URL
+        self.header = {'content-type': 'application/json'}
+        self.app = mock.Mock()
+        self.app_args = mock.Mock()
+        self.client_manager = self.cs
+        self.app.client_manager.tackerclient = self.client_manager
+
+
+def _get_columns_vnflcm():
+    columns = ['ID', 'Instantiation State', 'VNF Instance Description',
+               'VNF Instance Name', 'VNF Product Name', 'VNF Provider',
+               'VNF Software Version', 'VNFD ID', 'VNFD Version', 'Links']
+    return columns
+
+
+@ddt.ddt
+class TestCreateVnfLcm(TestVnfLcm):
+
+    def setUp(self):
+        super(TestCreateVnfLcm, self).setUp()
+        self.create_vnf_lcm = vnflcm.CreateVnfLcm(
+            self.app, self.app_args, cmd_name='vnflcm create')
+
+    def test_create_no_args(self):
+        self.assertRaises(base.ParserException, self.check_parser,
+                          self.create_vnf_lcm, [], [])
+
+    @ddt.data(True, False)
+    def test_take_action(self, optional_arguments):
+        arglist = [uuidsentinel.vnf_package_vnfd_id]
+        verifylist = [('vnfd_id', uuidsentinel.vnf_package_vnfd_id)]
+
+        if optional_arguments:
+            arglist.extend(['--name', 'test',
+                            '--description', 'test'])
+            verifylist.extend([('name', 'test'),
+                               ('description', 'test')])
+
+        # command param
+        parsed_args = self.check_parser(self.create_vnf_lcm, arglist,
+                                        verifylist)
+
+        json = vnflcm_fakes.vnf_instance_response()
+        self.requests_mock.register_uri(
+            'POST', os.path.join(self.url, 'vnflcm/v1/vnf_instances'),
+            json=json, headers=self.header)
+
+        columns, data = (self.create_vnf_lcm.take_action(parsed_args))
+        self.assertItemsEqual(_get_columns_vnflcm(),
+                              columns)
+        self.assertItemsEqual(vnflcm_fakes.get_vnflcm_data(json),
+                              data)
diff --git a/tackerclient/tests/unit/osc/v1/vnflcm_fakes.py b/tackerclient/tests/unit/osc/v1/vnflcm_fakes.py
new file mode 100644
index 00000000..5f560626
--- /dev/null
+++ b/tackerclient/tests/unit/osc/v1/vnflcm_fakes.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2020 NTT DATA
+# 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.fixture import uuidsentinel
+
+
+def vnf_instance_response(attrs=None):
+    """Create a fake vnf instance.
+
+    :param Dictionary attrs:
+        A dictionary with all attributes
+    :return:
+        A vnf instance dict
+    """
+    attrs = attrs or {}
+
+    # Set default attributes.
+    dummy_vnf_instance = {
+        "id": uuidsentinel.vnf_instance_id,
+        "vnfInstanceName": "Fake-VNF-Instance",
+        "vnfInstanceDescription": "Fake VNF",
+        "vnfdId": uuidsentinel.vnf_package_vnfd_id,
+        "vnfProvider": "NTT NS lab",
+        "vnfProductName": "Sample VNF",
+        "vnfSoftwareVersion": "1.0",
+        "vnfdVersion": "1.0",
+        "instantiationState": "NOT_INSTANTIATED",
+        "links": "vnflcm/v1/vnf_instances/" + uuidsentinel.vnf_instance_id +
+                 "/instantiate"
+    }
+    return dummy_vnf_instance
+
+
+def get_vnflcm_data(vnf_instance):
+    """Get the vnf instance data.
+
+    :return:
+        A tuple object sorted based on the name of the columns.
+    """
+    # return the list of data as per column order
+    return tuple([vnf_instance[key] for key in sorted(vnf_instance.keys())])
diff --git a/tackerclient/v1_0/client.py b/tackerclient/v1_0/client.py
index bc2fd30f..15c6d155 100644
--- a/tackerclient/v1_0/client.py
+++ b/tackerclient/v1_0/client.py
@@ -774,6 +774,23 @@ class VnfPackageClient(ClientBase):
                 body=file_data)
 
 
+class VnfLCMClient(ClientBase):
+    """Client for vnflcm APIs.
+
+    Purpose of this class is to create required request url for vnflcm
+    APIs.
+    """
+
+    vnf_instances_path = '/vnflcm/v1/vnf_instances'
+
+    def build_action(self, action):
+        return action
+
+    @APIParamsCall
+    def create_vnf_instance(self, body):
+        return self.post(self.vnf_instances_path, body=body)
+
+
 class Client(object):
     """Unified interface to interact with multiple applications of tacker service.
 
@@ -794,6 +811,7 @@ class Client(object):
     """
 
     def __init__(self, **kwargs):
+        self.vnf_lcm_client = VnfLCMClient(**kwargs)
         self.vnf_package_client = VnfPackageClient(**kwargs)
         self.legacy_client = LegacyClient(**kwargs)
 
@@ -1018,3 +1036,8 @@ class Client(object):
 
     def delete_vnf_package(self, vnf_package):
         return self.vnf_package_client.delete_vnf_package(vnf_package)
+
+    # VnfLCMClient methods.
+
+    def create_vnf_instance(self, body):
+        return self.vnf_lcm_client.create_vnf_instance(body)