Operate API in Gvnfm Driver 15/65215/6
authorShobana Jothi <shobana.jothi@verizon.com>
Fri, 7 Sep 2018 14:22:31 +0000 (19:52 +0530)
committerShobana Jothi <shobana.jothi@verizon.com>
Mon, 17 Sep 2018 07:10:41 +0000 (12:40 +0530)
Change-Id: I94551bac941ae155b8322a2fb6b38cca12d5872c
Issue-ID: VFC-1051
Signed-off-by: Shobana Jothi<shobana.jothi@verizon.com>
gvnfmadapter/driver/interfaces/serializers/operate_request.py [new file with mode: 0644]
gvnfmadapter/driver/interfaces/serializers/response.py [new file with mode: 0644]
gvnfmadapter/driver/interfaces/tests.py
gvnfmadapter/driver/interfaces/urls.py
gvnfmadapter/driver/interfaces/views.py
gvnfmadapter/driver/pub/utils/restcall.py

diff --git a/gvnfmadapter/driver/interfaces/serializers/operate_request.py b/gvnfmadapter/driver/interfaces/serializers/operate_request.py
new file mode 100644 (file)
index 0000000..8bbaab8
--- /dev/null
@@ -0,0 +1,36 @@
+# Copyright (C) 2018 Verizon. 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 rest_framework import serializers
+
+
+class VnfOperateRequestSerializer(serializers.Serializer):
+    changeStateTo = serializers.ChoiceField(
+        help_text="The desired operational state (i.e. started or stopped) to change the VNF to.",
+        choices=["STARTED", "STOPPED"],
+        required=True)
+    stopType = serializers.ChoiceField(
+        help_text="It signals whether forceful or graceful stop is requested.",
+        choices=["FORCEFUL", "GRACEFUL"],
+        required=False)
+    gracefulStopTimeout = serializers.IntegerField(
+        help_text="The time interval to wait for the VNF to be taken out of service during graceful stop.",
+        required=False)
+    additionalParams = serializers.DictField(
+        help_text="Additional input parameters for the operate process, \
+        specific to the VNF being operated, \
+        as declared in the VNFD as part of OperateVnfOpConfig.",
+        child=serializers.CharField(help_text="", allow_blank=True),
+        required=False,
+        allow_null=True)
diff --git a/gvnfmadapter/driver/interfaces/serializers/response.py b/gvnfmadapter/driver/interfaces/serializers/response.py
new file mode 100644 (file)
index 0000000..adcbaa0
--- /dev/null
@@ -0,0 +1,23 @@
+# Copyright (C) 2018 Verizon. 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 rest_framework import serializers
+
+
+class ProblemDetailsSerializer(serializers.Serializer):
+    type = serializers.CharField(help_text="Type", required=False, allow_null=True)
+    title = serializers.CharField(help_text="Title", required=False, allow_null=True)
+    status = serializers.IntegerField(help_text="Status", required=True)
+    detail = serializers.CharField(help_text="Detail", required=True, allow_null=True)
+    instance = serializers.CharField(help_text="Instance", required=False, allow_null=True)
index 84607a9..7196f3a 100644 (file)
@@ -728,3 +728,93 @@ class InterfacesTest(TestCase):
             content_type='application/json'
         )
         self.assertEqual(status.HTTP_500_INTERNAL_SERVER_ERROR, response.status_code)
+
+
+# Operate API Test case
+    @mock.patch.object(restcall, 'call_req')
+    def test_operate_vnf_404_NotFound(self, mock_call_req):
+        vnfm_info = {
+            "vnfmId": "19ecbb3a-3242-4fa3-9926-8dfb7ddc29ee",
+            "name": "g_vnfm",
+            "type": "gvnfmdriver",
+            "vimId": "",
+            "vendor": "vendor1",
+            "version": "v1.0",
+            "description": "vnfm",
+            "certificateUrl": "",
+            "url": "http://10.74.44.11",
+            "userName": "admin",
+            "password": "admin",
+            "createTime": "2016-07-06 15:33:18"
+        }
+        req_data = {
+            "changeStateTo": "STARTED"
+        }
+        probDetail = {"status": 404, "detail": "VNF Instance not found"}
+        r1 = [0, json.JSONEncoder().encode(vnfm_info), "200", ""]
+        r2 = [1, json.JSONEncoder().encode(probDetail), "404", ""]
+        mock_call_req.side_effect = [r1, r2]
+        response = self.client.post("/api/gvnfmdriver/v1/vnfmid/vnfs/2/operate",
+                                    data=json.dumps(req_data), content_type="application/json")
+        self.assertEqual(status.HTTP_404_NOT_FOUND, response.status_code)
+        self.assertEqual(probDetail, response.data)
+
+    @mock.patch.object(restcall, 'call_req')
+    def test_operate_vnf_409_Conflict(self, mock_call_req):
+        vnfm_info = {
+            "vnfmId": "19ecbb3a-3242-4fa3-9926-8dfb7ddc29ee",
+            "name": "g_vnfm",
+            "type": "gvnfmdriver",
+            "vimId": "",
+            "vendor": "vendor1",
+            "version": "v1.0",
+            "description": "vnfm",
+            "certificateUrl": "",
+            "url": "http://10.74.44.11",
+            "userName": "admin",
+            "password": "admin",
+            "createTime": "2016-07-06 15:33:18"
+        }
+        req_data = {
+            "changeStateTo": "STOPPED",
+            "stopType": "GRACEFUL",
+            "gracefulStopTimeout": 2
+        }
+        probDetail = {"status": 409, "detail": "VNF Instance not in Instantiated State"}
+        r1 = [0, json.JSONEncoder().encode(vnfm_info), "200", ""]
+        r2 = [1, json.JSONEncoder().encode(probDetail), "409", ""]
+        mock_call_req.side_effect = [r1, r2]
+        response = self.client.post("/api/gvnfmdriver/v1/vnfmid/vnfs/2/operate",
+                                    data=json.dumps(req_data), content_type="application/json")
+        self.assertEqual(status.HTTP_409_CONFLICT, response.status_code)
+        self.assertEqual(probDetail, response.data)
+
+    @mock.patch.object(restcall, 'call_req')
+    def test_operate_vnf_success(self, mock_call_req):
+        vnfm_info = {
+            "vnfmId": "19ecbb3a-3242-4fa3-9926-8dfb7ddc29ee",
+            "name": "g_vnfm",
+            "type": "gvnfmdriver",
+            "vimId": "",
+            "vendor": "vendor1",
+            "version": "v1.0",
+            "description": "vnfm",
+            "certificateUrl": "",
+            "url": "http://10.74.44.11",
+            "userName": "admin",
+            "password": "admin",
+            "createTime": "2016-07-06 15:33:18"
+        }
+        req_data = {
+            "changeStateTo": "STOPPED",
+            "stopType": "GRACEFUL",
+            "gracefulStopTimeout": 2
+        }
+        r1 = [0, json.JSONEncoder().encode(vnfm_info), "200", ""]
+        r2 = [0, json.JSONEncoder().encode(''), "202", "/vnf_lc_ops/NF-OPERATE-12-2a3be946-b01d-11e8-9302-08002705b121"]
+        mock_call_req.side_effect = [r1, r2]
+        response = self.client.post("/api/gvnfmdriver/v1/vnfmid/vnfs/2/operate",
+                                    data=json.dumps(req_data), content_type="application/json")
+        self.assertEqual(status.HTTP_202_ACCEPTED, response.status_code)
+        self.assertEqual(None, response.data)
+        self.assertEqual("/vnf_lc_ops/NF-OPERATE-12-2a3be946-b01d-11e8-9302-08002705b121", response['Location'])
index ac39eff..b615d9b 100644 (file)
@@ -15,7 +15,7 @@
 from django.conf.urls import url
 from driver.interfaces.views import VnfInstInfo, VnfTermInfo, VnfQueryInfo, VnfOperInfo
 from driver.interfaces.views import Subscription
-from driver.interfaces.views import VnfPkgsInfo, VnfGrantInfo, VnfNotifyInfo, QuerySingleVnfLcmOpOcc
+from driver.interfaces.views import VnfPkgsInfo, VnfGrantInfo, VnfNotifyInfo, QuerySingleVnfLcmOpOcc, VnfOperateView
 
 urlpatterns = [
     url(r'^api/(?P<vnfmtype>[0-9a-zA-Z\-\_]+)/v1/(?P<vnfmid>[0-9a-zA-Z\-\_]+)/vnfs$', VnfInstInfo.as_view()),
@@ -28,4 +28,6 @@ urlpatterns = [
     url(r'^api/(?P<vnfmtype>[0-9a-zA-Z\-\_]+)/v1/resource/grant$', VnfGrantInfo.as_view()),
     url(r'^api/(?P<vnfmtype>[0-9a-zA-Z\-\_]+)/v1/vnfs/lifecyclechangesnotification$', VnfNotifyInfo.as_view()),
     url(r'^api/(?P<vnfmtype>[0-9a-zA-Z\-\_]+)/v1/(?P<vnfmid>[0-9a-zA-Z\-\_]+)/vnf_lcm_op_occs/(?P<lcmopoccid>[0-9a-zA-Z_-]+)$', QuerySingleVnfLcmOpOcc.as_view()),
+    url(r'^api/(?P<vnfmtype>[0-9a-zA-Z\-\_]+)/v1/(?P<vnfmid>[0-9a-zA-Z\-\_]+)/vnfs/(?P<vnfInstanceId>'
+        r'[0-9a-zA-Z\-\_]+)/operate$', VnfOperateView.as_view()),
 ]
index 32897c2..b32aa8c 100644 (file)
@@ -34,6 +34,8 @@ from driver.interfaces.serializers.lccn_subscription_request import LccnSubscrip
 from driver.pub.exceptions import GvnfmDriverException
 from driver.pub.utils import restcall
 from driver.pub.utils.restcall import req_by_msb
+from driver.interfaces.serializers.operate_request import VnfOperateRequestSerializer
+from driver.interfaces.serializers.response import ProblemDetailsSerializer
 
 logger = logging.getLogger(__name__)
 
@@ -299,6 +301,46 @@ class VnfNotifyInfo(APIView):
             return Response(data={'error': 'unexpected exception'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
 
 
+class VnfOperateView(APIView):
+    @swagger_auto_schema(
+        request_body=VnfOperateRequestSerializer(),
+        responses={
+            status.HTTP_202_ACCEPTED: "Success",
+            status.HTTP_404_NOT_FOUND: ProblemDetailsSerializer(),
+            status.HTTP_409_CONFLICT: ProblemDetailsSerializer(),
+            status.HTTP_500_INTERNAL_SERVER_ERROR: "Internal error"
+        }
+    )
+    def post(self, request, vnfmtype, vnfmid, vnfInstanceId):
+        logger.debug("operate_vnf--post::> %s" % request.data)
+        logger.debug("Operate vnf begin!")
+        try:
+            requestSerializer = VnfOperateRequestSerializer(data=request.data)
+            request_isValid = requestSerializer.is_valid()
+            if not request_isValid:
+                raise Exception(requestSerializer.errors)
+            logger.debug("Operate vnf start!")
+            logger.debug("do_operate: vnfmid=[%s],vnfInstanceId=[%s],request data=[%s]",
+                         vnfmid, vnfInstanceId, request.data)
+            statusCode, resp, location = do_lcmVnf(vnfmid, vnfInstanceId, request.data, "operate")
+            logger.debug("do_operate: response data=[%s]", resp)
+            logger.debug("Operate vnf end!")
+            ret = int(statusCode)
+            if ret == status.HTTP_404_NOT_FOUND:
+                return Response(data=resp, status=status.HTTP_404_NOT_FOUND)
+            elif ret == status.HTTP_409_CONFLICT:
+                return Response(data=resp, status=status.HTTP_409_CONFLICT)
+            response = Response(data=None, status=status.HTTP_202_ACCEPTED)
+            response["Location"] = location
+            return response
+        except GvnfmDriverException as e:
+            logger.error('operate vnf failed, detail message: %s' % e.message)
+            return Response(data={'error': e.message}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+        except:
+            logger.error(traceback.format_exc())
+            return Response(data={'error': 'unexpected exception'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
 class VnfPkgsInfo(APIView):
     def get(request, *args, **kwargs):
         try:
@@ -518,6 +560,17 @@ def do_deletevnf(vnfm_id, vnfInstanceId, data):
     return json.JSONDecoder().decode(ret[1])
 
 
+def do_lcmVnf(vnfm_id, vnfInstanceId, data, lcmType):
+    logger.debug("[%s] request.data=%s", fun_name(), data)
+    vnfm_info = get_vnfminfo_from_nslcm(vnfm_id)
+    logger.debug("[do_lcmVnf] vnfm_info=[%s]", vnfm_info)
+    ret = call_vnfm("api/vnflcm/v1/vnf_instances/%s/%s" % (vnfInstanceId, lcmType), "POST", vnfm_info, data)
+    if ret[0] != 0 and int(ret[2]) != status.HTTP_404_NOT_FOUND and int(ret[2]) != status.HTTP_409_CONFLICT:
+        logger.error("Status code is %s, detail is %s.", ret[2], ret[1])
+        raise GvnfmDriverException('Failed to Operate vnf.')
+    return (ret[2], json.JSONDecoder().decode(ret[1]), ret[3])
+
+
 def do_queryvnf(data, vnfm_id, vnfInstanceId):
     logger.debug("[%s] request.data=%s", fun_name(), data)
     vnfm_info = get_vnfminfo_from_nslcm(vnfm_id)
index 08f4cf3..46defd3 100644 (file)
@@ -34,6 +34,7 @@ def call_req(base_url, user, passwd, auth_type, resource, method, content=''):
     logger.debug("[%s]call_req('%s','%s','%s',%s,'%s','%s','%s')" % (
         callid, base_url, user, passwd, auth_type, resource, method, content))
     ret = None
+    resp_Location = ''
     resp_status = ''
     try:
         full_url = combine_url(base_url, resource)
@@ -47,31 +48,32 @@ def call_req(base_url, user, passwd, auth_type, resource, method, content=''):
             try:
                 resp, resp_content = http.request(full_url, method=method.upper(), body=content, headers=headers)
                 resp_status, resp_body = resp['status'], resp_content.decode('UTF-8')
+                resp_Location = resp.get('Location', "")
                 logger.debug("[%s][%d]status=%s,resp_body=%s)" % (callid, retry_times, resp_status, resp_body))
                 if resp_status in status_ok_list:
-                    ret = [0, resp_body, resp_status]
+                    ret = [0, resp_body, resp_status, resp_Location]
                 else:
-                    ret = [1, resp_body, resp_status]
+                    ret = [1, resp_body, resp_status, resp_Location]
                 break
             except Exception as ex:
                 if 'httplib.ResponseNotReady' in str(sys.exc_info()):
                     logger.debug("retry_times=%d", retry_times)
                     logger.error(traceback.format_exc())
-                    ret = [1, "Unable to connect to %s" % full_url, resp_status]
+                    ret = [1, "Unable to connect to %s" % full_url, resp_status, resp_Location]
                     continue
                 raise ex
     except urllib2.URLError as err:
-        ret = [2, str(err), resp_status]
+        ret = [2, str(err), resp_status, resp_Location]
     except Exception as ex:
         logger.error(traceback.format_exc())
         logger.error("[%s]ret=%s" % (callid, str(sys.exc_info())))
         res_info = str(sys.exc_info())
         if 'httplib.ResponseNotReady' in res_info:
             res_info = "The URL[%s] request failed or is not responding." % full_url
-        ret = [3, res_info, resp_status]
+        ret = [3, res_info, resp_status, resp_Location]
     except:
         logger.error(traceback.format_exc())
-        ret = [4, str(sys.exc_info()), resp_status]
+        ret = [4, str(sys.exc_info()), resp_status, resp_Location]
 
     logger.debug("[%s]ret=%s" % (callid, str(ret)))
     return ret