From cb7770b35559af5d63cf44e20ea425b89046c096 Mon Sep 17 00:00:00 2001 From: Bin Yang Date: Tue, 29 Aug 2017 16:18:45 +0800 Subject: Add OpenStack proxy for newton refactor seed code for newton add proxy for identiy,service, add registration, extension management Issue-Id: MULTICLOUD-58 Change-Id: I6a7a21427af4c88b7f060470c1176009c13fc19e Signed-off-by: Bin Yang --- License.txt | 89 ++++ newton/README.md | 2 +- newton/assembly.xml | 1 - newton/newton/extensions/__init__.py | 10 + newton/newton/extensions/urls.py | 28 ++ newton/newton/extensions/views/__init__.py | 10 + newton/newton/extensions/views/epacaps.py | 65 +++ newton/newton/extensions/views/extensions.py | 76 +++ newton/newton/proxy/__init__.py | 10 + newton/newton/proxy/tests/__init__.py | 13 + newton/newton/proxy/tests/test_identity_proxy.py | 533 +++++++++++++++++++++ newton/newton/proxy/urls.py | 34 ++ newton/newton/proxy/views/__init__.py | 10 + newton/newton/proxy/views/identityV3.py | 202 ++++++++ newton/newton/proxy/views/services.py | 502 +++++++++++++++++++ newton/newton/pub/config/config.py | 30 +- newton/newton/pub/msapi/extsys.py | 78 ++- newton/newton/registration/__init__.py | 10 + newton/newton/registration/views/__init__.py | 10 + newton/newton/registration/views/registration.py | 93 ++++ newton/newton/requests/views/util.py | 134 +++++- newton/newton/settings.py | 21 +- newton/newton/swagger/multivim.flavor.swagger.json | 2 +- newton/newton/swagger/multivim.host.swagger.json | 2 +- newton/newton/swagger/multivim.image.swagger.json | 2 +- newton/newton/swagger/multivim.limit.swagger.json | 2 +- .../newton/swagger/multivim.network.swagger.json | 2 +- newton/newton/swagger/multivim.server.swagger.json | 2 +- newton/newton/swagger/multivim.subnet.swagger.json | 2 +- newton/newton/swagger/multivim.tenant.swagger.json | 2 +- newton/newton/swagger/multivim.volume.swagger.json | 2 +- newton/newton/swagger/multivim.vport.swagger.json | 2 +- newton/newton/swagger/tests.py | 2 +- newton/newton/swagger/urls.py | 2 +- newton/newton/swagger/views.py | 2 +- newton/newton/urls.py | 21 +- newton/pom.xml | 16 +- newton/requirements.txt | 3 + newton/tox.ini | 2 + pom.xml | 9 +- 40 files changed, 1926 insertions(+), 112 deletions(-) create mode 100644 newton/newton/extensions/__init__.py create mode 100644 newton/newton/extensions/urls.py create mode 100644 newton/newton/extensions/views/__init__.py create mode 100644 newton/newton/extensions/views/epacaps.py create mode 100644 newton/newton/extensions/views/extensions.py create mode 100644 newton/newton/proxy/__init__.py create mode 100644 newton/newton/proxy/tests/__init__.py create mode 100644 newton/newton/proxy/tests/test_identity_proxy.py create mode 100644 newton/newton/proxy/urls.py create mode 100644 newton/newton/proxy/views/__init__.py create mode 100644 newton/newton/proxy/views/identityV3.py create mode 100644 newton/newton/proxy/views/services.py create mode 100644 newton/newton/registration/__init__.py create mode 100644 newton/newton/registration/views/__init__.py create mode 100644 newton/newton/registration/views/registration.py diff --git a/License.txt b/License.txt index dd08487b..94e53422 100644 --- a/License.txt +++ b/License.txt @@ -471,3 +471,92 @@ the avoidance of doubt, this paragraph does not form part of the public licenses. Creative Commons may be contacted at creativecommons.org. + + ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +“Warranty Disclaimer & Limitation of Liability + + + +OPEN SOURCE SOFTWARE: "Open Source Software" is software that may be + +delivered with the Software and is licensed in accordance with open + +source licenses, including, but not limited to, any software licensed + +under Academic Free License, Apache Software License, Artistic License, + +BSD License, GNU General Public License, GNU Library General Public + +License, GNU Lesser Public License, Mozilla Public License, Python + +License or any other similar license. + + + +DISCLAIMER OF WARRANTIES: WIND RIVER AND ITS LICENSORS DISCLAIM ALL + +WARRANTIES, EXPRESS, IMPLIED AND STATUTORY INCLUDING, WITHOUT + +LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + +PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT OF THIRD PARTY RIGHTS + +WITH RESPECT TO OPEN SOURCE SOFTWARE. NO ORAL OR WRITTEN INFORMATION OR + +ADVICE GIVEN BY WIND RIVER, ITS DEALERS, DISTRIBUTORS, AGENTS OR + +EMPLOYEES SHALL IN ANY WAY INCREASE THE SCOPE OF THIS WARRANTY. Some + +jurisdictions do not allow the limitation or exclusion of implied + +warranties or how long an implied warranty may last, so the above + +limitations may not apply to Customer. This warranty gives Customer + +specific legal rights and Customer may have other rights that vary from + +jurisdiction to jurisdiction. + + + +LIMITATION OF LIABILITY: WIND RIVER AND ITS LICENSORS SHALL NOT BE + +LIABLE FOR ANY INCIDENTAL, SPECIAL, CONSEQUENTIAL OR INDIRECT DAMAGES + +OF ANY KIND (INCLUDING DAMAGES FOR INTERRUPTION OF BUSINESS, PROCUREMENT + +OF SUBSTITUTE GOODS, LOSS OF PROFITS, OR THE LIKE) REGARDLESS OF THE + +FORM OF ACTION WHETHER IN CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT + +PRODUCT LIABILITY OR ANY OTHER LEGAL OR EQUITABLE THEORY, ARISING OUT OF + +OR RELATED TO OPEN SOURCE SOFTWARE, EVEN IF WIND RIVER HAS BEEN ADVISED + +OF THE POSSIBILITY OF SUCH DAMAGES. IN NO EVENT WILL WIND RIVER'S + +AGGREGATE CUMULATIVE LIABILITY FOR ANY CLAIMS ARISING OUT OF OR RELATED + +TO OPEN SOURCE SOFTWARE EXCEED ONE HUNDRED DOLLARS ($100). Some + +jurisdictions do not allow the exclusion or limitation of incidental or + +consequential damages so this limitation and exclusion may not apply to + +Customer. + + + +THE WARRANTY DISCLAIMER AND LIMITED LIABILITY ARE FUNDAMENTAL ELEMENTS + +OF THE BASIS OF THE BARGAIN BETWEEN WIND RIVER AND CUSTOMER. WIND RIVER + +WOULD NOT BE ABLE TO PROVIDE OPEN SOURCE SOFTWARE WITHOUT SUCH + +LIMITATIONS. + + + +The End” diff --git a/newton/README.md b/newton/README.md index ca2bb587..f8434d2b 100644 --- a/newton/README.md +++ b/newton/README.md @@ -9,4 +9,4 @@ # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# Micro service of network service life cycle management. +# Micro service of MultiCloud plugin for newton. diff --git a/newton/assembly.xml b/newton/assembly.xml index 81b17ee6..89f1a954 100644 --- a/newton/assembly.xml +++ b/newton/assembly.xml @@ -50,5 +50,4 @@ newton - diff --git a/newton/newton/extensions/__init__.py b/newton/newton/extensions/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/newton/newton/extensions/__init__.py @@ -0,0 +1,10 @@ +# 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. diff --git a/newton/newton/extensions/urls.py b/newton/newton/extensions/urls.py new file mode 100644 index 00000000..ade2b6dc --- /dev/null +++ b/newton/newton/extensions/urls.py @@ -0,0 +1,28 @@ +# 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 views import extensions +from views import epacaps + +urlpatterns = [ + url(r'^sions$', extensions.Extensions.as_view()), + url(r'^sions/$', extensions.Extensions.as_view()), + url(r'^sions/epa-caps$', epacaps.EpaCaps.as_view()), + url(r'^sions/epa-caps/$', epacaps.EpaCaps.as_view()), +] + +urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/newton/newton/extensions/views/__init__.py b/newton/newton/extensions/views/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/newton/newton/extensions/views/__init__.py @@ -0,0 +1,10 @@ +# 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. diff --git a/newton/newton/extensions/views/epacaps.py b/newton/newton/extensions/views/epacaps.py new file mode 100644 index 00000000..7d3dd0af --- /dev/null +++ b/newton/newton/extensions/views/epacaps.py @@ -0,0 +1,65 @@ +# 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 +import traceback + +from django.core.cache import cache + +from keystoneauth1 import access +from keystoneauth1.access import service_catalog +from keystoneauth1.exceptions import HttpError +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from newton.pub.config import config +from newton.pub.exceptions import VimDriverNewtonException +from newton.requests.views.util import VimDriverUtils +from newton.pub.msapi import extsys + +logger = logging.getLogger(__name__) + +DEBUG=True + + +class EpaCaps(APIView): + + def get(self, request, vimid=""): + logger.debug("EpaCaps--get::data> %s" % request.data) + logger.debug("EpaCaps--get::vimid> %s" + % vimid) + try: + + vim = VimDriverUtils.get_vim_info(vimid) + caps_json = json.loads(vim['cloud_epa_caps']) + + cloud_owner, cloud_region_id = extsys.decode_vim_id(vimid) + content = { + "cloud-owner":cloud_owner, + "cloud-region-id":cloud_region_id, + "vimid":vimid, + "cloud-epa-caps": caps_json, + } + return Response(data=content, status=status.HTTP_200_OK) + #return resp + except VimDriverNewtonException as e: + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + logger.error("HttpError: status:%s, response:%s" % (e.http_status, e.response.json())) + return Response(data=e.response.json(), status=e.http_status) + except Exception as e: + logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/newton/newton/extensions/views/extensions.py b/newton/newton/extensions/views/extensions.py new file mode 100644 index 00000000..e930f06d --- /dev/null +++ b/newton/newton/extensions/views/extensions.py @@ -0,0 +1,76 @@ +# 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 +import traceback + +from django.core.cache import cache + +from keystoneauth1 import access +from keystoneauth1.access import service_catalog +from keystoneauth1.exceptions import HttpError +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from newton.pub.config import config +from newton.pub.exceptions import VimDriverNewtonException +from newton.requests.views.util import VimDriverUtils +from newton.pub.msapi import extsys + +logger = logging.getLogger(__name__) + +DEBUG=True + + +class Extensions(APIView): + + def get(self, request, vimid=""): + logger.debug("Extensions--get::data> %s" % request.data) + logger.debug("Extensions--get::vimid> %s" + % vimid) + try: + cloud_owner, cloud_region_id = extsys.decode_vim_id(vimid) + registered_extensions = \ + [ + { + "alias": "epa-caps", + "description": "Multiple network support", + "name": "EPACapsQuery", + 'links': [ \ + { + "url": "http://%s:%s/api/multicloud-newton/v0/%s/extensions/epa-caps" \ + % (config.MSB_SERVICE_IP, config.MSB_SERVICE_PORT, vimid), \ + } + ] + } + ] + + content = { + "cloud-owner":cloud_owner, + "cloud-region-id":cloud_region_id, + "vimid":vimid, + "extensions": registered_extensions + } + return Response(data=content, status=status.HTTP_200_OK) + #return resp + except VimDriverNewtonException as e: + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + logger.error("HttpError: status:%s, response:%s" % (e.http_status, e.response.json())) + return Response(data=e.response.json(), status=e.http_status) + except Exception as e: + logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/newton/newton/proxy/__init__.py b/newton/newton/proxy/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/newton/newton/proxy/__init__.py @@ -0,0 +1,10 @@ +# 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. diff --git a/newton/newton/proxy/tests/__init__.py b/newton/newton/proxy/tests/__init__.py new file mode 100644 index 00000000..48b6e44b --- /dev/null +++ b/newton/newton/proxy/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/proxy/tests/test_identity_proxy.py b/newton/newton/proxy/tests/test_identity_proxy.py new file mode 100644 index 00000000..5066ed67 --- /dev/null +++ b/newton/newton/proxy/tests/test_identity_proxy.py @@ -0,0 +1,533 @@ +# 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 json + +import mock +import unittest + +from django.test import Client +from rest_framework import status + +from keystoneauth1 import session +from keystoneauth1.exceptions import HttpError + +from newton.requests.views.util import VimDriverUtils +from newton.proxy.views.identityV3 import Tokens, Catalog + +mock_viminfo = { + "createTime": "2017-04-01 02:22:27", + "domain": "Default", + "name": "TiS_R4", + "password": "admin", + "tenant": "admin", + "type": "openstack", + "url": "http://128.224.180.14:5000/v3", + "userName": "admin", + "vendor": "WindRiver", + "version": "newton", + "vimId": "windriver-hudson-dc_RegionOne", + 'cloud_owner':'windriver-hudson-dc', + 'cloud_region_id':'RegionOne', + 'cloud_extra_info':'', + 'cloud_epa_caps':'{"huge_page":"true","cpu_pinning":"true",\ + "cpu_thread_policy":"true","numa_aware":"true","sriov":"true",\ + "dpdk_vswitch":"true","rdt":"false","numa_locality_pci":"true"}', + 'insecure':'True', +} + +mock_auth_state = { + "body" : { + "token" : { + "is_domain" : "false", + "expires_at" : "2017-08-27T14:19:15.000000Z", + "issued_at" : "2017-08-27T13:19:15.000000Z", + "roles" : [ + { + "id" : "9fe2ff9ee4384b1894a90878d3e92bab", + "name" : "_member_" + }, + { + "id" : "b86a7e02935844b899d3d326f83c1b1f", + "name" : "admin" + }, + { + "name" : "heat_stack_owner", + "id" : "7de502236e954c8282de32e773fc052e" + } + ], + "methods" : [ + "password" + ], + "catalog" : [ + { + "id" : "99aefcc82a9246f98f8c281e61ffc754", + "endpoints" : [ + { + "region" : "RegionOne", + "url" : "http://128.224.180.14:9696", + "id" : "39583c1508ad4b71b380570a745ee10a", + "interface" : "public", + "region_id" : "RegionOne" + }, + { + "url" : "http://192.168.204.2:9696", + "region" : "RegionOne", + "id" : "37e8d07ba24e4b8f93490c9daaba06e2", + "interface" : "internal", + "region_id" : "RegionOne" + }, + { + "interface" : "admin", + "id" : "7eee4ca98d444b1abb00a50d4b89373f", + "region_id" : "RegionOne", + "region" : "RegionOne", + "url" : "http://192.168.204.2:9696" + } + ], + "name" : "neutron", + "type" : "network" + }, + { + "endpoints" : [ + { + "interface" : "public", + "id" : "10496738fa374295a4a88a63b81a1589", + "region_id" : "RegionOne", + "url" : "http://128.224.180.14:8777", + "region" : "RegionOne" + }, + { + "id" : "02dcb8c0bd464c4489fa0a0c9f28571f", + "region_id" : "RegionOne", + "interface" : "internal", + "url" : "http://192.168.204.2:8777", + "region" : "RegionOne" + }, + { + "region_id" : "RegionOne", + "id" : "8a73b0d3743b4e78b87614690f6e97fe", + "interface" : "admin", + "url" : "http://192.168.204.2:8777", + "region" : "RegionOne" + } + ], + "id" : "d131054da83f4c93833799747a0f4709", + "name" : "ceilometer", + "type" : "metering" + }, + { + "type" : "volumev2", + "name" : "cinderv2", + "endpoints" : [ + { + "id" : "35a67ad36f0447d19c9662babf7cf609", + "interface" : "public", + "region_id" : "RegionOne", + "url" : "http://128.224.180.14:8776/v2/fcca3cc49d5e42caae15459e27103efc", + "region" : "RegionOne" + }, + { + "region" : "RegionOne", + "url" : "http://192.168.204.2:8776/v2/fcca3cc49d5e42caae15459e27103efc", + "id" : "c6ea42052268420fa2c8d351ee68c922", + "interface" : "internal", + "region_id" : "RegionOne" + }, + { + "region_id" : "RegionOne", + "id" : "91cb24853dc3450d847b0c286a2e44ea", + "interface" : "admin", + "region" : "RegionOne", + "url" : "http://192.168.204.2:8776/v2/fcca3cc49d5e42caae15459e27103efc" + } + ], + "id" : "40440057102440739c30be10a66bc5d1" + }, + { + "name" : "heat", + "type" : "orchestration", + "id" : "35300cce88db4bd4bb5a72ffe3b88b00", + "endpoints" : [ + { + "id" : "58999d7b4a94439089ecfb2aca2d7f6c", + "region_id" : "RegionOne", + "interface" : "public", + "region" : "RegionOne", + "url" : "http://128.224.180.14:8004/v1/fcca3cc49d5e42caae15459e27103efc" + }, + { + "url" : "http://192.168.204.2:8004/v1/fcca3cc49d5e42caae15459e27103efc", + "region" : "RegionOne", + "interface" : "internal", + "id" : "1e0ee1a2aef84802b921d422372a567e", + "region_id" : "RegionOne" + }, + { + "region" : "RegionOne", + "url" : "http://192.168.204.2:8004/v1/fcca3cc49d5e42caae15459e27103efc", + "id" : "17661bf4859741b8a43a461dedad1871", + "region_id" : "RegionOne", + "interface" : "admin" + } + ] + }, + { + "id" : "08dc6912aea64c01925012c8a6df250a", + "endpoints" : [ + { + "id" : "02792c4eed77486083f9b2e52d7b94b0", + "region_id" : "RegionOne", + "interface" : "public", + "region" : "RegionOne", + "url" : "http://128.224.180.14:5000/v3" + }, + { + "id" : "b6d5cad394b94309ae40d8de88059c5f", + "region_id" : "RegionOne", + "interface" : "internal", + "url" : "http://192.168.204.2:5000/v3", + "region" : "RegionOne" + }, + { + "region" : "RegionOne", + "url" : "http://192.168.204.2:35357/v3", + "region_id" : "RegionOne", + "id" : "1f18e2b7c6a34493b86853b65917888e", + "interface" : "admin" + } + ], + "type" : "identity", + "name" : "keystone" + }, + { + "name" : "vim", + "type" : "nfv", + "endpoints" : [ + { + "url" : "http://128.224.180.14:4545", + "region" : "RegionOne", + "id" : "b33e317345e4480ab0786e4960995ec9", + "interface" : "public", + "region_id" : "RegionOne" + }, + { + "region" : "RegionOne", + "url" : "http://192.168.204.2:4545", + "interface" : "internal", + "id" : "03c85828d5bf432ab04831aa65ac9c52", + "region_id" : "RegionOne" + }, + { + "id" : "067983abb061476cb53a9e23a740d98f", + "region_id" : "RegionOne", + "interface" : "admin", + "url" : "http://192.168.204.2:4545", + "region" : "RegionOne" + } + ], + "id" : "01636c856fc84988b38b9117eb4a8021" + }, + { + "name" : "aodh", + "type" : "alarming", + "id" : "eb269151d0e44744a5b5449657bdc61c", + "endpoints" : [ + { + "id" : "5bfc6c056e0244c493642eb82f6aaa11", + "region_id" : "RegionOne", + "interface" : "public", + "url" : "http://128.224.180.14:8042", + "region" : "RegionOne" + }, + { + "region" : "RegionOne", + "url" : "http://192.168.204.2:8042", + "region_id" : "RegionOne", + "id" : "ad69c7f76dce4089a195b9221ddbfb44", + "interface" : "internal" + }, + { + "interface" : "admin", + "id" : "3e8fcdfa7bcb40b0ae33c282adfcc9ff", + "region_id" : "RegionOne", + "region" : "RegionOne", + "url" : "http://192.168.204.2:8042" + } + ] + }, + { + "name" : "sysinv", + "type" : "platform", + "endpoints" : [ + { + "region" : "RegionOne", + "url" : "http://128.224.180.14:6385/v1", + "interface" : "public", + "id" : "ba4ba8104590421b84672306c7e0e1f1", + "region_id" : "RegionOne" + }, + { + "region" : "RegionOne", + "url" : "http://192.168.204.2:6385/v1", + "interface" : "internal", + "id" : "a1cba34b163f496ab1acd6e9b51e39a2", + "region_id" : "RegionOne" + }, + { + "url" : "http://192.168.204.2:6385/v1", + "region" : "RegionOne", + "id" : "7c171210a2c841a6a52a5713e316d6fc", + "interface" : "admin", + "region_id" : "RegionOne" + } + ], + "id" : "256bbad671f946fea543e6bd71f98875" + }, + { + "id" : "e84665dcce814c05b4c5084964547534", + "endpoints" : [ + { + "url" : "http://128.224.180.14:8000/v1/fcca3cc49d5e42caae15459e27103efc", + "region" : "RegionOne", + "region_id" : "RegionOne", + "id" : "b2ed1a23dc6944bea129c20861e0286a", + "interface" : "public" + }, + { + "region" : "RegionOne", + "url" : "http://192.168.204.2:8000/v1/fcca3cc49d5e42caae15459e27103efc", + "interface" : "internal", + "id" : "c4df7c6bc15646848eff35caf6ffea8e", + "region_id" : "RegionOne" + }, + { + "region_id" : "RegionOne", + "id" : "61b3dabb761443a89ab549f437c05ab0", + "interface" : "admin", + "region" : "RegionOne", + "url" : "http://192.168.204.2:8000/v1/fcca3cc49d5e42caae15459e27103efc" + } + ], + "name" : "heat-cfn", + "type" : "cloudformation" + }, + { + "id" : "823024424a014981a3721229491c0b1a", + "endpoints" : [ + { + "region" : "RegionOne", + "url" : "http://128.224.180.14:8776/v1/fcca3cc49d5e42caae15459e27103efc", + "region_id" : "RegionOne", + "id" : "4a52e4e54ff440789f9a797919c4a0f2", + "interface" : "public" + }, + { + "url" : "http://192.168.204.2:8776/v1/fcca3cc49d5e42caae15459e27103efc", + "region" : "RegionOne", + "id" : "d4f9a84476524a39844f0fce63f1022e", + "region_id" : "RegionOne", + "interface" : "internal" + }, + { + "region" : "RegionOne", + "url" : "http://192.168.204.2:8776/v1/fcca3cc49d5e42caae15459e27103efc", + "interface" : "admin", + "id" : "81bf3810a8cc4697b68c6e93b5b8fe1f", + "region_id" : "RegionOne" + } + ], + "type" : "volume", + "name" : "cinder" + }, + { + "name" : "glance", + "type" : "image", + "endpoints" : [ + { + "id" : "bd930aba961946cfb1401bada56d55e3", + "region_id" : "RegionOne", + "interface" : "public", + "region" : "RegionOne", + "url" : "http://128.224.180.14:9292" + }, + { + "region" : "RegionOne", + "url" : "http://192.168.204.2:9292", + "id" : "c11da585f0b141b99d1e18bb9a607beb", + "region_id" : "RegionOne", + "interface" : "internal" + }, + { + "region" : "RegionOne", + "url" : "http://192.168.204.2:9292", + "id" : "31b26c625a6a4fc7910dc5935155996e", + "interface" : "admin", + "region_id" : "RegionOne" + } + ], + "id" : "3b78cf039bc54d1bbb99ab3a4be15ef1" + }, + { + "id" : "b8701374bf254de1beee8a2c9ecc6b33", + "endpoints" : [ + { + "region_id" : "RegionOne", + "id" : "f7407f330c8b4577b1d377d3fab9c2f8", + "interface" : "public", + "region" : "RegionOne", + "url" : "http://128.224.180.14:15491" + }, + { + "url" : "http://192.168.204.2:5491", + "region" : "RegionOne", + "interface" : "internal", + "id" : "0b37ce31a32f4b6fa5e1aa0d6c20680f", + "region_id" : "RegionOne" + }, + { + "region_id" : "RegionOne", + "id" : "7b87ea72adf245e1991e9e0df29b7ea9", + "interface" : "admin", + "region" : "RegionOne", + "url" : "http://192.168.204.2:5491" + } + ], + "type" : "patching", + "name" : "patching" + }, + { + "id" : "0ec0923a58f04ffeb6fced3bbc5c0947", + "endpoints" : [ + { + "url" : "http://128.224.180.14:8774/v2.1/fcca3cc49d5e42caae15459e27103efc", + "region" : "RegionOne", + "id" : "13168b12da17451fb39630de67db168f", + "region_id" : "RegionOne", + "interface" : "public" + }, + { + "id" : "22dd6a44209f42d986b82e3aa6535f82", + "interface" : "internal", + "region_id" : "RegionOne", + "region" : "RegionOne", + "url" : "http://192.168.204.2:8774/v2.1/fcca3cc49d5e42caae15459e27103efc" + }, + { + "region" : "RegionOne", + "url" : "http://192.168.204.2:8774/v2.1/fcca3cc49d5e42caae15459e27103efc", + "id" : "552a991ae501492f841c1b6e2ff38fc5", + "region_id" : "RegionOne", + "interface" : "admin" + } + ], + "type" : "compute", + "name" : "nova" + }, + { + "id" : "50b219650f1049b097b3f14e8c70cdf8", + "endpoints" : [ + { + "interface" : "public", + "id" : "5a4276cd6e4d43e883cf8640d4e13f7d", + "region_id" : "RegionOne", + "region" : "RegionOne", + "url" : "http://128.224.180.14:8776/v3/fcca3cc49d5e42caae15459e27103efc" + }, + { + "region" : "RegionOne", + "url" : "http://192.168.204.2:8776/v3/fcca3cc49d5e42caae15459e27103efc", + "region_id" : "RegionOne", + "id" : "c796df3ca5a84fc18db5b43a55283953", + "interface" : "internal" + }, + { + "region_id" : "RegionOne", + "id" : "cf55c2b34d0049ba835a2e48b9ad0e2e", + "interface" : "admin", + "url" : "http://192.168.204.2:8776/v3/fcca3cc49d5e42caae15459e27103efc", + "region" : "RegionOne" + } + ], + "type" : "volumev3", + "name" : "cinderv3" + } + ], + "project" : { + "name" : "admin", + "id" : "fcca3cc49d5e42caae15459e27103efc", + "domain" : { + "id" : "default", + "name" : "Default" + } + }, + "user" : { + "name" : "admin", + "id" : "9efb043c7629497a8028d7325ca1afb0", + "domain" : { + "id" : "default", + "name" : "Default" + } + }, + "audit_ids" : [ + "_ZWT10DtSZKRXIvIcxun7w" + ] + } + }, + "auth_token" : "1a62b3971d774404a504c5d9a3e506e3" +} + + +class TestIdentityService(unittest.TestCase): + def setUp(self): + self.client = Client() + pass + + def tearDown(self): + pass + + @mock.patch.object(VimDriverUtils, 'get_vim_info') + @mock.patch.object(VimDriverUtils, 'get_session') + @mock.patch.object(VimDriverUtils, 'get_auth_state') + @mock.patch.object(VimDriverUtils, 'update_token_cache') + def test_token(self, mock_update_token_cache, mock_get_auth_state, mock_get_session, mock_get_vim_info): + ''' + test API: get token + :param mock_update_token_cache: + :param mock_get_auth_state: + :param mock_get_session: + :param mock_get_vim_info: + :return: + ''' + + #mock VimDriverUtils APIs + mock_session_specs = ["get"] + mock_session_get_response = {'status':200} + mock_session = mock.Mock(name='mock_session', spec=mock_session_specs) + mock_session.get.return_value = mock_session_get_response + + mock_get_vim_info.return_value = mock_viminfo + mock_get_session.return_value = mock_session + mock_get_auth_state.return_value = json.dumps(mock_auth_state) + mock_update_token_cache.return_value = 0 + + #simulate client to make the request + data ={} + response = self.client.post("/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/identity/v3/auth/tokens", data=data, format='json') + self.failUnlessEqual(status.HTTP_201_CREATED, response.status_code) + context = json.loads(response.content) + + self.assertTrue(response['X-Subject-Token'] != None) + self.assertTrue(context['token']['catalog'] != None) + + pass diff --git a/newton/newton/proxy/urls.py b/newton/newton/proxy/urls.py new file mode 100644 index 00000000..01d2007c --- /dev/null +++ b/newton/newton/proxy/urls.py @@ -0,0 +1,34 @@ +# 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 views import identityV3 +from views import services + +urlpatterns = [ + # url(r'^identity/v2)$', + # identityV2.Tokens.as_view()), + url(r'^identity/v3/auth/tokens$', + identityV3.Tokens.as_view()), + url(r'^identity/v3/auth/catalog$', + identityV3.Catalog.as_view()), + url(r'^identity/(?:v2.0/|)tenants$', + services.GetTenants.as_view()), + url(r'^(?P[0-9a-zA-Z_-]+)/(?P[0-9a-zA-Z./_-]*)$', + services.Services.as_view()), +] + +urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/newton/newton/proxy/views/__init__.py b/newton/newton/proxy/views/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/newton/newton/proxy/views/__init__.py @@ -0,0 +1,10 @@ +# 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. diff --git a/newton/newton/proxy/views/identityV3.py b/newton/newton/proxy/views/identityV3.py new file mode 100644 index 00000000..b75dcd3b --- /dev/null +++ b/newton/newton/proxy/views/identityV3.py @@ -0,0 +1,202 @@ +# 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 +import traceback + +from django.core.cache import cache + +from keystoneauth1 import access +from keystoneauth1.access import service_catalog +from keystoneauth1.exceptions import HttpError +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from newton.pub.config import config +from newton.pub.exceptions import VimDriverNewtonException +from newton.requests.views.util import VimDriverUtils + +logger = logging.getLogger(__name__) + +DEBUG=True +MULTICLOUD_PREFIX = "http://%s:%s/api/multicloud-newton/v0" %(config.MSB_SERVICE_IP, config.MSB_SERVICE_PORT) + +def update_catalog(vimid, catalog, multicould_namespace): + ''' + replace the orignal endpoints with multicloud's + return the catalog with updated endpoints, and also another catalog with prefix and suffix of each endpoint + :param vimid: + :param catalog: service catalog to be updated + :param multicould_namespace: multicloud namespace prefix to replace the real one in catalog endpoints url + :return:updated catalog, and metadata_catalog looks like: + { + 'compute': { + 'prefix': 'http://ip:port', + 'proxy_prefix': 'http://another_ip: another_port', + 'suffix': 'v2.1/53a4ab9015c84ee892e46d294f3b8b2d', + }, + 'network': { + 'prefix': 'http://ip:port', + 'proxy_prefix': 'http://another_ip: another_port', + 'suffix': '', + }, + } + ''' + + metadata_catalog = {} + if catalog: + # filter and replace endpoints of catalogs + for item in catalog: + one_catalog = {} + metadata_catalog[item['type']] = one_catalog + + endpoints = item['endpoints'] + item['endpoints']=[] + for endpoint in endpoints: + interface = endpoint.get('interface', None) + if interface != 'public': + continue +# elif item["type"] == "identity": +# endpoint["url"] = multicould_namespace + "/%s/identity/v3" % vimid + else: + # replace the endpoint with MultiCloud's proxy + import re + endpoint_url = endpoint["url"] +# m = re.search(r'^http[s]*://([0-9.]+:[0-9]+)[/]*([0-9a-zA-Z/._-]*)$', endpoint_url) + m = re.search(r'^(http[s]?://[0-9.]+:[0-9]+)(/([0-9a-zA-Z/._-]+)$)?', endpoint_url) + if m: + real_prefix = m.group(1) + real_suffix = m.group(3) + + # populate metadata_catalog + one_catalog['prefix'] = real_prefix + one_catalog['suffix'] = real_suffix if real_suffix else '' + one_catalog['proxy_prefix'] = multicould_namespace + "/%s" % vimid + "/" + item["type"] + + endpoint_url = multicould_namespace + "/%s" % vimid + "/" + item["type"] + + if real_suffix: + endpoint_url += "/" + real_suffix + + if item["type"] == "identity": + endpoint_url = multicould_namespace + "/%s/identity/v3" % vimid + +# endpoint["url"] = re.sub(r"^http([s]*)://([0-9.]+):([0-9]+)", +# multicould_namespace + "/%s/" % vimid + item["type"], +# endpoint["url"]) + + + endpoint["url"] = endpoint_url + item['endpoints'].append( endpoint ) + + return catalog, metadata_catalog + else: + return None + pass + + + + +class Tokens(APIView): + service = {'service_type': 'identity', + 'interface': 'public'} + + def __init__(self): + self.proxy_prefix = MULTICLOUD_PREFIX + + def post(self, request, vimid=""): + logger.debug("identityV3--post::> %s" % request.data) + sess = None + resp = None + resp_body = None + try: + # prepare request resource to vim instance + vim = VimDriverUtils.get_vim_info(vimid) + sess = VimDriverUtils.get_session(vim) + + tmp_auth_state = VimDriverUtils.get_auth_state(vim, sess) + tmp_auth_info = json.loads(tmp_auth_state) + tmp_auth_token = tmp_auth_info['auth_token'] + tmp_auth_data = tmp_auth_info['body'] + + #store the auth_state, redis/memcached + #set expiring in 1 hour + + #update the catalog + tmp_auth_data['token']['catalog'], tmp_metadata_catalog = update_catalog(vimid, tmp_auth_data['token']['catalog'], self.proxy_prefix) + VimDriverUtils.update_token_cache(vim, sess, tmp_auth_token, tmp_auth_state, json.dumps(tmp_metadata_catalog)) + + resp = Response(headers={'X-Subject-Token': tmp_auth_token}, data=tmp_auth_data, status=status.HTTP_201_CREATED) + return resp + except VimDriverNewtonException as e: + + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + logger.error("HttpError: status:%s, response:%s" % (e.http_status, e.response.json())) + return Response(data=e.response.json(), status=e.http_status) + except Exception as e: + logger.error(traceback.format_exc()) + + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + pass + + +class Catalog(APIView): + + service = {'service_type': 'identity', + 'interface': 'public'} + + def __init__(self): + self.proxy_prefix = MULTICLOUD_PREFIX + + def get(self, request, vimid=""): + logger.debug("Catalog--get::data> %s" % request.data) +# logger.debug("Catalog--get::META> %s" % request.META) + try: + # prepare request resource to vim instance + #get token: + tmp_auth_token = request.META.get('HTTP_X_AUTH_TOKEN', None) + if not tmp_auth_token: + return Response(data={'error': "No X-Auth-Token found in headers"}, status=status.HTTP_401_UNAUTHORIZED) + + vim = VimDriverUtils.get_vim_info(vimid) + #fetch the auth_state out of cache + tmp_auth_state, metadata_catalog = VimDriverUtils.get_token_cache(vim, tmp_auth_token) + if not tmp_auth_state: + return Response(data={'error': "Expired X-Auth-Token found in headers"}, status=status.HTTP_401_UNAUTHORIZED) + + sess = VimDriverUtils.get_session(vim, auth_state=tmp_auth_state) + req_resource = "/auth/catalog" + + resp = sess.get(req_resource, endpoint_filter=self.service) + #update token cache in case the token was required during the requests + tmp_auth_token = VimDriverUtils.update_token_cache(vim, sess, tmp_auth_token, tmp_auth_state) + + content = resp.json() + tmp_auth_catalog = content['catalog'] + update_catalog(vimid, tmp_auth_catalog, self.proxy_prefix) + + return Response(headers={'X-Subject-Token':tmp_auth_token}, data={'catalog': tmp_auth_catalog}, status=resp.status_code) + except VimDriverNewtonException as e: + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + logger.error("HttpError: status:%s, response:%s" % (e.http_status, e.response.json())) + return Response(data=e.response.json(), status=e.http_status) + except Exception as e: + logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + pass diff --git a/newton/newton/proxy/views/services.py b/newton/newton/proxy/views/services.py new file mode 100644 index 00000000..d3069111 --- /dev/null +++ b/newton/newton/proxy/views/services.py @@ -0,0 +1,502 @@ +# 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 +import traceback + +import re +from django.core.cache import cache + +from keystoneauth1 import access +from keystoneauth1.access import service_catalog +from keystoneauth1.exceptions import HttpError +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from newton.pub.exceptions import VimDriverNewtonException +from newton.requests.views.util import VimDriverUtils +from newton.pub.msapi import extsys + +logger = logging.getLogger(__name__) + +DEBUG=True + +class Services(APIView): + + def head(self, request, vimid="", servicetype="", requri=""): + logger.debug("Services--head::data> %s" % request.data) + logger.debug("Services--head::vimid, servicetype, requri> %s,%s,%s" + % (vimid, servicetype, requri)) + + try: + # prepare request resource to vim instance + #get token: + tmp_auth_token = request.META.get('HTTP_X_AUTH_TOKEN', None) + if not tmp_auth_token: + return Response(data={'error': "No X-Auth-Token found in headers"}, status=status.HTTP_401_UNAUTHORIZED) + + vim = VimDriverUtils.get_vim_info(vimid) + #fetch the auth_state out of cache + tmp_auth_state, metadata_catalog = VimDriverUtils.get_token_cache(vim,tmp_auth_token) + if not tmp_auth_state: + return Response(data={'error': "Expired X-Auth-Token found in headers"}, status=status.HTTP_401_UNAUTHORIZED) + + + sess = VimDriverUtils.get_session(vim, tenantid=None, auth_state=tmp_auth_state) + req_resource = '' + if requri and requri != '': + req_resource = "/" if re.match(r'//', requri) else ''+ requri + + cloud_owner, regionid = extsys.decode_vim_id(vimid) + interface = 'public' + service = {'service_type': servicetype, + 'interface': interface, + 'region_id': regionid} + + resp = sess.head(req_resource, endpoint_filter=service) + #update token cache in case the token was required during the requests + tmp_auth_token = VimDriverUtils.update_token_cache(vim, sess, tmp_auth_token, tmp_auth_state) + content = resp.json() + return Response(headers={'X-Subject-Token': tmp_auth_token}, data=content, status=resp.status_code) + #return resp + except VimDriverNewtonException as e: + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + logger.error("HttpError: status:%s, response:%s" % (e.http_status, e.response.json())) + return Response(data=e.response.json(), status=e.http_status) + except Exception as e: + logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + def get(self, request, vimid="", servicetype="", requri=""): + logger.debug("Services--get::data> %s" % request.data) + logger.debug("Services--get::vimid, servicetype, requri> %s,%s,%s" + % (vimid, servicetype, requri)) + try: + # prepare request resource to vim instance + #get token: + tmp_auth_token = request.META.get('HTTP_X_AUTH_TOKEN', None) + if not tmp_auth_token: + return Response(data={'error': "No X-Auth-Token found in headers"}, status=status.HTTP_401_UNAUTHORIZED) + + vim = VimDriverUtils.get_vim_info(vimid) + # fetch the auth_state out of cache + tmp_auth_state, metadata_catalog = VimDriverUtils.get_token_cache(vim, tmp_auth_token) + if not tmp_auth_state: + return Response(data={'error': "Expired X-Auth-Token found in headers"}, status=status.HTTP_401_UNAUTHORIZED) + + real_prefix = None + proxy_prefix = None + suffix = None + if servicetype and metadata_catalog: +# logger.error("metadata_catalog:%s" % metadata_catalog) + metadata_catalog = json.loads(metadata_catalog) + service_metadata = metadata_catalog.get(servicetype, None) + if service_metadata: + real_prefix = service_metadata['prefix'] + proxy_prefix = service_metadata['proxy_prefix'] + suffix = service_metadata['suffix'] + + if not real_prefix or not proxy_prefix: + raise VimDriverNewtonException(message="internal state error", + content="invalid cached metadata", + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) + + if requri == suffix: + requri = None + + if suffix and requri: + #remove the suffix from the requri to avoid duplicated suffix in real request uri later + tmp_pattern = re.compile(suffix) + requri = tmp_pattern.sub('', requri) + + sess = VimDriverUtils.get_session(vim, tenantid=None, auth_state=tmp_auth_state) + req_resource = '' + if requri and requri != '': + req_resource = "/" if re.match(r'//', requri) else ''+ requri + + cloud_owner, regionid = extsys.decode_vim_id(vimid) + interface = 'public' + service = {'service_type': servicetype, + 'interface': interface, + 'region_id': regionid} + + resp = sess.get(req_resource, endpoint_filter=service) + #update token cache in case the token was required during the requests + tmp_auth_token = VimDriverUtils.update_token_cache(vim, sess, tmp_auth_token, tmp_auth_state) + content = resp.json() + + #filter the resp content and replace all endpoint prefix + tmp_content = json.dumps(content) + tmp_pattern = re.compile(real_prefix) + tmp_content = tmp_pattern.sub(proxy_prefix, tmp_content) + content = json.loads(tmp_content) + + return Response(headers={'X-Subject-Token': tmp_auth_token}, data=content, status=resp.status_code) + #return resp + except VimDriverNewtonException as e: + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + logger.error("HttpError: status:%s, response:%s" % (e.http_status, e.response.json())) + return Response(data=e.response.json(), status=e.http_status) + except Exception as e: + logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + def post(self, request, vimid="", servicetype="", requri=""): + logger.debug("Services--post::data> %s" % request.data) + logger.debug("Services--post::vimid, servicetype, requri> %s,%s,%s" + % (vimid, servicetype, requri)) + try: + # prepare request resource to vim instance + # get token: + tmp_auth_token = request.META.get('HTTP_X_AUTH_TOKEN', None) + if not tmp_auth_token: + return Response(data={'error': "No X-Auth-Token found in headers"}, status=status.HTTP_401_UNAUTHORIZED) + + vim = VimDriverUtils.get_vim_info(vimid) + # fetch the auth_state out of cache + tmp_auth_state, metadata_catalog = VimDriverUtils.get_token_cache(vim, tmp_auth_token) + if not tmp_auth_state: + return Response(data={'error': "Expired X-Auth-Token found in headers"}, + status=status.HTTP_401_UNAUTHORIZED) + + real_prefix = None + proxy_prefix = None + suffix = None + if servicetype and metadata_catalog: +# logger.error("metadata_catalog:%s" % metadata_catalog) + metadata_catalog = json.loads(metadata_catalog) + service_metadata = metadata_catalog.get(servicetype, None) + if service_metadata: + real_prefix = service_metadata['prefix'] + proxy_prefix = service_metadata['proxy_prefix'] + suffix = service_metadata['suffix'] + + if not real_prefix or not proxy_prefix: + raise VimDriverNewtonException(message="internal state error", + content="invalid cached metadata", + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) + + if requri == suffix: + requri = None + + if suffix and requri: + #remove the suffix from the requri to avoid duplicated suffix in real request uri later + tmp_pattern = re.compile(suffix) + requri = tmp_pattern.sub('', requri) + + sess = VimDriverUtils.get_session(vim, tenantid=None, auth_state=tmp_auth_state) + req_resource = "" + if requri and requri != "": + req_resource = "/" if re.match(r'//', requri) else ''+ requri + + cloud_owner, regionid = extsys.decode_vim_id(vimid) + interface = 'public' + service = {'service_type': servicetype, + 'interface': interface, + 'region_id': regionid} + + resp = sess.post(req_resource, data=json.JSONEncoder().encode(request.data),endpoint_filter=service) + # update token cache in case the token was required during the requests + tmp_auth_token = VimDriverUtils.update_token_cache(vim, sess, tmp_auth_token, tmp_auth_state) + content = resp.json() + + #filter the resp content and replace all endpoint prefix + tmp_content = json.dumps(content) + tmp_pattern = re.compile(real_prefix) + tmp_content = tmp_pattern.sub(proxy_prefix, tmp_content) + content = json.loads(tmp_content) + + return Response(headers={'X-Subject-Token': tmp_auth_token}, data=content, status=resp.status_code) + + except VimDriverNewtonException as e: + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + logger.error("HttpError: status:%s, response:%s" % (e.http_status, e.response.json())) + return Response(data=e.response.json(), status=e.http_status) + except Exception as e: + logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + def put(self, request, vimid="", servicetype="", requri=""): + logger.debug("Services--put::data> %s" % request.data) + logger.debug("Services--put::vimid, servicetype, requri> %s,%s,%s" + % (vimid, servicetype, requri)) + try: + # prepare request resource to vim instance + # get token: + tmp_auth_token = request.META.get('HTTP_X_AUTH_TOKEN', None) + if not tmp_auth_token: + return Response(data={'error': "No X-Auth-Token found in headers"}, status=status.HTTP_401_UNAUTHORIZED) + + vim = VimDriverUtils.get_vim_info(vimid) + # fetch the auth_state out of cache + tmp_auth_state, metadata_catalog = VimDriverUtils.get_token_cache(vim, tmp_auth_token) + if not tmp_auth_state: + return Response(data={'error': "Expired X-Auth-Token found in headers"}, + status=status.HTTP_401_UNAUTHORIZED) + + real_prefix = None + proxy_prefix = None + suffix = None + if servicetype and metadata_catalog: +# logger.error("metadata_catalog:%s" % metadata_catalog) + metadata_catalog = json.loads(metadata_catalog) + service_metadata = metadata_catalog.get(servicetype, None) + if service_metadata: + real_prefix = service_metadata['prefix'] + proxy_prefix = service_metadata['proxy_prefix'] + suffix = service_metadata['suffix'] + + if not real_prefix or not proxy_prefix: + raise VimDriverNewtonException(message="internal state error", + content="invalid cached metadata", + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) + + if requri == suffix: + requri = None + + if suffix and requri: + #remove the suffix from the requri to avoid duplicated suffix in real request uri later + tmp_pattern = re.compile(suffix) + requri = tmp_pattern.sub('', requri) + + sess = VimDriverUtils.get_session(vim, tenantid=None, auth_state=tmp_auth_state) + req_resource = "" + if requri and requri != "": + req_resource = "/" + requri + + cloud_owner, regionid = extsys.decode_vim_id(vimid) + interface = 'public' + service = {'service_type': servicetype, + 'interface': interface, + 'region_id': regionid} + + resp = sess.put(req_resource, data=json.JSONEncoder().encode(request.data),endpoint_filter=service) + # update token cache in case the token was required during the requests + tmp_auth_token = VimDriverUtils.update_token_cache(vim, sess, tmp_auth_token, tmp_auth_state) + content = resp.json() + + #filter the resp content and replace all endpoint prefix + tmp_content = json.dumps(content) + tmp_pattern = re.compile(real_prefix) + tmp_content = tmp_pattern.sub(proxy_prefix, tmp_content) + content = json.loads(tmp_content) + + return Response(headers={'X-Subject-Token': tmp_auth_token}, data=content, status=resp.status_code) + + except VimDriverNewtonException as e: + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + logger.error("HttpError: status:%s, response:%s" % (e.http_status, e.response.json())) + return Response(data=e.response.json(), status=e.http_status) + except Exception as e: + logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + + def patch(self, request, vimid="", servicetype="", requri=""): + logger.debug("Services--patch::data> %s" % request.data) + logger.debug("Services--patch::vimid, servicetype, requri> %s,%s,%s" + % (vimid, servicetype, requri)) + try: + # prepare request resource to vim instance + # get token: + tmp_auth_token = request.META.get('HTTP_X_AUTH_TOKEN', None) + if not tmp_auth_token: + return Response(data={'error': "No X-Auth-Token found in headers"}, status=status.HTTP_401_UNAUTHORIZED) + + vim = VimDriverUtils.get_vim_info(vimid) + # fetch the auth_state out of cache + tmp_auth_state, metadata_catalog = VimDriverUtils.get_token_cache(vim, tmp_auth_token) + if not tmp_auth_state: + return Response(data={'error': "Expired X-Auth-Token found in headers"}, + status=status.HTTP_401_UNAUTHORIZED) + + real_prefix = None + proxy_prefix = None + suffix = None + if servicetype and metadata_catalog: +# logger.error("metadata_catalog:%s" % metadata_catalog) + metadata_catalog = json.loads(metadata_catalog) + service_metadata = metadata_catalog.get(servicetype, None) + if service_metadata: + real_prefix = service_metadata['prefix'] + proxy_prefix = service_metadata['proxy_prefix'] + suffix = service_metadata['suffix'] + + if not real_prefix or not proxy_prefix: + raise VimDriverNewtonException(message="internal state error", + content="invalid cached metadata", + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) + + if requri == suffix: + requri = None + + if suffix and requri: + #remove the suffix from the requri to avoid duplicated suffix in real request uri later + tmp_pattern = re.compile(suffix) + requri = tmp_pattern.sub('', requri) + + sess = VimDriverUtils.get_session(vim, tenantid=None, auth_state=tmp_auth_state) + req_resource = "" + if requri and requri != "": + req_resource = "/" if re.match(r'//', requri) else ''+ requri + + cloud_owner, regionid = extsys.decode_vim_id(vimid) + interface = 'public' + service = {'service_type': servicetype, + 'interface': interface, + 'region_id': regionid} + + resp = sess.patch(req_resource, data=json.JSONEncoder().encode(request.data),endpoint_filter=service) + # update token cache in case the token was required during the requests + tmp_auth_token = VimDriverUtils.update_token_cache(vim, sess, tmp_auth_token, tmp_auth_state) + content = resp.json() + + #filter the resp content and replace all endpoint prefix + tmp_content = json.dumps(content) + tmp_pattern = re.compile(real_prefix) + tmp_content = tmp_pattern.sub(proxy_prefix, tmp_content) + content = json.loads(tmp_content) + + return Response(headers={'X-Subject-Token': tmp_auth_token}, data=content, status=resp.status_code) + + except VimDriverNewtonException as e: + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + logger.error("HttpError: status:%s, response:%s" % (e.http_status, e.response.json())) + return Response(data=e.response.json(), status=e.http_status) + except Exception as e: + logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + def delete(self, request, vimid="", servicetype="", requri=""): + logger.debug("Services--delete::data> %s" % request.data) + logger.debug("Services--delete::vimid, servicetype, requri> %s,%s,%s" + % (vimid, servicetype, requri)) + try: + # prepare request resource to vim instance + # get token: + tmp_auth_token = request.META.get('HTTP_X_AUTH_TOKEN', None) + if not tmp_auth_token: + return Response(data={'error': "No X-Auth-Token found in headers"}, status=status.HTTP_401_UNAUTHORIZED) + + vim = VimDriverUtils.get_vim_info(vimid) + # fetch the auth_state out of cache + tmp_auth_state, metadata_catalog = VimDriverUtils.get_token_cache(vim, tmp_auth_token) + if not tmp_auth_state: + return Response(data={'error': "Expired X-Auth-Token found in headers"}, + status=status.HTTP_401_UNAUTHORIZED) + + real_prefix = None + proxy_prefix = None + suffix = None + if servicetype and metadata_catalog: +# logger.error("metadata_catalog:%s" % metadata_catalog) + metadata_catalog = json.loads(metadata_catalog) + service_metadata = metadata_catalog.get(servicetype, None) + if service_metadata: + real_prefix = service_metadata['prefix'] + proxy_prefix = service_metadata['proxy_prefix'] + suffix = service_metadata['suffix'] + + if not real_prefix or not proxy_prefix: + raise VimDriverNewtonException(message="internal state error", + content="invalid cached metadata", + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) + + if requri == suffix: + requri = None + + if suffix and requri: + #remove the suffix from the requri to avoid duplicated suffix in real request uri later + tmp_pattern = re.compile(suffix) + requri = tmp_pattern.sub('', requri) + + sess = VimDriverUtils.get_session(vim, tenantid=None, auth_state=tmp_auth_state) + req_resource = "" + if requri and requri != "": + req_resource = "/" if re.match(r'//', requri) else ''+ requri + + cloud_owner, regionid = extsys.decode_vim_id(vimid) + interface = 'public' + service = {'service_type': servicetype, + 'interface': interface, + 'region_id': regionid} + + resp = sess.delete(req_resource, endpoint_filter=service) + # update token cache in case the token was required during the requests + tmp_auth_token = VimDriverUtils.update_token_cache(vim, sess, tmp_auth_token, tmp_auth_state) + content = resp.json() + + #filter the resp content and replace all endpoint prefix + tmp_content = json.dumps(content) + tmp_pattern = re.compile(real_prefix) + tmp_content = tmp_pattern.sub(proxy_prefix, tmp_content) + content = json.loads(tmp_content) + + return Response(headers={'X-Subject-Token': tmp_auth_token}, data=content, status=resp.status_code) + + except VimDriverNewtonException as e: + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + logger.error("HttpError: status:%s, response:%s" % (e.http_status, e.response.json())) + return Response(data=e.response.json(), status=e.http_status) + except Exception as e: + logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +class GetTenants(Services): + ''' + Backward compatible API for /v2.0/tenants + ''' + def get(self, request, vimid="", servicetype="identity", requri='projects'): + logger.debug("GetTenants--get::data> %s" % request.data) + logger.debug("GetTenants--get::vimid, servicetype, requri> %s,%s,%s" + % (vimid, servicetype, requri)) + + tmp_auth_token = request.META.get('HTTP_X_AUTH_TOKEN', None) + + resp = super(GetTenants,self).get(request, vimid, servicetype, requri) + if resp.status_code == status.HTTP_200_OK: + content = resp.data + return Response(headers={'X-Subject-Token': tmp_auth_token}, data={'tenants': content['projects'],'tenants_links':[]}, + status=resp.status_code) + else: + return resp + + def head(self, request, vimid="", servicetype="", requri=""): + return Response(data={'error': 'unsupported operation'}, status=status.HTTP_400_BAD_REQUEST) + + def post(self, request, vimid="", servicetype="", requri=""): + return Response(data={'error': 'unsupported operation'}, status=status.HTTP_400_BAD_REQUEST) + + def put(self, request, vimid="", servicetype="", requri=""): + return Response(data={'error': 'unsupported operation'}, status=status.HTTP_400_BAD_REQUEST) + + def patch(self, request, vimid="", servicetype="", requri=""): + return Response(data={'error': 'unsupported operation'}, status=status.HTTP_400_BAD_REQUEST) + + def delete(self, request, vimid="", servicetype="", requri=""): + return Response(data={'error': 'unsupported operation'}, status=status.HTTP_400_BAD_REQUEST) diff --git a/newton/newton/pub/config/config.py b/newton/newton/pub/config/config.py index e27682bd..fc7775b5 100644 --- a/newton/newton/pub/config/config.py +++ b/newton/newton/pub/config/config.py @@ -13,35 +13,7 @@ import os # [MSB] MSB_SERVICE_IP = '127.0.0.1' -MSB_SERVICE_PORT = '10080' +MSB_SERVICE_PORT = '80' # [IMAGE LOCAL PATH] ROOT_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -# [REDIS] -REDIS_HOST = '127.0.0.1' -REDIS_PORT = '6379' -REDIS_PASSWD = '' - -# [mysql] -DB_IP = "127.0.0.1" -DB_PORT = 3306 -DB_NAME = "multivimnewton" -DB_USER = "root" -DB_PASSWD = "password" - -# [register] -REG_TO_MSB_WHEN_START = True -REG_TO_MSB_REG_URL = "/openoapi/microservices/v1/services" -REG_TO_MSB_REG_PARAM = { - "serviceName": "multivim-newton", - "version": "v1", - "url": "/openoapi/multivim-newton/v1", - "protocol": "REST", - "visualRange": "1", - "nodes": [{ - "ip": "127.0.0.1", - "port": "9003", - "ttl": 0 - }] -} diff --git a/newton/newton/pub/msapi/extsys.py b/newton/newton/pub/msapi/extsys.py index daab5037..449ad5b5 100644 --- a/newton/newton/pub/msapi/extsys.py +++ b/newton/newton/pub/msapi/extsys.py @@ -11,6 +11,7 @@ import json import logging +import re from rest_framework import status from newton.pub.exceptions import VimDriverNewtonException @@ -18,22 +19,67 @@ from newton.pub.utils.restcall import req_by_msb logger = logging.getLogger(__name__) +def get_vim_by_id(vim_id): -def get_vims(): - 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(content) + cloud_owner,cloud_region_id = decode_vim_id(vim_id) + if cloud_owner and cloud_region_id: + retcode, content, status_code = \ + req_by_msb("/api/aai-cloudInfrastructure/v1/cloud-infrastructure/cloud-regions/cloud-region/%s/%s" + % (cloud_owner,cloud_region_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:%s,%s) from extsys." % (vim_id,cloud_owner,cloud_region_id), + status_code, content) + tmp_viminfo = json.JSONDecoder().decode(content) + #convert vim information + #tbd + + if tmp_viminfo: + viminfo = {} + viminfo['vimId'] = vim_id + viminfo['cloud_owner'] = cloud_owner + viminfo['cloud_region_id'] = cloud_region_id + viminfo['type'] = tmp_viminfo['cloud-type'] + viminfo['name'] = tmp_viminfo['complex-name'] + viminfo['version'] = tmp_viminfo['cloud-region-version'] + viminfo['cloud_extra_info'] = tmp_viminfo['cloud-extra-info'] + viminfo['cloud_epa_caps'] = tmp_viminfo['cloud-epa-caps'] + + tmp_authinfo = tmp_viminfo['auth-info-items'][0] + if tmp_authinfo: + viminfo['userName'] = tmp_authinfo['username'] + viminfo['password'] = tmp_authinfo['password'] + viminfo['domain'] = tmp_authinfo['cloud-domain'] + viminfo['url'] = tmp_authinfo['auth-url'] + viminfo['tenant'] = tmp_authinfo['defaultTenant']['name'] if not tmp_authinfo['defaultTenant'] else None + viminfo['cacert'] = tmp_authinfo['ssl-cacert'] + viminfo['insecure'] = tmp_authinfo['ssl-insecure'] + + return viminfo + else: + return None + else: + return None + +def delete_vim_by_id(vim_id): + cloud_owner, cloud_region_id = decode_vim_id(vim_id) + if cloud_owner and cloud_region_id: + retcode, content, status_code = \ + req_by_msb("/api/aai-cloudInfrastructure/v1/cloud-infrastructure/cloud-regions/cloud-region/%s/%s" + % ( cloud_owner, cloud_region_id), "DELETE") + if retcode != 0: + logger.error("Status code is %s, detail is %s.", status_code, content) + raise VimDriverNewtonException( + "Failed to delete VIM in AAI with id (%s:%s,%s) from extsys." % (vim_id,cloud_owner,cloud_region_id), + status_code, content) + return 0 + # return non zero if failed to decode cloud owner and region id + return 1 + +def decode_vim_id(vim_id): + m = re.search(r'^([0-9a-zA-Z-]+)_([0-9a-zA-Z_-]+)$', vim_id) + cloud_owner, cloud_region_id = m.group(1), m.group(2) + return cloud_owner, cloud_region_id -def get_vim_by_id(vim_id): - 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, - status.HTTP_404_NOT_FOUND, content) - return json.JSONDecoder().decode(content) diff --git a/newton/newton/registration/__init__.py b/newton/newton/registration/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/newton/newton/registration/__init__.py @@ -0,0 +1,10 @@ +# 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. diff --git a/newton/newton/registration/views/__init__.py b/newton/newton/registration/views/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/newton/newton/registration/views/__init__.py @@ -0,0 +1,10 @@ +# 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. diff --git a/newton/newton/registration/views/registration.py b/newton/newton/registration/views/registration.py new file mode 100644 index 00000000..9f2f31be --- /dev/null +++ b/newton/newton/registration/views/registration.py @@ -0,0 +1,93 @@ +# 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 +import traceback + +from django.core.cache import cache + +from keystoneauth1 import access +from keystoneauth1.access import service_catalog +from keystoneauth1.exceptions import HttpError +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from newton.pub.exceptions import VimDriverNewtonException +from newton.requests.views.util import VimDriverUtils + +logger = logging.getLogger(__name__) + +DEBUG=True + +class Registry(APIView): + + def post(self, request, vimid=""): + logger.debug("Registration--post::data> %s" % request.data) + logger.debug("Registration--post::vimid > %s" % vimid) + + try: + # prepare request resource to vim instance + # get token: + vim = VimDriverUtils.get_vim_info(vimid) + #set the default tenant since there is no tenant info in the VIM yet + sess = VimDriverUtils.get_session(vim, tenantname=request.data['defaultTenant']) + + #step 1. discover all projects and populate into AAI + req_resource = "/projects" + service = {'service_type': "identity", + 'interface': 'public', + 'region_id': vim['cloud_region_id']} + + resp = sess.get(req_resource, endpoint_filter=service) + content = resp.json() + #iterate all projects and populate them into AAI + # TBD + + # discover all flavors + # discover all images + # discover all az + # discover all vg + # discover all snapshots + # discover all server groups + # discover all pservers + # discover all epa resources, e.g. sriov pf and vf, etc. + + return Response(status=status.HTTP_202_ACCEPTED) + + except VimDriverNewtonException as e: + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + logger.error("HttpError: status:%s, response:%s" % (e.http_status, e.response.json())) + return Response(data=e.response.json(), status=e.http_status) + except Exception as e: + logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + def delete(self, request, vimid=""): + logger.debug("Registration--delete::data> %s" % request.data) + logger.debug("Registration--delete::vimid > %s"% vimid) + try: + ret_code = VimDriverUtils.delete_vim_info(vimid) + return Response(status=status.HTTP_202_ACCEPTED) + except VimDriverNewtonException as e: + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + logger.error("HttpError: status:%s, response:%s" % (e.http_status, e.response.json())) + return Response(data=e.response.json(), status=e.http_status) + except Exception as e: + logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/newton/newton/requests/views/util.py b/newton/newton/requests/views/util.py index 089a0e1f..eab4c370 100644 --- a/newton/newton/requests/views/util.py +++ b/newton/newton/requests/views/util.py @@ -13,11 +13,17 @@ # limitations under the License. import logging +import datetime + +from django.core.cache import cache + +from keystoneauth1 import _utils as utils from keystoneauth1.identity import v2 as keystone_v2 from keystoneauth1.identity import v3 as keystone_v3 from keystoneauth1 import session -from newton.pub.msapi.extsys import get_vim_by_id +#from newton.pub.msapi.extsys import get_vim_by_id +from newton.pub.msapi import extsys logger = logging.getLogger(__name__) @@ -27,9 +33,13 @@ class VimDriverUtils(object): 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) + vim = extsys.get_vim_by_id(vimid) return vim + @staticmethod + def delete_vim_info(vimid): + return extsys.delete_vim_by_id(vimid) + @staticmethod def get_query_part(request): query = "" @@ -39,25 +49,117 @@ class VimDriverUtils(object): return query @staticmethod - def get_session(vim, tenantid=None): + def get_session(vim, tenantid=None, tenantname=None, auth_state=None): """ - get vim info from ESR and create auth plugin and session object + get session object and optionally preload auth_state """ auth = None - if '/v2' in vim["url"]: - auth = keystone_v2.Password(auth_url=vim["url"], - username=vim["userName"], - password=vim["password"], - tenant_name=vim["tenant"]) - elif '/v3' in vim["url"]: - auth = keystone_v3.Password(auth_url=vim["url"], - username=vim["userName"], - password=vim["password"], - project_name=vim["tenant"], - user_domain_id='default', - project_domain_id='default') + + #tenantid takes precedence over tenantname + if not tenantid: + #input tenant name takes precedence over the default one from AAI data store + tenant_name = tenantname if tenantname else vim['tenant'] + + if tenantid: + if '/v2' in vim["url"]: + auth = keystone_v2.Password(auth_url=vim["url"], + username=vim["userName"], + password=vim["password"], + tenant_id=tenantid) + elif '/v3' in vim["url"]: + auth = keystone_v3.Password(auth_url=vim["url"], + username=vim["userName"], + password=vim["password"], + project_id=tenantid) + elif tenant_name: + if '/v2' in vim["url"]: + auth = keystone_v2.Password(auth_url=vim["url"], + username=vim["userName"], + password=vim["password"], + tenant_name=tenant_name) + elif '/v3' in vim["url"]: + auth = keystone_v3.Password(auth_url=vim["url"], + username=vim["userName"], + password=vim["password"], + project_name=tenant_name, + user_domain_name=vim["domain"], + project_domain_name=vim["domain"]) + + else: + #something wrong + return None + pass + + #preload auth_state which was acquired in last requests + if auth_state: + auth.set_auth_state(auth_state) + return session.Session(auth=auth) + + @staticmethod + def get_auth_state(vim, session): + auth = session._auth_required(None, 'fetch a token') + if not auth: + return None + + #trigger the authenticate request + session.get_auth_headers(auth) + +# norm_expires = utils.normalize_time(auth.expires) + + #return a string dump of json object with token and resp_data of authentication request + return auth.get_auth_state() +# return auth.get_auth_ref(session) + + @staticmethod + def get_token_cache(vim, token): + ''' + get auth_state and metadata fromm cache + :param vim: + :param token: + :return: + ''' + metadata_key = "meta_%s" % token + return cache.get(token), cache.get(metadata_key) + + + @staticmethod + def update_token_cache(vim, session, old_token, auth_state, metadata=None): + ''' + cache the auth_state as well as metadata_catalog + :param vim: + :param session: + :param old_token: + :param auth_state: + :param matadata: + :return: + ''' + + metadata_key = "meta_%s" % old_token + + if not metadata: + metadata = cache.get(metadata_key) + if not metadata: + #something wrong since metadata is neither inputted, nor cached previously. but ignore temporarily + pass + + tmp_auth_token = session.get_token() + #check if need to update token:auth_state mapping + if tmp_auth_token != old_token: + cache.delete(old_token) + cache.delete(metadata_key) + metadata_key = "meta_%s" % tmp_auth_token + + elif not cache.get(old_token): + # store the auth_state, memcached + # set expiring in 1 hour + cache.set(tmp_auth_token, auth_state, 3600) + cache.set(metadata_key, metadata, 3600) + + #return new token + return tmp_auth_token + @staticmethod def replace_a_key(dict_obj, keypair, reverse=False): old_key, new_key = None, None diff --git a/newton/newton/settings.py b/newton/newton/settings.py index b0be17e8..682e8eb4 100644 --- a/newton/newton/settings.py +++ b/newton/newton/settings.py @@ -66,13 +66,6 @@ REST_FRAMEWORK = { ) } -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } -} - TIME_ZONE = 'UTC' # Static files (CSS, JavaScript, Images) @@ -110,14 +103,16 @@ LOGGING = { } } +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'LOCATION': '127.0.0.1:11211', + } +} + if 'test' in sys.argv: from newton.pub.config import config - config.REG_TO_MSB_WHEN_START = False - DATABASES = {} - DATABASES['default'] = { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ':memory:', - } + REST_FRAMEWORK = {} import platform diff --git a/newton/newton/swagger/multivim.flavor.swagger.json b/newton/newton/swagger/multivim.flavor.swagger.json index 2053e039..e73be9ed 100644 --- a/newton/newton/swagger/multivim.flavor.swagger.json +++ b/newton/newton/swagger/multivim.flavor.swagger.json @@ -4,7 +4,7 @@ "version": "1.0.0", "title": "MultiVIM Service rest API" }, - "basePath": "/openoapi/multivim/v1/", + "basePath": "/api/multicloud-newton/v0/", "tags": [ { "name": "MultiVIM services" diff --git a/newton/newton/swagger/multivim.host.swagger.json b/newton/newton/swagger/multivim.host.swagger.json index e04b5267..1ea4603e 100644 --- a/newton/newton/swagger/multivim.host.swagger.json +++ b/newton/newton/swagger/multivim.host.swagger.json @@ -4,7 +4,7 @@ "version": "1.0.0", "title": "MultiVIM Service rest API" }, - "basePath": "/openoapi/multivim/v1/", + "basePath": "/api/multicloud-newton/v0/", "tags": [ { "name": "MultiVIM services" diff --git a/newton/newton/swagger/multivim.image.swagger.json b/newton/newton/swagger/multivim.image.swagger.json index dcbba06b..1ff3741a 100644 --- a/newton/newton/swagger/multivim.image.swagger.json +++ b/newton/newton/swagger/multivim.image.swagger.json @@ -4,7 +4,7 @@ "version": "1.0.0", "title": "MultiVIM Service rest API" }, - "basePath": "/openoapi/multivim/v1/", + "basePath": "/api/multicloud-newton/v0/", "tags": [ { "name": "MultiVIM services" diff --git a/newton/newton/swagger/multivim.limit.swagger.json b/newton/newton/swagger/multivim.limit.swagger.json index 4665af47..3fea1b72 100644 --- a/newton/newton/swagger/multivim.limit.swagger.json +++ b/newton/newton/swagger/multivim.limit.swagger.json @@ -4,7 +4,7 @@ "version": "1.0.0", "title": "MultiVIM Service rest API" }, - "basePath": "/openoapi/multivim/v1/", + "basePath": "/api/multicloud-newton/v0/", "tags": [ { "name": "MultiVIM services" diff --git a/newton/newton/swagger/multivim.network.swagger.json b/newton/newton/swagger/multivim.network.swagger.json index fb8524bc..3962e891 100644 --- a/newton/newton/swagger/multivim.network.swagger.json +++ b/newton/newton/swagger/multivim.network.swagger.json @@ -4,7 +4,7 @@ "version": "1.0.0", "title": "MultiVIM Service rest API" }, - "basePath": "/openoapi/multivim/v1/", + "basePath": "/api/multicloud-newton/v0/", "tags": [ { "name": "MultiVIM services" diff --git a/newton/newton/swagger/multivim.server.swagger.json b/newton/newton/swagger/multivim.server.swagger.json index 14b1d0e9..c309a394 100644 --- a/newton/newton/swagger/multivim.server.swagger.json +++ b/newton/newton/swagger/multivim.server.swagger.json @@ -4,7 +4,7 @@ "version": "1.0.0", "title": "MultiVIM Service rest API" }, - "basePath": "/openoapi/multivim/v1/", + "basePath": "/api/multicloud-newton/v0/", "tags": [ { "name": "MultiVIM services" diff --git a/newton/newton/swagger/multivim.subnet.swagger.json b/newton/newton/swagger/multivim.subnet.swagger.json index 301c0fc8..ae466d49 100644 --- a/newton/newton/swagger/multivim.subnet.swagger.json +++ b/newton/newton/swagger/multivim.subnet.swagger.json @@ -4,7 +4,7 @@ "version": "1.0.0", "title": "MultiVIM Service rest API" }, - "basePath": "/openoapi/multivim/v1/", + "basePath": "/api/multicloud-newton/v0/", "tags": [ { "name": "MultiVIM services" diff --git a/newton/newton/swagger/multivim.tenant.swagger.json b/newton/newton/swagger/multivim.tenant.swagger.json index 0f718745..ed0c6c45 100644 --- a/newton/newton/swagger/multivim.tenant.swagger.json +++ b/newton/newton/swagger/multivim.tenant.swagger.json @@ -4,7 +4,7 @@ "version": "1.0.0", "title": "MultiVIM Service rest API" }, - "basePath": "/openoapi/multivim/v1/", + "basePath": "/api/multicloud-newton/v0/", "tags": [ { "name": "MultiVIM services" diff --git a/newton/newton/swagger/multivim.volume.swagger.json b/newton/newton/swagger/multivim.volume.swagger.json index fda75973..7dd787d1 100644 --- a/newton/newton/swagger/multivim.volume.swagger.json +++ b/newton/newton/swagger/multivim.volume.swagger.json @@ -4,7 +4,7 @@ "version": "1.0.0", "title": "MultiVIM Service rest API" }, - "basePath": "/openoapi/multivim/v1/", + "basePath": "/api/multicloud-newton/v0/", "tags": [ { "name": "MultiVIM services" diff --git a/newton/newton/swagger/multivim.vport.swagger.json b/newton/newton/swagger/multivim.vport.swagger.json index 5a70dfbc..5b0a04a7 100644 --- a/newton/newton/swagger/multivim.vport.swagger.json +++ b/newton/newton/swagger/multivim.vport.swagger.json @@ -4,7 +4,7 @@ "version": "1.0.0", "title": "MultiVIM Service rest API" }, - "basePath": "/openoapi/multivim/v1/", + "basePath": "/api/multicloud-newton/v0/", "tags": [ { "name": "MultiVIM services" diff --git a/newton/newton/swagger/tests.py b/newton/newton/swagger/tests.py index 299f3ff9..29efb26b 100644 --- a/newton/newton/swagger/tests.py +++ b/newton/newton/swagger/tests.py @@ -23,7 +23,7 @@ class SampleViewTest(unittest.TestCase): pass def test_sample(self): - response = self.client.get("/openoapi/multivim-newton/v1/swagger.json") + response = self.client.get("/api/multicloud-newton/v0/swagger.json") self.assertEqual(status.HTTP_200_OK, response.status_code, response.content) # resp_data = json.loads(response.content) # self.assertEqual({"status": "active"}, resp_data) diff --git a/newton/newton/swagger/urls.py b/newton/newton/swagger/urls.py index 279a430e..76c0ed46 100644 --- a/newton/newton/swagger/urls.py +++ b/newton/newton/swagger/urls.py @@ -16,7 +16,7 @@ from newton.swagger import views from newton.swagger.views import SwaggerJsonView urlpatterns = [ - url(r'^openoapi/multivim-newton/v1/swagger.json$', SwaggerJsonView.as_view()), + url(r'^api/multicloud-newton/v0/swagger.json$', SwaggerJsonView.as_view()), ] urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/newton/newton/swagger/views.py b/newton/newton/swagger/views.py index 2e87bc74..66d42d68 100644 --- a/newton/newton/swagger/views.py +++ b/newton/newton/swagger/views.py @@ -83,7 +83,7 @@ class SwaggerJsonView(APIView): f.close() json_data["paths"].update(json_data_temp["paths"]) json_data["definitions"].update(json_data_temp["definitions"]) - json_data["basePath"] = "/openoapi/multivim-newton/v1/" + json_data["basePath"] = "/api/multicloud-newton/v0/" json_data["info"]["title"] = "MultiVIM driver of OpenStack Newton Service NBI" return Response(json_data) diff --git a/newton/newton/urls.py b/newton/newton/urls.py index e831feb6..3c740a06 100644 --- a/newton/newton/urls.py +++ b/newton/newton/urls.py @@ -10,24 +10,25 @@ # 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.registration.views import registration from newton.requests.views import tenants urlpatterns = [ url(r'^', include('newton.swagger.urls')), url(r'^', include('newton.samples.urls')), - url(r'^openoapi/multivim-newton/v1/(?P[0-9a-zA-Z_-]+)/tenants$', + url(r'^api/multicloud-newton/v0/(?P[0-9a-zA-Z_-]+)/registry$', + registration.Registry.as_view()), + url(r'^api/multicloud-newton/v0/(?P[0-9a-zA-Z_-]+)$', + registration.Registry.as_view()), + url(r'^api/multicloud-newton/v0/(?P[0-9a-zA-Z_-]+)/exten', + include('newton.extensions.urls')), + url(r'^api/multicloud-newton/v0/(?P[0-9a-zA-Z_-]+)/', + include('newton.proxy.urls')), + url(r'^api/multicloud-newton/v0/(?P[0-9a-zA-Z_-]+)/tenants$', tenants.Tenants.as_view()), - url(r'^openoapi/multivim-newton/v1/(?P[0-9a-zA-Z_-]+)/' + url(r'^api/multicloud-newton/v0/(?P[0-9a-zA-Z_-]+)/' '(?P[0-9a-zA-Z_-]{8,})/', include('newton.requests.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)) diff --git a/newton/pom.xml b/newton/pom.xml index 0c8782ed..6d668991 100644 --- a/newton/pom.xml +++ b/newton/pom.xml @@ -13,17 +13,17 @@ --> - org.openo.multivimdriver.openstack - multivimdriver-openstack-root - 1.1.0-SNAPSHOT + org.onap.multicloud.openstack + multicloud-openstack-root + 1.0.0-SNAPSHOT 4.0.0 - org.openo.multivimdriver.openstack - multivimdriver-openstack-newton - 1.1.0-SNAPSHOT + org.onap.multicloud.openstack + multicloud-openstack-newton + 1.0.0-SNAPSHOT pom - multivimdriver/openstack/newton - multivimdriver for openstack newton + multicloud/openstack/newton + multicloud for openstack newton diff --git a/newton/requirements.txt b/newton/requirements.txt index 5976f5c9..841fe278 100644 --- a/newton/requirements.txt +++ b/newton/requirements.txt @@ -8,6 +8,9 @@ httplib2==0.9.2 # for call openstack auth and transport api keystoneauth1==2.18.0 +#python-memcached +python-memcached + # for unit test coverage==4.2 mock==2.0.0 diff --git a/newton/tox.ini b/newton/tox.ini index 87aca481..4e15a2e7 100644 --- a/newton/tox.ini +++ b/newton/tox.ini @@ -8,3 +8,5 @@ downloadcache = ~/cache/pip [testenv] deps = -r{toxinidir}/requirements.txt commands = coverage run --branch manage.py test newton + coverage html --omit="*test*,*__init__.py" -d htmlcov + diff --git a/pom.xml b/pom.xml index 276b6760..2234f546 100644 --- a/pom.xml +++ b/pom.xml @@ -21,15 +21,14 @@ 4.0.0 - org.openo.multivimdriver.openstack - multivimdriver-openstack-root - 1.1.0-SNAPSHOT - multivim-openstack + org.onap.multicloud.openstack + multicloud-openstack-root + 1.0.0-SNAPSHOT + multicloud/openstack pom newton - kilo -- cgit 1.2.3-korg