diff options
author | Litao Gao <litao.gao@windriver.com> | 2017-02-24 05:05:00 -0500 |
---|---|---|
committer | Litao Gao <litao.gao@windriver.com> | 2017-02-24 05:05:24 -0500 |
commit | 69aafd35f48c98c4337c7c37d1eb4aab7d538fe7 (patch) | |
tree | 43097c75edaee9f5fde2ca697502cb38bb8b78d5 /newton | |
parent | d3cdec447d691553a1861a8ac8a2b7d0cf61def1 (diff) |
openstak newton version vim driver implementation
this commit includes the capability of accepting
the 'networks' get request from multivim broker
and do authorization and then send the proper
request to newton openstack instance and do
some converting work upon the response and then
send back the response to multivim borker
Change-Id: If4d3da0d5cc3865a831fa5f1b9effec4da56c3d3
Issue-Id: MULTIVIM-47
Signed-off-by: Litao Gao <litao.gao@windriver.com>
Diffstat (limited to 'newton')
-rw-r--r-- | newton/newton/pub/exceptions.py | 6 | ||||
-rw-r--r-- | newton/newton/pub/msapi/extsys.py | 21 | ||||
-rw-r--r-- | newton/newton/pub/utils/restcall.py | 57 | ||||
-rw-r--r--[-rwxr-xr-x] | newton/newton/pub/utils/share_lock.py | 0 | ||||
-rw-r--r-- | newton/newton/requests/__init__.py | 13 | ||||
-rw-r--r-- | newton/newton/requests/tests/__init__.py | 13 | ||||
-rw-r--r-- | newton/newton/requests/tests/test_reqeust.py | 26 | ||||
-rw-r--r-- | newton/newton/requests/urls.py | 25 | ||||
-rw-r--r-- | newton/newton/requests/views.py | 151 | ||||
-rw-r--r-- | newton/newton/urls.py | 9 |
10 files changed, 295 insertions, 26 deletions
diff --git a/newton/newton/pub/exceptions.py b/newton/newton/pub/exceptions.py index dff87e8a..b750d0a8 100644 --- a/newton/newton/pub/exceptions.py +++ b/newton/newton/pub/exceptions.py @@ -11,4 +11,8 @@ class VimDriverNewtonException(Exception): - pass + def __init__(self, message, status_code="", content=""): + super(VimDriverNewtonException, self).__init__(message) + self.status_code = status_code + self.content = content + pass diff --git a/newton/newton/pub/msapi/extsys.py b/newton/newton/pub/msapi/extsys.py index 748d33a9..297d4716 100644 --- a/newton/newton/pub/msapi/extsys.py +++ b/newton/newton/pub/msapi/extsys.py @@ -19,16 +19,19 @@ logger = logging.getLogger(__name__) def get_vims(): - ret = req_by_msb("/openoapi/extsys/v1/vims", "GET") - if ret[0] != 0: - logger.error("Status code is %s, detail is %s.", ret[2], ret[1]) + retcode, content, status_code = \ + req_by_msb("/openoapi/extsys/v1/vims", "GET") + if retcode != 0: + logger.error("Status code is %s, detail is %s.", status_code, content) raise VimDriverNewtonException("Failed to query VIMs from extsys.") - return json.JSONDecoder().decode(ret[1]) + return json.JSONDecoder().decode(content) def get_vim_by_id(vim_id): - ret = req_by_msb("/openoapi/extsys/v1/vims/%s" % vim_id, "GET") - if ret[0] != 0: - logger.error("Status code is %s, detail is %s.", ret[2], ret[1]) - raise VimDriverNewtonException("Failed to query VIM with id (%s) from extsys." % vim_id) - return json.JSONDecoder().decode(ret[1]) + retcode, content, status_code = \ + req_by_msb("/openoapi/extsys/v1/vims/%s" % vim_id, "GET") + if retcode != 0: + logger.error("Status code is %s, detail is %s.", status_code, content) + raise VimDriverNewtonException( + "Failed to query VIM with id (%s) from extsys." % vim_id) + return json.JSONDecoder().decode(content) diff --git a/newton/newton/pub/utils/restcall.py b/newton/newton/pub/utils/restcall.py index 6f9f9d8b..7f372982 100644 --- a/newton/newton/pub/utils/restcall.py +++ b/newton/newton/pub/utils/restcall.py @@ -19,14 +19,19 @@ import httplib2 from newton.pub.config.config import MSB_SERVICE_IP, MSB_SERVICE_PORT rest_no_auth, rest_oneway_auth, rest_bothway_auth = 0, 1, 2 -HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_202_ACCEPTED = '200', '201', '204', '202' -status_ok_list = [HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_202_ACCEPTED] -HTTP_404_NOTFOUND, HTTP_403_FORBIDDEN, HTTP_401_UNAUTHORIZED, HTTP_400_BADREQUEST = '404', '403', '401', '400' +HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_202_ACCEPTED \ + = '200', '201', '204', '202' +status_ok_list \ + = [HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT, HTTP_202_ACCEPTED] +HTTP_404_NOTFOUND, HTTP_403_FORBIDDEN, \ + HTTP_401_UNAUTHORIZED, HTTP_400_BADREQUEST = '404', '403', '401', '400' +MAX_RETRY_TIME = 3 logger = logging.getLogger(__name__) -def call_req(base_url, user, passwd, auth_type, resource, method, content=''): +def call_req(base_url, user, passwd, auth_type, + resource, method, extra_headers='', content=''): callid = str(uuid.uuid1()) logger.debug("[%s]call_req('%s','%s','%s',%s,'%s','%s','%s')" % ( callid, base_url, user, passwd, auth_type, resource, method, content)) @@ -34,17 +39,31 @@ def call_req(base_url, user, passwd, auth_type, resource, method, content=''): resp_status = '' try: full_url = combine_url(base_url, resource) - headers = {'content-type': 'application/json', 'accept': 'application/json'} + headers = { + 'content-type': 'application/json', + 'accept': 'application/json' + } + + if extra_headers: + headers.update(extra_headers) if user: - headers['Authorization'] = 'Basic ' + ('%s:%s' % (user, passwd)).encode("base64") + headers['Authorization'] = \ + 'Basic ' + ('%s:%s' % (user, passwd)).encode("base64") ca_certs = None - for retry_times in range(3): - http = httplib2.Http(ca_certs=ca_certs, disable_ssl_certificate_validation=(auth_type == rest_no_auth)) + for retry_times in range(MAX_RETRY_TIME): + http = httplib2.Http( + ca_certs=ca_certs, + disable_ssl_certificate_validation=(auth_type == rest_no_auth)) http.follow_all_redirects = True 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') - logger.debug("[%s][%d]status=%s,resp_body=%s)" % (callid, retry_times, resp_status, resp_body)) + 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') + 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] else: @@ -64,7 +83,8 @@ def call_req(base_url, user, passwd, auth_type, resource, method, content=''): 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 + res_info = ("The URL[%s] request failed or is not responding." % + full_url) ret = [3, res_info, resp_status] except: logger.error(traceback.format_exc()) @@ -76,11 +96,22 @@ def call_req(base_url, user, passwd, auth_type, resource, method, content=''): def req_by_msb(resource, method, content=''): base_url = "http://%s:%s/" % (MSB_SERVICE_IP, MSB_SERVICE_PORT) - return call_req(base_url, "", "", rest_no_auth, resource, method, content) + logger.debug("Networks--get::> %s" % "33") + return call_req(base_url, "", "", rest_no_auth, + resource, method, "", content) + + +def req_to_vim(base_url, resource, method, extra_headers='', content=''): + return call_req(base_url, "", "", rest_no_auth, + resource, method, extra_headers, content) def combine_url(base_url, resource): full_url = None + + if not resource: + return base_url + if base_url.endswith('/') and resource.startswith('/'): full_url = base_url[:-1] + resource elif base_url.endswith('/') and not resource.startswith('/'): diff --git a/newton/newton/pub/utils/share_lock.py b/newton/newton/pub/utils/share_lock.py index aba599a2..aba599a2 100755..100644 --- a/newton/newton/pub/utils/share_lock.py +++ b/newton/newton/pub/utils/share_lock.py diff --git a/newton/newton/requests/__init__.py b/newton/newton/requests/__init__.py new file mode 100644 index 00000000..48b6e44b --- /dev/null +++ b/newton/newton/requests/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2017 Wind River Systems, Inc. +# +# 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. diff --git a/newton/newton/requests/tests/__init__.py b/newton/newton/requests/tests/__init__.py new file mode 100644 index 00000000..48b6e44b --- /dev/null +++ b/newton/newton/requests/tests/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2017 Wind River Systems, Inc. +# +# 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. diff --git a/newton/newton/requests/tests/test_reqeust.py b/newton/newton/requests/tests/test_reqeust.py new file mode 100644 index 00000000..bae2b84b --- /dev/null +++ b/newton/newton/requests/tests/test_reqeust.py @@ -0,0 +1,26 @@ +# Copyright (c) 2017 Wind River Systems, Inc. +# +# 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 django.test import TestCase + + +class TestNetworksRequest(TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def assert_true_result(self): + self.assertTrue(1) diff --git a/newton/newton/requests/urls.py b/newton/newton/requests/urls.py new file mode 100644 index 00000000..f832eec4 --- /dev/null +++ b/newton/newton/requests/urls.py @@ -0,0 +1,25 @@ +# Copyright (c) 2017 Wind River Systems, Inc. +# +# 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 django.conf.urls import url +from rest_framework.urlpatterns import format_suffix_patterns + +from . import views + +urlpatterns = [ + url(r'^networks(/(?P<networkid>[0-9a-zA-Z_-]+))?', views.Networks.as_view()), + url(r'^subnets/(?P<subnetid>[0-9a-zA-Z_-]+)', views.Subnets.as_view()), +] + +urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/newton/newton/requests/views.py b/newton/newton/requests/views.py new file mode 100644 index 00000000..0333f86c --- /dev/null +++ b/newton/newton/requests/views.py @@ -0,0 +1,151 @@ +# Copyright (c) 2017 Wind River Systems, Inc. +# +# 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 logging +import json +from json import JSONEncoder + +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from newton.pub.utils.restcall import req_to_vim +from newton.pub.exceptions import VimDriverNewtonException +from newton.pub.msapi.extsys import get_vim_by_id + +logger = logging.getLogger(__name__) + + +class VimDriverUtils(object): + @staticmethod + def get_vim_info(vimid): + # get vim info from local cache firstly + # if cache miss, get it from ESR service + vim = get_vim_by_id(vimid) + return vim + + @staticmethod + def relay_request_to_vim_service(vimid, tenantid, request, service_type, + req_resouce, req_content=""): + """ + if there is no token cache, do auth + if there is, use token directly + if response is 'need auth', do auth + get the use extractor to get token + and service rul and then do request + """ + vim = VimDriverUtils.get_vim_info(vimid) + auth_resouce = "/tokens" + method = "POST" + headers = "" + r_content_dict = { + "auth": { + "tenantName": vim["tenant"], + "passwordCredentials": { + "username": vim["userName"], + "password": vim["password"] + } + } + } + r_content = JSONEncoder().encode(r_content_dict) + retcode, content, status_code = \ + req_to_vim(vim["url"], auth_resouce, method, headers, r_content) + if retcode != 0: + logger.error("Status code is %s, detail is %s.", + status_code, content) + raise VimDriverNewtonException("Fail to authorize", + status_code, content) + else: + # extract token id and issue the get request + json_content = None + auth_resp = json.JSONDecoder().decode(content) + tokenid, svcurl = VimDriverUtils.extractor(auth_resp, service_type) + method = request.method + headers = {'X-Auth-Token': tokenid} + retcode, content, status_code = \ + req_to_vim(svcurl, req_resouce, method, headers, req_content) + if retcode != 0: + logger.error("Status code is %s, detail is %s.", + status_code, content) + raise VimDriverNewtonException("Fail to complte request", + status_code, content) + else: + json_content = json.JSONDecoder().decode(content) + vim_dict = { + "vimName": vim["name"], + "vimId": vim["vimId"], + "tenantId": tenantid, + } + json_content.update(vim_dict) + return status_code, json_content + + @staticmethod + def extractor(resp_data, service_type): + try: + tokenid = resp_data["access"]["token"]["id"] + sc = resp_data["access"]["serviceCatalog"] + service = [svc for svc in sc if svc["type"] == service_type] + return tokenid, service[0]["endpoints"][0]["publicURL"] + except Exception: + raise Exception( + "There is no valid %s token or service info" % service_type) + + +class Networks(APIView): + SERVICE = "network" + keys_map_resp = [ + ("provider:segmentation_id", "segmentationId"), + ("provider:physical_network", "physicalNetwork"), + ("router:external", "routerExternal"), + ("provider:network_type", "networkType"), + ("vlan_transparent", "vlanTransparent"), + ] + + def get(self, request, vimid="", tenantid="", networkid=""): + logger.debug("Networks--get::> %s" % request.data) + try: + # prepare request resource to vim instance + req_resouce = "v2.0/networks" + full_path = request.get_full_path() + if '?' in full_path: + _, query = request.get_full_path().split('?') + req_resouce += "?%s" % query + status_code, content = VimDriverUtils.relay_request_to_vim_service( + vimid, tenantid, request, self.SERVICE, req_resouce) + + logger.debug("response content after11: %s" % content) + # convert the key naming in networks + for network in content["networks"]: + for k in self.keys_map_resp: + v = network.pop(k[0], None) + if v: + network[k[1]] = v + logger.debug("response content after: %s" % content) + return Response(data=content, status=status_code) + except VimDriverNewtonException as e: + return Response(data=e.content, status=e.status_code) + except Exception as e: + return Response(data={'error': e}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + def post(self, request, tenantid=""): + logger.debug("Networks--post::> %s" % request.data) + pass + + def delete(self, request): + logger.debug("Networks--delete::> %s" % request.data) + pass + + +class Subnets(APIView): + pass diff --git a/newton/newton/urls.py b/newton/newton/urls.py index 94ca0bc4..ad2979ff 100644 --- a/newton/newton/urls.py +++ b/newton/newton/urls.py @@ -10,16 +10,19 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. from django.conf.urls import include, url -from newton.pub.config.config import REG_TO_MSB_WHEN_START, REG_TO_MSB_REG_URL, REG_TO_MSB_REG_PARAM +from newton.pub.config.config \ + import REG_TO_MSB_WHEN_START, REG_TO_MSB_REG_URL, REG_TO_MSB_REG_PARAM urlpatterns = [ url(r'^', include('newton.swagger.urls')), url(r'^', include('newton.samples.urls')), + url(r'^openoapi/multivim-newton/v1/(?P<vimid>[0-9a-zA-Z_-]+)/' + '(?P<tenantid>[0-9a-zA-Z_-]+)/', include('newton.requests.urls')), ] -#url(r'^', include('newton.forward.urls')), # regist to MSB when startup if REG_TO_MSB_WHEN_START: import json from newton.pub.utils.restcall import req_by_msb - req_by_msb(REG_TO_MSB_REG_URL, "POST", json.JSONEncoder().encode(REG_TO_MSB_REG_PARAM)) + req_by_msb(REG_TO_MSB_REG_URL, "POST", + json.JSONEncoder().encode(REG_TO_MSB_REG_PARAM)) |