From 9fd7049657992304428d4bcd651c13312479917b Mon Sep 17 00:00:00 2001 From: Shobana Jothi Date: Fri, 7 Sep 2018 19:52:31 +0530 Subject: Operate API in Gvnfm Driver Change-Id: I94551bac941ae155b8322a2fb6b38cca12d5872c Issue-ID: VFC-1051 Signed-off-by: Shobana Jothi --- .../interfaces/serializers/operate_request.py | 36 +++++++++ .../driver/interfaces/serializers/response.py | 23 ++++++ gvnfmadapter/driver/interfaces/tests.py | 90 ++++++++++++++++++++++ gvnfmadapter/driver/interfaces/urls.py | 4 +- gvnfmadapter/driver/interfaces/views.py | 53 +++++++++++++ gvnfmadapter/driver/pub/utils/restcall.py | 14 ++-- 6 files changed, 213 insertions(+), 7 deletions(-) create mode 100644 gvnfmadapter/driver/interfaces/serializers/operate_request.py create mode 100644 gvnfmadapter/driver/interfaces/serializers/response.py diff --git a/gvnfmadapter/driver/interfaces/serializers/operate_request.py b/gvnfmadapter/driver/interfaces/serializers/operate_request.py new file mode 100644 index 0000000..8bbaab8 --- /dev/null +++ b/gvnfmadapter/driver/interfaces/serializers/operate_request.py @@ -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 index 0000000..adcbaa0 --- /dev/null +++ b/gvnfmadapter/driver/interfaces/serializers/response.py @@ -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) diff --git a/gvnfmadapter/driver/interfaces/tests.py b/gvnfmadapter/driver/interfaces/tests.py index 84607a9..7196f3a 100644 --- a/gvnfmadapter/driver/interfaces/tests.py +++ b/gvnfmadapter/driver/interfaces/tests.py @@ -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']) diff --git a/gvnfmadapter/driver/interfaces/urls.py b/gvnfmadapter/driver/interfaces/urls.py index ac39eff..b615d9b 100644 --- a/gvnfmadapter/driver/interfaces/urls.py +++ b/gvnfmadapter/driver/interfaces/urls.py @@ -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[0-9a-zA-Z\-\_]+)/v1/(?P[0-9a-zA-Z\-\_]+)/vnfs$', VnfInstInfo.as_view()), @@ -28,4 +28,6 @@ urlpatterns = [ url(r'^api/(?P[0-9a-zA-Z\-\_]+)/v1/resource/grant$', VnfGrantInfo.as_view()), url(r'^api/(?P[0-9a-zA-Z\-\_]+)/v1/vnfs/lifecyclechangesnotification$', VnfNotifyInfo.as_view()), url(r'^api/(?P[0-9a-zA-Z\-\_]+)/v1/(?P[0-9a-zA-Z\-\_]+)/vnf_lcm_op_occs/(?P[0-9a-zA-Z_-]+)$', QuerySingleVnfLcmOpOcc.as_view()), + url(r'^api/(?P[0-9a-zA-Z\-\_]+)/v1/(?P[0-9a-zA-Z\-\_]+)/vnfs/(?P' + r'[0-9a-zA-Z\-\_]+)/operate$', VnfOperateView.as_view()), ] diff --git a/gvnfmadapter/driver/interfaces/views.py b/gvnfmadapter/driver/interfaces/views.py index 32897c2..b32aa8c 100644 --- a/gvnfmadapter/driver/interfaces/views.py +++ b/gvnfmadapter/driver/interfaces/views.py @@ -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) diff --git a/gvnfmadapter/driver/pub/utils/restcall.py b/gvnfmadapter/driver/pub/utils/restcall.py index 08f4cf3..46defd3 100644 --- a/gvnfmadapter/driver/pub/utils/restcall.py +++ b/gvnfmadapter/driver/pub/utils/restcall.py @@ -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 -- cgit 1.2.3-korg