diff options
56 files changed, 1779 insertions, 467 deletions
@@ -8,4 +8,5 @@ logs/*.log .tox .coverage htmlcov/ - +.idea/ +mydatabase diff --git a/newton/newton/extensions/tests/__init__.py b/newton/newton/extensions/tests/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/newton/newton/extensions/tests/__init__.py diff --git a/newton/newton/extensions/tests/test_epacaps.py b/newton/newton/extensions/tests/test_epacaps.py new file mode 100644 index 00000000..65125a43 --- /dev/null +++ b/newton/newton/extensions/tests/test_epacaps.py @@ -0,0 +1,67 @@ +# Copyright (c) 2017 Intel Corporation. +# +# 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 +from django.test import Client +from rest_framework import status +import unittest + +from newton.requests.views.util import VimDriverUtils + +MOCK_VIM_INFO = { + "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', +} + + +class TestEpaCaps(unittest.TestCase): + def setUp(self): + self.client = Client() + + @mock.patch.object(VimDriverUtils, 'get_vim_info') + def test_get_epa_caps_info(self, mock_get_vim_info): + mock_get_vim_info.return_value = MOCK_VIM_INFO + cloud_owner = "windriver-hudson-dc" + cloud_region_id = "RegionOne" + vimid = cloud_owner + "_" + cloud_region_id + + response = self.client.get( + "/api/multicloud-newton/v0/" + vimid + "/extensions/epa-caps") + json_content = response.json() + + self.assertEquals(status.HTTP_200_OK, response.status_code) + self.assertEquals(4, len(json_content.keys())) + self.assertEquals(cloud_owner, json_content["cloud-owner"]) + self.assertEquals(cloud_region_id, json_content["cloud-region-id"]) + self.assertEquals(vimid, json_content["vimid"]) + self.assertEquals(json.loads(MOCK_VIM_INFO['cloud_epa_caps']), + json_content["cloud-epa-caps"])
\ No newline at end of file diff --git a/newton/newton/extensions/tests/test_extensions.py b/newton/newton/extensions/tests/test_extensions.py new file mode 100644 index 00000000..89ef0d11 --- /dev/null +++ b/newton/newton/extensions/tests/test_extensions.py @@ -0,0 +1,45 @@ +# Copyright (c) 2017 Intel Corporation. +# +# 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 Client +from rest_framework import status +import unittest + + +class TestExtensions(unittest.TestCase): + def setUp(self): + self.client = Client() + + def test_get_extensions_info(self): + cloud_owner = "windriver-hudson-dc" + cloud_region_id = "RegionOne" + vimid = cloud_owner + "_" + cloud_region_id + + response = self.client.get( + "/api/multicloud-newton/v0/" + vimid + "/extensions/") + json_content = response.json() + + self.assertEquals(status.HTTP_200_OK, response.status_code) + self.assertEquals(4, len(json_content.keys())) + + self.assertEquals(cloud_owner, json_content["cloud-owner"]) + self.assertEquals(cloud_region_id, json_content["cloud-region-id"]) + self.assertEquals(vimid, json_content["vimid"]) + + self.assertEquals("epa-caps", json_content["extensions"][0]["alias"]) + self.assertEquals("Multiple network support", json_content["extensions"][0]["description"]) + self.assertEquals("EPACapsQuery", json_content["extensions"][0]["name"]) + self.assertEquals("http://127.0.0.1:80/api/multicloud-newton/v0/%s/extensions/epa-caps" % vimid, + json_content["extensions"][0]["url"]) + self.assertEquals("", json_content["extensions"][0]["spec"])
\ No newline at end of file diff --git a/newton/newton/extensions/views/epacaps.py b/newton/newton/extensions/views/epacaps.py index 54bd0ebe..e41d16c2 100644 --- a/newton/newton/extensions/views/epacaps.py +++ b/newton/newton/extensions/views/epacaps.py @@ -11,12 +11,11 @@ # 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.exceptions import HttpError from rest_framework import status from rest_framework.response import Response @@ -29,8 +28,6 @@ from newton.pub.msapi import extsys logger = logging.getLogger(__name__) -DEBUG=True - class EpaCaps(APIView): @@ -55,7 +52,6 @@ class EpaCaps(APIView): "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: diff --git a/newton/newton/extensions/views/extensions.py b/newton/newton/extensions/views/extensions.py index a196edcc..c9f4df7b 100644 --- a/newton/newton/extensions/views/extensions.py +++ b/newton/newton/extensions/views/extensions.py @@ -52,7 +52,7 @@ class Extensions(APIView): "alias": "epa-caps", "description": "Multiple network support", "name": "EPACapsQuery", - "url": self.proxy_prefix+"/%s/extensions/epa-caps" \ + "url": self.proxy_prefix + "/%s/extensions/epa-caps" \ % (vimid), "spec": "" } diff --git a/newton/newton/proxy/tests/test_service_proxy.py b/newton/newton/proxy/tests/test_service_proxy.py index 2dd558d5..adac3002 100644 --- a/newton/newton/proxy/tests/test_service_proxy.py +++ b/newton/newton/proxy/tests/test_service_proxy.py @@ -11,19 +11,17 @@ # 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 +import copy +import json from django.test import Client +import mock from rest_framework import status +import unittest -from keystoneauth1 import session -from keystoneauth1.exceptions import HttpError from newton.requests.views.util import VimDriverUtils -from newton.proxy.views.services import Services, GetTenants MOCK_VIM_INFO = { "createTime": "2017-04-01 02:22:27", @@ -696,7 +694,7 @@ MOCK_PATCH_IMAGE_RESPONSE = { } -class mock_get_servers_response_specs(object): +class MockResponse(object): status_code = 200 content = '' @@ -707,68 +705,75 @@ class mock_get_servers_response_specs(object): class TestServiceProxy(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') @mock.patch.object(VimDriverUtils, 'get_token_cache') - def test_get(self, mock_get_token_cache, mock_update_token_cache, mock_get_auth_state, mock_get_session, mock_get_vim_info): - ''' - Test service proxy API: GET - - :param mock_get_token_cache: - :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.patch.object(VimDriverUtils, 'get_vim_info') + def test_get_token(self, mock_get_vim_info, mock_get_token_cache, mock_get_session): + mock_session_specs = ["head"] mock_session = mock.Mock(name='mock_session', spec=mock_session_specs) - mock_get_servers_response_obj = mock.Mock(spec=mock_get_servers_response_specs) + mock_get_servers_response_obj = mock.Mock(spec=MockResponse) mock_get_servers_response_obj.status_code=200 mock_get_servers_response_obj.content = MOCK_GET_SERVERS_RESPONSE mock_get_servers_response_obj.json.return_value=MOCK_GET_SERVERS_RESPONSE - mock_session.get.return_value = mock_get_servers_response_obj + mock_session.head.return_value = mock_get_servers_response_obj mock_get_vim_info.return_value = MOCK_VIM_INFO mock_get_session.return_value = mock_session - mock_get_auth_state.return_value = json.dumps(MOCK_AUTH_STATE) - mock_update_token_cache.return_value = MOCK_TOKEN_ID mock_get_token_cache.return_value = (json.dumps(MOCK_AUTH_STATE),json.dumps(MOCK_INTERNAL_METADATA_CATALOG)) + response = self.client.head( + "/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/compute/v2.1/fcca3cc49d5e42caae15459e27103efc/servers", + {}, HTTP_X_AUTH_TOKEN=MOCK_TOKEN_ID) + self.assertEquals(status.HTTP_200_OK, response.status_code) - #simulate client to make the request + def test_unauthorized_access(self): response = self.client.get( - "/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/compute/v2.1/fcca3cc49d5e42caae15459e27103efc/servers", - {}, HTTP_X_AUTH_TOKEN=MOCK_TOKEN_ID) + "/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/compute/v2.1/fcca3cc49d5e42caae15459e27103efc/servers") + self.assertEquals(status.HTTP_403_FORBIDDEN, response.status_code) - self.failUnlessEqual(status.HTTP_200_OK, response.status_code) - context = response.json() + @mock.patch.object(VimDriverUtils, 'get_vim_info') + def test_expired_auth_token(self, mock_get_vim_info): + mock_get_vim_info.return_value = MOCK_VIM_INFO + + response = self.client.get( + "/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/compute/v2.1/fcca3cc49d5e42caae15459e27103efc/servers", + {}, HTTP_X_AUTH_TOKEN=MOCK_TOKEN_ID) + self.assertEquals(status.HTTP_403_FORBIDDEN, response.status_code) + + @mock.patch.object(VimDriverUtils, 'get_token_cache') + @mock.patch.object(VimDriverUtils, 'get_vim_info') + def test_request_without_servicetype(self, mock_get_vim_info, mock_get_token_cache): + mock_get_vim_info.return_value = MOCK_VIM_INFO + mock_get_token_cache.return_value = (json.dumps(MOCK_AUTH_STATE), {}) + servicetype = "compute" + url = ("/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/" + servicetype + + "/v2.1/fcca3cc49d5e42caae15459e27103efc/servers") + response = self.client.get(url, {}, HTTP_X_AUTH_TOKEN=MOCK_TOKEN_ID) + self.assertEquals(status.HTTP_500_INTERNAL_SERVER_ERROR, response.status_code) - self.assertTrue(response['X-Subject-Token'] == MOCK_TOKEN_ID) - self.assertTrue(context['servers'] != None) + metadata_catalog = copy.deepcopy(MOCK_INTERNAL_METADATA_CATALOG) + metadata_catalog[servicetype] = None + mock_get_token_cache.return_value = (json.dumps(MOCK_AUTH_STATE), json.dumps(metadata_catalog)) - pass + response = self.client.get(url, {}, HTTP_X_AUTH_TOKEN=MOCK_TOKEN_ID) + self.assertEquals(status.HTTP_500_INTERNAL_SERVER_ERROR, response.status_code) + metadata_catalog = copy.deepcopy(MOCK_INTERNAL_METADATA_CATALOG) + metadata_catalog[servicetype]['prefix'] = None + metadata_catalog[servicetype]['proxy_prefix'] = None + mock_get_token_cache.return_value = (json.dumps(MOCK_AUTH_STATE), json.dumps(metadata_catalog)) + response = self.client.get(url, {}, HTTP_X_AUTH_TOKEN=MOCK_TOKEN_ID) + self.assertEquals(status.HTTP_500_INTERNAL_SERVER_ERROR, response.status_code) @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') @mock.patch.object(VimDriverUtils, 'get_token_cache') - def test_post(self, mock_get_token_cache, mock_update_token_cache, mock_get_auth_state, mock_get_session, mock_get_vim_info): + def test_crud_resources(self, mock_get_token_cache, mock_update_token_cache, mock_get_auth_state, mock_get_session, mock_get_vim_info): ''' - Test service proxy API: POST + Test service proxy API: GET :param mock_get_token_cache: :param mock_update_token_cache: @@ -779,14 +784,31 @@ class TestServiceProxy(unittest.TestCase): ''' #mock VimDriverUtils APIs - mock_session_specs = ["post"] + mock_session_specs = ["get", "post", "put", "patch", "delete"] - mock_session = mock.Mock(name='mock_session', spec=mock_session_specs) - mock_post_server_response_obj = mock.Mock(spec=mock_get_servers_response_specs) + mock_get_servers_response_obj = mock.Mock(spec=MockResponse) + mock_get_servers_response_obj.status_code=200 + mock_get_servers_response_obj.content = MOCK_GET_SERVERS_RESPONSE + mock_get_servers_response_obj.json.return_value=MOCK_GET_SERVERS_RESPONSE + + mock_post_server_response_obj = mock.Mock(spec=MockResponse) mock_post_server_response_obj.status_code=202 mock_post_server_response_obj.content = MOCK_POST_SERVER_RESPONSE mock_post_server_response_obj.json.return_value=MOCK_POST_SERVER_RESPONSE + + mock_patch_server_response_obj = mock.Mock(spec=MockResponse) + mock_patch_server_response_obj.status_code=202 + mock_patch_server_response_obj.content = MOCK_PATCH_IMAGE_REQUEST + mock_patch_server_response_obj.json.return_value=MOCK_PATCH_IMAGE_REQUEST + + mock_delete_server_response_obj = mock.Mock(spec=MockResponse) + mock_delete_server_response_obj.status_code=204 + + mock_session = mock.Mock(name='mock_session', spec=mock_session_specs) + mock_session.get.return_value = mock_get_servers_response_obj mock_session.post.return_value = mock_post_server_response_obj + mock_session.patch.return_value = mock_patch_server_response_obj + mock_session.delete.return_value = mock_delete_server_response_obj mock_get_vim_info.return_value = MOCK_VIM_INFO mock_get_session.return_value = mock_session @@ -794,57 +816,40 @@ class TestServiceProxy(unittest.TestCase): mock_update_token_cache.return_value = MOCK_TOKEN_ID mock_get_token_cache.return_value = (json.dumps(MOCK_AUTH_STATE),json.dumps(MOCK_INTERNAL_METADATA_CATALOG)) - #simulate client to make the request + # Create resource response = self.client.post( "/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/compute/v2.1/fcca3cc49d5e42caae15459e27103efc/servers", MOCK_POST_SERVER_REQUEST, HTTP_X_AUTH_TOKEN=MOCK_TOKEN_ID) - self.failUnlessEqual(status.HTTP_202_ACCEPTED, response.status_code) + self.assertEquals(status.HTTP_202_ACCEPTED, response.status_code) context = response.json() + self.assertEquals(MOCK_TOKEN_ID, response['X-Subject-Token']) + self.assertIsNotNone(context['server']) - self.assertTrue(response['X-Subject-Token'] == MOCK_TOKEN_ID) - self.assertTrue(context['server'] != None) - - 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') - @mock.patch.object(VimDriverUtils, 'get_token_cache') - def test_delete(self, mock_get_token_cache, mock_update_token_cache, mock_get_auth_state, mock_get_session, mock_get_vim_info): - ''' - Test service proxy API: DELETE - - :param mock_get_token_cache: - :param mock_update_token_cache: - :param mock_get_auth_state: - :param mock_get_session: - :param mock_get_vim_info: - :return: - ''' + # Retrieve resource + response = self.client.get( + "/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/compute/v2.1/fcca3cc49d5e42caae15459e27103efc/servers", + {}, HTTP_X_AUTH_TOKEN=MOCK_TOKEN_ID) + self.assertEquals(status.HTTP_200_OK, response.status_code) + context = response.json() - #mock VimDriverUtils APIs - mock_session_specs = ["delete"] + self.assertEquals(MOCK_TOKEN_ID, response['X-Subject-Token']) + self.assertIsNotNone(context['servers']) - mock_session = mock.Mock(name='mock_session', spec=mock_session_specs) - mock_post_server_response_obj = mock.Mock(spec=mock_get_servers_response_specs) - mock_post_server_response_obj.status_code=204 - mock_session.delete.return_value = mock_post_server_response_obj + # Update resource + response = self.client.get( + "/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/compute/v2.1/fcca3cc49d5e42caae15459e27103efc/servers", + {}, HTTP_X_AUTH_TOKEN=MOCK_TOKEN_ID) + self.assertEquals(status.HTTP_200_OK, response.status_code) + context = response.json() - mock_get_vim_info.return_value = MOCK_VIM_INFO - mock_get_session.return_value = mock_session - mock_get_auth_state.return_value = json.dumps(MOCK_AUTH_STATE) - mock_update_token_cache.return_value = MOCK_TOKEN_ID - mock_get_token_cache.return_value = (json.dumps(MOCK_AUTH_STATE),json.dumps(MOCK_INTERNAL_METADATA_CATALOG)) + self.assertEquals(MOCK_TOKEN_ID, response['X-Subject-Token']) + self.assertIsNotNone(context['servers']) #simulate client to make the request response = self.client.delete( "/api/multicloud-newton/v0/windriver-hudson-dc_RegionOne/compute/v2.1/fcca3cc49d5e42caae15459e27103efc/servers/324dfb7d-f4a9-419a-9a19-237df04b443b", HTTP_X_AUTH_TOKEN=MOCK_TOKEN_ID) - self.failUnlessEqual(status.HTTP_204_NO_CONTENT, response.status_code) - self.assertTrue(response['X-Subject-Token'] == MOCK_TOKEN_ID) - - pass + self.assertEquals(status.HTTP_204_NO_CONTENT, response.status_code) + self.assertEquals(MOCK_TOKEN_ID, response['X-Subject-Token']) diff --git a/newton/newton/proxy/views/services.py b/newton/newton/proxy/views/services.py index 6e166f95..b6e1be0c 100644 --- a/newton/newton/proxy/views/services.py +++ b/newton/newton/proxy/views/services.py @@ -15,51 +15,130 @@ 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 +import re +from rest_framework.permissions import BasePermission from rest_framework.response import Response +from rest_framework import status from rest_framework.views import APIView +from newton.proxy.views.proxy_utils import ProxyUtils from newton.pub.exceptions import VimDriverNewtonException -from newton.requests.views.util import VimDriverUtils from newton.pub.msapi import extsys -from newton.proxy.views.proxy_utils import ProxyUtils +from newton.requests.views.util import VimDriverUtils logger = logging.getLogger(__name__) DEBUG=True + +class HasValidToken(BasePermission): + + def has_permission(self, request, view): + token = request.META.get('HTTP_X_AUTH_TOKEN', None) + if token: + state, metadata = VimDriverUtils.get_token_cache(token) + if state: + return True + return False + + class Services(APIView): + permission_classes = (HasValidToken,) def __init__(self): self._logger = logger + def _get_token(self, request): + return request.META.get('HTTP_X_AUTH_TOKEN', None) + + def _get_resource_and_metadata(self, servicetype, metadata_catalog, requri): + real_prefix = None + proxy_prefix = None + suffix = None + if servicetype and 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) + + req_resource = '' + if requri and requri != '': + req_resource = "/" if re.match(r'//', requri) else '' + requri + return req_resource, metadata_catalog + + def _do_action(self, action, request, vim_id, servicetype, requri): + tmp_auth_token = self._get_token(request) + try: + vim = VimDriverUtils.get_vim_info(vim_id) + # fetch the auth_state out of cache + auth_state, metadata_catalog = VimDriverUtils.get_token_cache(tmp_auth_token) + req_resource, metadata_catalog = self._get_resource_and_metadata(servicetype, metadata_catalog, requri) + sess = VimDriverUtils.get_session(vim, tenantid=None, auth_state=auth_state) + + cloud_owner, regionid = extsys.decode_vim_id(vim_id) + interface = 'public' + service = { + 'service_type': servicetype, + 'interface': interface, + 'region_id': regionid + } + + self._logger.debug("service " + action + " request uri %s" % (req_resource)) + if(action == "get"): + resp = sess.get(req_resource, endpoint_filter=service) + elif(action == "post"): + resp = sess.post(req_resource, data=json.JSONEncoder().encode(request.data), endpoint_filter=service) + elif(action == "put"): + resp = sess.put(req_resource, data=json.JSONEncoder().encode(request.data), endpoint_filter=service) + elif(action == "patch"): + resp = sess.patch(req_resource, data=json.JSONEncoder().encode(request.data), endpoint_filter=service) + elif (action == "delete"): + resp = sess.delete(req_resource, endpoint_filter=service) + content = resp.json() if resp.content else None + self._logger.debug("service " + action + " response: %s, %s" % (resp.status_code, content)) + + if (action != "delete"): + content = ProxyUtils.update_prefix(metadata_catalog, content) + return Response(headers={'X-Subject-Token': tmp_auth_token}, data=content, status=resp.status_code) + return Response(headers={'X-Subject-Token': tmp_auth_token}, status=resp.status_code) + except VimDriverNewtonException as e: + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + self._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: + self._logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + def head(self, request, vimid="", servicetype="", requri=""): self._logger.debug("Services--head::META> %s" % request.META) self._logger.debug("Services--head::data> %s" % request.data) self._logger.debug("Services--head::vimid, servicetype, requri> %s,%s,%s" % (vimid, servicetype, requri)) + token = self._get_token(request) 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) - + auth_state, metadata_catalog = VimDriverUtils.get_token_cache(token) + sess = VimDriverUtils.get_session(vim, auth_state=auth_state) - 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 @@ -73,13 +152,10 @@ class Services(APIView): self._logger.debug("service head request uri %s" % (req_resource)) 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() if resp.content else None self._logger.debug("service head response: %s, %s" % (resp.status_code, content)) - return Response(headers={'X-Subject-Token': tmp_auth_token}, data=content, status=resp.status_code) - #return resp + return Response(headers={'X-Subject-Token': 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: @@ -95,377 +171,36 @@ class Services(APIView): self._logger.debug("Services--get::data> %s" % request.data) self._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: -# self._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} - - self._logger.debug("service get request uri %s" % (req_resource)) - - 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() if resp.content else None - self._logger.debug("service get response: %s, %s" % (resp.status_code, content)) - - content = ProxyUtils.update_prefix(metadata_catalog, 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: - self._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: - self._logger.error(traceback.format_exc()) - return Response(data={'error': str(e)}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return self._do_action("get", request, vimid, servicetype, requri) def post(self, request, vimid="", servicetype="", requri=""): self._logger.debug("Services--post::META> %s" % request.META) self._logger.debug("Services--post::data> %s" % request.data) self._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: -# self._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} - - self._logger.debug("service post request uri %s" % (req_resource)) - - 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() if resp.content else None - self._logger.debug("service post response: %s, %s" % (resp.status_code, content)) - - content = ProxyUtils.update_prefix(metadata_catalog, 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: - self._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: - self._logger.error(traceback.format_exc()) - return Response(data={'error': str(e)}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return self._do_action("post", request, vimid, servicetype, requri) def put(self, request, vimid="", servicetype="", requri=""): self._logger.debug("Services--put::META> %s" % request.META) self._logger.debug("Services--put::data> %s" % request.data) self._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: -# self._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} - - self._logger.debug("service put request uri %s" % (req_resource)) - - 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() if resp.content else None - self._logger.debug("service put response: %s, %s" % (resp.status_code, content)) - - content = ProxyUtils.update_prefix(metadata_catalog, 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: - self._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: - self._logger.error(traceback.format_exc()) - return Response(data={'error': str(e)}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR) - + return self._do_action("put", request, vimid, servicetype, requri) def patch(self, request, vimid="", servicetype="", requri=""): self._logger.debug("Services--patch::META> %s" % request.META) self._logger.debug("Services--patch::data> %s" % request.data) self._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: -# self._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} - - self._logger.debug("service patch request uri %s" % (req_resource)) - - 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() if resp.content else None - self._logger.debug("service patch response: %s, %s" % (resp.status_code, content)) - - content = ProxyUtils.update_prefix(metadata_catalog, 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: - self._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: - self._logger.error(traceback.format_exc()) - return Response(data={'error': str(e)}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return self._do_action("patch", request, vimid, servicetype, requri) def delete(self, request, vimid="", servicetype="", requri=""): self._logger.debug("Services--delete::META> %s" % request.META) self._logger.debug("Services--delete::data> %s" % request.data) self._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: -# self._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} - - self._logger.debug("service delete request uri %s" % (req_resource)) - - 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) - - return Response(headers={'X-Subject-Token': tmp_auth_token}, status=resp.status_code) - - except VimDriverNewtonException as e: - return Response(data={'error': e.content}, status=e.status_code) - except HttpError as e: - self._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: - self._logger.error(traceback.format_exc()) - return Response(data={'error': str(e)}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return self._do_action("delete", request, vimid, servicetype, requri) class GetTenants(Services): diff --git a/newton/newton/requests/tests/test_reqeust.py b/newton/newton/requests/tests/test_request.py index d094314d..d094314d 100644 --- a/newton/newton/requests/tests/test_reqeust.py +++ b/newton/newton/requests/tests/test_request.py diff --git a/newton/newton/requests/views/util.py b/newton/newton/requests/views/util.py index c5046387..f2c62896 100644 --- a/newton/newton/requests/views/util.py +++ b/newton/newton/requests/views/util.py @@ -124,15 +124,13 @@ class VimDriverUtils(object): # return auth.get_auth_ref(session) @staticmethod - def get_token_cache(vim, token): + def get_token_cache(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) + return cache.get(token), cache.get("meta_%s" % token) @staticmethod diff --git a/newton/newton/settings.py b/newton/newton/settings.py index b62a584f..d460758c 100644 --- a/newton/newton/settings.py +++ b/newton/newton/settings.py @@ -12,6 +12,13 @@ import os import sys +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': 'mydatabase', + } +} + # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -29,6 +29,8 @@ <modules> <module>newton</module> + <module>ocata</module> + <module>windriver</module> </modules> <build> diff --git a/windriver/.gitignore b/windriver/.gitignore new file mode 100644 index 00000000..e86d02b0 --- /dev/null +++ b/windriver/.gitignore @@ -0,0 +1,11 @@ +.project +.classpath +.settings/ +.checkstyle +target/ +logs/*.log +*.pyc +.tox +.coverage +htmlcov/ + diff --git a/windriver/README.md b/windriver/README.md new file mode 100644 index 00000000..3de1facc --- /dev/null +++ b/windriver/README.md @@ -0,0 +1,12 @@ +# 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. + +# Micro service of MultiCloud plugin for Wind River Titanium Cloud. diff --git a/windriver/assembly.xml b/windriver/assembly.xml new file mode 100644 index 00000000..ef3a749f --- /dev/null +++ b/windriver/assembly.xml @@ -0,0 +1,70 @@ +<!-- + 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. +--> +<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd"> + <id>windriver</id> + <formats> + <format>zip</format> + </formats> + <fileSets> + <fileSet> + <directory>titanium_cloud</directory> + <outputDirectory>/titanium_cloud</outputDirectory> + <includes> + <include>**/*.py</include> + <include>**/*.json</include> + <include>**/*.xml</include> + <include>**/*.wsdl</include> + <include>**/*.xsd</include> + <include>**/*.bpel</include> + </includes> + </fileSet> + <fileSet> + <directory>logs</directory> + <outputDirectory>/logs</outputDirectory> + <includes> + <include>*.txt</include> + </includes> + </fileSet> + <fileSet> + <directory>docker</directory> + <outputDirectory>/docker</outputDirectory> + <includes> + <include>*.sh</include> + <include>Dockerfile</include> + </includes> + </fileSet> + <fileSet> + <directory>.</directory> + <outputDirectory>/</outputDirectory> + <includes> + <include>*.py</include> + <include>*.txt</include> + <include>*.sh</include> + <include>*.ini</include> + <include>*.md</include> + </includes> + </fileSet> + </fileSets> + <dependencySets> + <dependencySet> + <unpack>true</unpack> + <outputDirectory>/lib</outputDirectory> + <includes> + <include>org.onap.multicloud.openstack:multicloud-openstack-newton</include> + </includes> + </dependencySet> + </dependencySets> + <baseDirectory>windriver</baseDirectory> +</assembly> diff --git a/windriver/docker/Dockerfile b/windriver/docker/Dockerfile new file mode 100644 index 00000000..ac2e62ef --- /dev/null +++ b/windriver/docker/Dockerfile @@ -0,0 +1,31 @@ +FROM python:2 + +ARG HTTP_PROXY=${HTTP_PROXY} +ARG HTTPS_PROXY=${HTTPS_PROXY} + +ENV http_proxy $HTTP_PROXY +ENV https_proxy $HTTPS_PROXY + +ENV MSB_ADDR "127.0.0.1" +ENV MSB_PORT "80" +ENV AAI_ADDR "aai.api.simpledemo.openecomp.org" +ENV AAI_PORT "8443" +ENV AAI_SCHEMA_VERSION "v11" +ENV AAI_USERNAME "AAI" +ENV AAI_PASSWORD "AAI" + +EXPOSE 9005 + +# COPY ./ /opt/windriver/ +RUN apt-get update && \ + apt-get install -y memcached && \ + apt-get install -y unzip && \ + cd /opt/ && \ + wget -O multicloud-openstack-windriver.zip "https://nexus.onap.org/service/local/artifact/maven/redirect?r=snapshots&g=org.onap.multicloud.openstack&a=multicloud-openstack-windriver&e=zip&v=LATEST" && \ + unzip -q -o -B multicloud-openstack-windriver.zip && \ + chmod +x /opt/windriver/*.sh && \ + rm -f multicloud-openstack-windriver.zip && \ + pip install -r /opt/windriver/requirements.txt + +WORKDIR /opt/windriver +CMD /bin/sh -c /opt/windriver/run.sh
\ No newline at end of file diff --git a/windriver/docker/build-image.sh b/windriver/docker/build-image.sh new file mode 100644 index 00000000..cc32212e --- /dev/null +++ b/windriver/docker/build-image.sh @@ -0,0 +1,32 @@ +#!/bin/bash +DIRNAME=`dirname $0` +DOCKER_BUILD_DIR=`cd $DIRNAME/; pwd` +echo "DOCKER_BUILD_DIR=${DOCKER_BUILD_DIR}" +cd ${DOCKER_BUILD_DIR} + +BUILD_ARGS="--no-cache" +ORG="onap" +VERSION="1.0.0-SNAPSHOT" +PROJECT="multicloud" +IMAGE="openstack-windriver" +DOCKER_REPOSITORY="nexus3.onap.org:10003" +IMAGE_NAME="${DOCKER_REPOSITORY}/${ORG}/${PROJECT}/${IMAGE}" + +if [ $HTTP_PROXY ]; then + BUILD_ARGS+=" --build-arg HTTP_PROXY=${HTTP_PROXY}" +fi +if [ $HTTPS_PROXY ]; then + BUILD_ARGS+=" --build-arg HTTPS_PROXY=${HTTPS_PROXY}" +fi + +function build_image { + docker build ${BUILD_ARGS} -t ${IMAGE_NAME}:${VERSION} -t ${IMAGE_NAME}:latest . +} + +function push_image { + docker push ${IMAGE_NAME}:${VERSION} + docker push ${IMAGE_NAME}:latest +} + +build_image +push_image
\ No newline at end of file diff --git a/windriver/initialize.sh b/windriver/initialize.sh new file mode 100644 index 00000000..5fed1714 --- /dev/null +++ b/windriver/initialize.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# 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. + +pip install -r requirements.txt diff --git a/windriver/logs/empty.txt b/windriver/logs/empty.txt new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/windriver/logs/empty.txt diff --git a/windriver/manage.py b/windriver/manage.py new file mode 100644 index 00000000..14931558 --- /dev/null +++ b/windriver/manage.py @@ -0,0 +1,19 @@ +# 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. + +import os +import sys + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "titanium_cloud.settings") + +if __name__ == "__main__": + from django.core.management import execute_from_command_line + execute_from_command_line(sys.argv) diff --git a/windriver/pom.xml b/windriver/pom.xml new file mode 100644 index 00000000..6e6c9d00 --- /dev/null +++ b/windriver/pom.xml @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <groupId>org.onap.oparent</groupId> + <artifactId>oparent</artifactId> + <version>1.0.0-SNAPSHOT</version> + <relativePath>../oparent</relativePath> + </parent> + <modelVersion>4.0.0</modelVersion> + <groupId>org.onap.multicloud.openstack</groupId> + <artifactId>multicloud-openstack-windriver</artifactId> + <version>1.0.0-SNAPSHOT</version> + <packaging>pom</packaging> + <name>multicloud/openstack/windriver</name> + <description>multicloud for openstack Wind River Titanium Cloud</description> + <dependencies> + <dependency> + <groupId>org.onap.multicloud.openstack</groupId> + <artifactId>multicloud-openstack-newton</artifactId> + <version>1.0.0-SNAPSHOT</version> + <type>zip</type> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <configuration> + <appendAssemblyId>false</appendAssemblyId> + <descriptors> + <descriptor>assembly.xml</descriptor> + </descriptors> + </configuration> + <executions> + <execution> + <id>make-assembly</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/windriver/requirements.txt b/windriver/requirements.txt new file mode 100644 index 00000000..841fe278 --- /dev/null +++ b/windriver/requirements.txt @@ -0,0 +1,17 @@ +# rest framework +Django==1.9.6 +djangorestframework==3.3.3 + +# for call rest api +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 +unittest_xml_reporting==1.12.0 diff --git a/windriver/run.sh b/windriver/run.sh new file mode 100644 index 00000000..031719b4 --- /dev/null +++ b/windriver/run.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# 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. + +#!/bin/bash + +sed -i "s/MSB_SERVICE_ADDR =.*/MSB_SERVICE_ADDR = \"${MSB_ADDR}\"/g" titanium_cloud/pub/config/config.py +sed -i "s/MSB_SERVICE_PORT =.*/MSB_SERVICE_PORT = \"${MSB_PORT}\"/g" titanium_cloud/pub/config/config.py +sed -i "s/AAI_ADDR =.*/AAI_ADDR = \"${AAI_ADDR}\"/g" titanium_cloud/pub/config/config.py +sed -i "s/AAI_PORT =.*/AAI_PORT = \"${AAI_PORT}\"/g" titanium_cloud/pub/config/config.py +sed -i "s/AAI_SCHEMA_VERSION =.*/AAI_SCHEMA_VERSION = \"${AAI_SCHEMA_VERSION}\"/g" titanium_cloud/pub/config/config.py +sed -i "s/AAI_USERNAME =.*/AAI_USERNAME = \"${AAI_USERNAME}\"/g" titanium_cloud/pub/config/config.py +sed -i "s/AAI_PASSWORD =.*/AAI_PASSWORD = \"${AAI_PASSWORD}\"/g" titanium_cloud/pub/config/config.py + +memcached -d -m 2048 -u root -c 1024 -p 11211 -P /tmp/memcached1.pid +export PYTHONPATH=lib/newton +nohup python manage.py runserver 0.0.0.0:9005 2>&1 & + +while [ ! -f logs/runtime_titanium_cloud.log ]; do + sleep 1 +done + +tail -F logs/runtime_titanium_cloud.log + diff --git a/windriver/stop.sh b/windriver/stop.sh new file mode 100644 index 00000000..10ce7464 --- /dev/null +++ b/windriver/stop.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# 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. + +#!/bin/bash + +ps auxww | grep 'manage.py runserver 0.0.0.0:9005' | awk '{print $2}' | xargs kill -9 +ps auxww | grep 'memcached -d -m 2048 -u root -c 1024 -p 11211 -P /tmp/memcached1.pid' | awk '{print $2}' | xargs kill -9 diff --git a/windriver/titanium_cloud/__init__.py b/windriver/titanium_cloud/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/windriver/titanium_cloud/__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/windriver/titanium_cloud/extensions/__init__.py b/windriver/titanium_cloud/extensions/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/windriver/titanium_cloud/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/windriver/titanium_cloud/extensions/urls.py b/windriver/titanium_cloud/extensions/urls.py new file mode 100644 index 00000000..b0ffec89 --- /dev/null +++ b/windriver/titanium_cloud/extensions/urls.py @@ -0,0 +1,29 @@ +# 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 titanium_cloud.extensions.views import extensions +from titanium_cloud.extensions.views import epacaps +from titanium_cloud.extensions.views import fcaps + + +urlpatterns = [ + url(r'^sions/?$', extensions.Extensions.as_view()), + url(r'^sions/epa-caps/?$', epacaps.EpaCaps.as_view()), + url(r'^sions/guest-monitor/(?P<vserverid>[0-9a-zA-Z_-]+)/?$', fcaps.GuestMonitor.as_view()), +] + +urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/windriver/titanium_cloud/extensions/views/__init__.py b/windriver/titanium_cloud/extensions/views/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/windriver/titanium_cloud/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/windriver/titanium_cloud/extensions/views/epacaps.py b/windriver/titanium_cloud/extensions/views/epacaps.py new file mode 100644 index 00000000..7f638fad --- /dev/null +++ b/windriver/titanium_cloud/extensions/views/epacaps.py @@ -0,0 +1,32 @@ +# 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 titanium_cloud.pub.config import config + + +from newton.extensions.views import epacaps as newton_epacaps + +logger = logging.getLogger(__name__) + +DEBUG=True + + +class EpaCaps(newton_epacaps.EpaCaps): + + def __init__(self): + self.proxy_prefix = config.MULTICLOUD_PREFIX + self._logger = logger diff --git a/windriver/titanium_cloud/extensions/views/extensions.py b/windriver/titanium_cloud/extensions/views/extensions.py new file mode 100644 index 00000000..2dd61fe9 --- /dev/null +++ b/windriver/titanium_cloud/extensions/views/extensions.py @@ -0,0 +1,73 @@ +# 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 + +from titanium_cloud.pub.config import config +from newton.extensions.views import extensions as newton_extensions + +logger = logging.getLogger(__name__) + +DEBUG=True + +class Extensions(newton_extensions.Extensions): + + def __init__(self): + self._logger = logger + self.proxy_prefix = config.MULTICLOUD_PREFIX + + + 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", + "url": self.proxy_prefix + "/%s/extensions/epa-caps" \ + % (vimid), + "spec": "" + }, + { + "alias": "guest-monitor", + "description": "Multiple network support", + "name": "EPACapsQuery", + "url": self.proxy_prefix +\ + "/%s/extensions/guest-monitor/{server_id}" \ + % (vimid), + "spec": "" + } + ] + + 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) + + 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/windriver/titanium_cloud/extensions/views/fcaps.py b/windriver/titanium_cloud/extensions/views/fcaps.py new file mode 100644 index 00000000..ad054e95 --- /dev/null +++ b/windriver/titanium_cloud/extensions/views/fcaps.py @@ -0,0 +1,320 @@ +# 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 threading + +from django.core.cache import cache + +from keystoneauth1.exceptions import HttpError +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from titanium_cloud.pub.config import config +from newton.pub.exceptions import VimDriverNewtonException +from newton.requests.views.util import VimDriverUtils +from newton.pub.msapi import extsys + + + +#from newton.extensions.views import fcaps as newton_fcaps + +logger = logging.getLogger(__name__) + +DEBUG=True + +#dict to store running worker threads +running_threads = {} +running_thread_lock = threading.Lock() + +class GuestMonitorWorker (threading.Thread): + service = {'service_type': 'platform', + 'interface': 'public'} + def __init__(self, vimid, tenantid=None): + threading.Thread.__init__(self) + self.vimid = vimid + self.tenantid = tenantid + self.eventid = '700.213' #Guest Heartbeat failed for instance + + def run(self): + logger.debug("start GuestMonitorWorker %s,%s" % (self.vimid, self.tenantid)) + + viminfo = VimDriverUtils.get_vim_info(self.vimid) + sess = VimDriverUtils.get_session(viminfo, tenantid=self.tenantid) + + thread_info = running_threads.get(self.vimid) + if not thread_info: + return + + while thread_info.get('state') == 'start': + #wait for jobs + vservers = thread_info.get('vservers') if thread_info else None + if not vservers: + continue + + # do jobs + for (vserverid, vserverinfo) in vservers.items(): + status_code, heartbeat_event = \ + self.monitor_heartbeat(self.vimid, self.tenantid, vserverid, viminfo, sess) + + if status_code == status.HTTP_403_FORBIDDEN: + #invalid tenant, so remove this job + + running_thread_lock.acquire() + thread_info['state'] = 'error' + running_thread_lock.release() + + return #exit this thread since error + + if heartbeat_event: + #report to VES + #tbd + pass + else: + continue + + running_thread_lock.acquire() + thread_info['state'] = 'stopped' + running_thread_lock.release() + + logger.debug("stop GuestMonitorWorker %s, %s, %s" % (self.vimid, self.tenantid, self.vserverid)) +# running_thread_lock.acquire() +# running_threads.pop(self.vimid) +# running_thread_lock.release() + + def monitor_heartbeat(self, vimid, tenantid, vserverid, viminfo, session): + logger.debug("GuestMonitorWorker--monitor_heartbeat::> %s" % (vserverid)) + try: + # prepare request resource to vim instance + req_resouce = "/v1/event_log?q.field=entity_instance_id&\ + q.field=event_log_id&\ + q.op=eq&q.op=eq&q.type=&q.type=&\ + q.value=tenant\%%s.instance\%%s&\ + q.value=%s" % (tenantid, vserverid, self.eventid) + + resp = session.get(req_resouce, endpoint_filter=self.service, + headers={"Content-Type": "application/json", + "Accept": "application/json"}) + + logger.debug("response status code of monitor_heartbeat %s" % resp.status_code) + + return resp.status_code, resp.json() if resp.content else None + + except HttpError as e: + logger.error("monitor_heartbeat, HttpError: status:%s, response:%s" % (e.http_status, e.response.json())) + return e.http_status, e.response.json() + except Exception as e: + logger.error(traceback.format_exc()) + logger.error("Failed to monitor_heartbeat:%s" % str(e)) + return e.http_status, e.response.json() + + +class GuestMonitor(APIView): + + def __init__(self): + self.proxy_prefix = config.MULTICLOUD_PREFIX + self._logger = logger + + def post(self, request, vimid="", vserverid=""): + '''Start guest monitoring on specified virtual server''' + self._logger.debug("GuestMonitor--post::data> %s" % request.data) + self._logger.debug("GuestMonitor--post::vimid > %s" % vimid) + + try: + # populate proxy identity url + cloud_owner, cloud_region_id = extsys.decode_vim_id(vimid) + + tenant_name = request.data.get('tenantName') + tenant_id = request.data.get('tenantID') + ves_url = request.data.get('vesurl') + + # prepare request resource to vim instance + # get token: + viminfo = VimDriverUtils.get_vim_info(vimid) + # the tenant should have the privilege to access the event-log API + # usually it is 'admin'. Otherwise the 403 will be returned. + sess = None + if tenant_id: + sess = VimDriverUtils.get_session(viminfo, tenantid=tenant_id) + else: + sess = VimDriverUtils.get_session(viminfo, tenantname=tenant_name) + + #now try to convert tenant_name to tenant_id + #tbd + + thread_info = running_threads[vimid] + + if thread_info and thread_info['state'] == 'error': + #the thread is in error state, so recreate with new tenant_id + running_thread_lock.acquire() + running_threads.pop(vimid) + running_thread_lock.release() + thread_info = None + + if not thread_info: + tmp_thread = GuestMonitorWorker(vimid, tenant_id) + if not tmp_thread: + raise VimDriverNewtonException(message="internal error", + content="Fail to spawn thread for Guest Monitoring", + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR) + thread_info = { + 'thread': tmp_thread, + 'tenantid':tenant_id, + 'vservers':{}, + 'state':'start' + } + + running_thread_lock.acquire() + running_threads[vimid] = thread_info + running_thread_lock.release() + tmp_thread.start() + else: + thread_info['state'] = 'start' + + + vservers = thread_info.get('vservers') + vservers[vserverid] = {'vesurl': ves_url} + + 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: + self._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: + self._logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + + def GET(self, request, vimid="", vserverid=""): + '''query guest monitoring on specified virtual server''' + self._logger.debug("GuestMonitor--get::data> %s" % request.data) + self._logger.debug("GuestMonitor--get::vimid > %s" % vimid) + + try: + # populate proxy identity url + cloud_owner, cloud_region_id = extsys.decode_vim_id(vimid) + + tenant_name = request.data.get('tenantName') + tenant_id = request.data.get('tenantID') + vserver_id = vserverid + + # prepare request resource to vim instance + # get token: + viminfo = VimDriverUtils.get_vim_info(vimid) + # the tenant should have the privilege to access the event-log API + # usually it is 'admin'. Otherwise the 403 will be returned. + sess = None + if tenant_id: + sess = VimDriverUtils.get_session(viminfo, tenantid=tenant_id) + else: + sess = VimDriverUtils.get_session(viminfo, tenantname=tenant_name) + + #now try to convert tenant_name to tenant_id, and vserver_name to vserver_id + #tbd + + thread_info = running_threads[vimid] + if not thread_info \ + or not thread_info.get('vservers') \ + or not thread_info.get('vservers').get(vserverid): + status_code = status.HTTP_204_NO_CONTENT + content = {'error': + 'Guest Monitor job is not created for this virtual server,\ + vim id: %s, vserver id: %s' + % (self.vimid, vserverid)} + pass + elif thread_info['state'] == 'error': + status_code = status.HTTP_500_INTERNAL_SERVER_ERROR + content = {'error': + 'Guest Monitor job for this virtual server \ + (vim id: %s, vserver id: %s) failed due to: %s' + % (self.vimid, vserverid, thread_info.get('message'))} + pass + else: + vserverinfo = thread_info.get('vservers').get(vserverid) + content = vserverinfo.get('message') + status_code = vserverinfo.get('status') or status.HTTP_200_OK + pass + + #return Response(status=status.HTTP_202_ACCEPTED) + return Response(status=status_code, data=content) + + except VimDriverNewtonException as e: + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + self._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: + self._logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + + + def DELETE(self, request, vimid="", vserverid=""): + '''Stop guest monitoring on specified virtual server''' + self._logger.debug("GuestMonitor--delete::data> %s" % request.data) + self._logger.debug("GuestMonitor--delete::vimid > %s" % vimid) + + try: + # populate proxy identity url + cloud_owner, cloud_region_id = extsys.decode_vim_id(vimid) + + tenant_name = request.data.get('tenantName') + tenant_id = request.data.get('tenantID') + + # prepare request resource to vim instance + # get token: + viminfo = VimDriverUtils.get_vim_info(vimid) + # the tenant should have the privilege to access the event-log API + # usually it is 'admin'. Otherwise the 403 will be returned. + sess = None + if tenant_id: + sess = VimDriverUtils.get_session(viminfo, tenantid=tenant_id) + else: + sess = VimDriverUtils.get_session(viminfo, tenantname=tenant_name) + + #now try to convert tenant_name to tenant_id, and vserver_name to vserver_id + #tbd + + thread_info = running_threads[vimid] + if not thread_info: + status_code = status.HTTP_204_NO_CONTENT + else: + vservers = thread_info.get('vservers') + if vservers.get(vserverid): + vservers.pop(vserverid) + + running_thread_lock.acquire() + if len(vservers.items()) == 0: + thread_info.stop() + running_threads.pop(vimid) + running_thread_lock.release() + status_code = status.HTTP_202_ACCEPTED + + return Response(status=status_code) + + except VimDriverNewtonException as e: + return Response(data={'error': e.content}, status=e.status_code) + except HttpError as e: + self._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: + self._logger.error(traceback.format_exc()) + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR)
\ No newline at end of file diff --git a/windriver/titanium_cloud/proxy/__init__.py b/windriver/titanium_cloud/proxy/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/windriver/titanium_cloud/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/windriver/titanium_cloud/proxy/urls.py b/windriver/titanium_cloud/proxy/urls.py new file mode 100644 index 00000000..f848e76c --- /dev/null +++ b/windriver/titanium_cloud/proxy/urls.py @@ -0,0 +1,32 @@ +# 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 titanium_cloud.proxy.views import identityV3 +from titanium_cloud.proxy.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/(?:v2.0/|)tenants/?$', + services.GetTenants.as_view()), + url(r'^(?P<servicetype>[0-9a-zA-Z_-]+)/(?P<requri>[0-9a-zA-Z./_-]*)$', + services.Services.as_view()), +] + +urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/windriver/titanium_cloud/proxy/views/__init__.py b/windriver/titanium_cloud/proxy/views/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/windriver/titanium_cloud/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/windriver/titanium_cloud/proxy/views/identityV3.py b/windriver/titanium_cloud/proxy/views/identityV3.py new file mode 100644 index 00000000..a6efa6ac --- /dev/null +++ b/windriver/titanium_cloud/proxy/views/identityV3.py @@ -0,0 +1,27 @@ +# 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 + +from titanium_cloud.pub.config import config +from newton.proxy.views import identityV3 as newton_identityV3 + +logger = logging.getLogger(__name__) + +DEBUG=True + +class Tokens(newton_identityV3.Tokens): + + def __init__(self): + self.proxy_prefix = config.MULTICLOUD_PREFIX + self._logger = logger diff --git a/windriver/titanium_cloud/proxy/views/services.py b/windriver/titanium_cloud/proxy/views/services.py new file mode 100644 index 00000000..9bd5cc4e --- /dev/null +++ b/windriver/titanium_cloud/proxy/views/services.py @@ -0,0 +1,45 @@ +# 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 + +from rest_framework import status + +from titanium_cloud.pub.config import config +from newton.proxy.views import services as newton_services + +logger = logging.getLogger(__name__) + +DEBUG=True + +class Services(newton_services.Services): + + def __init__(self): + self._logger = logger + + +class GetTenants(newton_services.GetTenants): + ''' + Backward compatible API for /v2.0/tenants + ''' + + def __init__(self): + self._logger = logger + + def get(self, request, vimid="", servicetype="identity", requri='v3/projects'): + self._logger.debug("GetTenants--get::META> %s" % request.META) + self._logger.debug("GetTenants--get::data> %s" % request.data) + self._logger.debug("GetTenants--get::vimid, servicetype, requri> %s,%s,%s" + % (vimid, servicetype, requri)) + + return super(GetTenants,self).get(request, vimid, servicetype, requri) diff --git a/windriver/titanium_cloud/pub/__init__.py b/windriver/titanium_cloud/pub/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/windriver/titanium_cloud/pub/__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/windriver/titanium_cloud/pub/config/__init__.py b/windriver/titanium_cloud/pub/config/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/windriver/titanium_cloud/pub/config/__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/windriver/titanium_cloud/pub/config/config.py b/windriver/titanium_cloud/pub/config/config.py new file mode 100644 index 00000000..bc7e8553 --- /dev/null +++ b/windriver/titanium_cloud/pub/config/config.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. + +import os + +# [MSB] +MSB_SERVICE_ADDR = '127.0.0.1' +MSB_SERVICE_PORT = '80' + +#[Multicloud] +MULTICLOUD_PREFIX = "http://%s:%s/api/multicloud-titanium_cloud/v0" %(MSB_SERVICE_ADDR, MSB_SERVICE_PORT) + +# [A&AI] +AAI_ADDR = "aai.api.simpledemo.openecomp.org" +AAI_PORT = "8443" +AAI_SERVICE_URL = 'https://%s:%s/aai' % (AAI_ADDR, AAI_PORT) +AAI_SCHEMA_VERSION = "v11" +AAI_USERNAME = 'AAI' +AAI_PASSWORD = 'AAI' + +AAI_BASE_URL = "%s/%s" % (AAI_SERVICE_URL, AAI_SCHEMA_VERSION) + +MULTICLOUD_APP_ID = 'MultiCloud-Titanium_Cloud' + +# [IMAGE LOCAL PATH] +ROOT_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) diff --git a/windriver/titanium_cloud/registration/__init__.py b/windriver/titanium_cloud/registration/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/windriver/titanium_cloud/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/windriver/titanium_cloud/registration/views/__init__.py b/windriver/titanium_cloud/registration/views/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/windriver/titanium_cloud/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/windriver/titanium_cloud/registration/views/registration.py b/windriver/titanium_cloud/registration/views/registration.py new file mode 100644 index 00000000..e58e170b --- /dev/null +++ b/windriver/titanium_cloud/registration/views/registration.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. +import logging + +from titanium_cloud.pub.config import config + +from newton.registration.views import registration as newton_registration + +logger = logging.getLogger(__name__) + +DEBUG=True + +class Registry(newton_registration.Registry): + + def __init__(self): + self.proxy_prefix = config.MULTICLOUD_PREFIX + self._logger = logger diff --git a/windriver/titanium_cloud/requests/__init__.py b/windriver/titanium_cloud/requests/__init__.py new file mode 100644 index 00000000..48b6e44b --- /dev/null +++ b/windriver/titanium_cloud/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/windriver/titanium_cloud/requests/urls.py b/windriver/titanium_cloud/requests/urls.py new file mode 100644 index 00000000..69f0e444 --- /dev/null +++ b/windriver/titanium_cloud/requests/urls.py @@ -0,0 +1,47 @@ +# 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 newton.requests.views import network +from newton.requests.views import subnet +from newton.requests.views import image +from newton.requests.views import volume +from newton.requests.views import server +from newton.requests.views import vport +from newton.requests.views import limits +from newton.requests.views import hosts +from newton.requests.views import flavor + +urlpatterns = [ + url(r'^networks(/(?P<networkid>[0-9a-zA-Z_-]+))?', + network.Networks.as_view()), + url(r'^subnets(/(?P<subnetid>[0-9a-zA-Z_-]+))?', + subnet.Subnets.as_view()), + url(r'^images(/(?P<imageid>[0-9a-zA-Z_-]+))?', + image.Images.as_view()), + url(r'^volumes(/(?P<volumeid>[0-9a-zA-Z_-]+))?', + volume.Volumes.as_view()), + url(r'^servers(/(?P<serverid>[0-9a-zA-Z_-]+))?', + server.Servers.as_view()), + url(r'^ports(/(?P<portid>[0-9a-zA-Z_-]+))?', + vport.Vports.as_view()), + url(r'^flavors(/(?P<flavorid>[0-9a-zA-Z_-]+))?', + flavor.Flavors.as_view()), + url(r'^limits$', limits.Limits.as_view()), + url(r'^hosts(/(?P<hostname>[0-9a-zA-Z_-]+))?', hosts.Hosts.as_view()), +] + +urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/windriver/titanium_cloud/samples/__init__.py b/windriver/titanium_cloud/samples/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/windriver/titanium_cloud/samples/__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/windriver/titanium_cloud/samples/tests.py b/windriver/titanium_cloud/samples/tests.py new file mode 100644 index 00000000..d419efa5 --- /dev/null +++ b/windriver/titanium_cloud/samples/tests.py @@ -0,0 +1,29 @@ +# 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. + +import unittest +import json +from django.test import Client +from rest_framework import status + + +class SampleViewTest(unittest.TestCase): + def setUp(self): + self.client = Client() + + def tearDown(self): + pass + + def test_sample(self): + response = self.client.get("/samples/") + self.assertEqual(status.HTTP_200_OK, response.status_code, response.content) + resp_data = response.json() + self.assertEqual({"status": "active"}, resp_data) diff --git a/windriver/titanium_cloud/samples/urls.py b/windriver/titanium_cloud/samples/urls.py new file mode 100644 index 00000000..ae67a819 --- /dev/null +++ b/windriver/titanium_cloud/samples/urls.py @@ -0,0 +1,16 @@ +# 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. + +from django.conf.urls import url +from titanium_cloud.samples import views + +urlpatterns = [ + url(r'^samples/?$', views.SampleList.as_view()), ] diff --git a/windriver/titanium_cloud/samples/views.py b/windriver/titanium_cloud/samples/views.py new file mode 100644 index 00000000..e51044ad --- /dev/null +++ b/windriver/titanium_cloud/samples/views.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. + +import logging + +from rest_framework.views import APIView +from rest_framework.response import Response + +logger = logging.getLogger(__name__) + + +class SampleList(APIView): + """ + List all samples. + """ + def get(self, request, format=None): + logger.debug("get") + return Response({"status": "active"}) diff --git a/windriver/titanium_cloud/settings.py b/windriver/titanium_cloud/settings.py new file mode 100644 index 00000000..393f994e --- /dev/null +++ b/windriver/titanium_cloud/settings.py @@ -0,0 +1,122 @@ +# 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. + +import os +import sys + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '3o-wney!99y)^h3v)0$j16l9=fdjxcb+a8g+q3tfbahcnu2b0o' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'rest_framework', +] + +MIDDLEWARE_CLASSES = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'titanium_cloud.urls' + +WSGI_APPLICATION = 'titanium_cloud.wsgi.application' + +REST_FRAMEWORK = { + 'DEFAULT_RENDERER_CLASSES': ( + 'rest_framework.renderers.JSONRenderer', + ), + + 'DEFAULT_PARSER_CLASSES': ( + 'rest_framework.parsers.JSONParser', + 'rest_framework.parsers.MultiPartParser', + # 'rest_framework.parsers.FormParser', + # 'rest_framework.parsers.FileUploadParser', + ) +} + +TIME_ZONE = 'UTC' + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.6/howto/static-files/ + +STATIC_URL = '/static/' + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': True, + 'formatters': { + 'standard': { + 'format': '%(asctime)s:[%(name)s]:[%(filename)s]-[%(lineno)d] [%(levelname)s]:%(message)s', + }, + }, + 'filters': { + }, + 'handlers': { + 'titanium_cloud_handler': { + 'level': 'DEBUG', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': os.path.join(BASE_DIR, 'logs/runtime_titanium_cloud.log'), + 'formatter': 'standard', + 'maxBytes': 1024 * 1024 * 50, + 'backupCount': 5, + }, + }, + + 'loggers': { + 'titanium_cloud': { + 'handlers': ['titanium_cloud_handler'], + 'level': 'DEBUG', + 'propagate': False + }, + } +} + +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'LOCATION': '127.0.0.1:11211', + } +} + +if 'test' in sys.argv: + from titanium_cloud.pub.config import config + + REST_FRAMEWORK = {} + import platform + + if platform.system() == 'Linux': + TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner' + TEST_OUTPUT_VERBOSE = True + TEST_OUTPUT_DESCRIPTIONS = True + TEST_OUTPUT_DIR = 'test-reports' diff --git a/windriver/titanium_cloud/swagger/__init__.py b/windriver/titanium_cloud/swagger/__init__.py new file mode 100644 index 00000000..802f3fba --- /dev/null +++ b/windriver/titanium_cloud/swagger/__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/windriver/titanium_cloud/swagger/tests.py b/windriver/titanium_cloud/swagger/tests.py new file mode 100644 index 00000000..ea9c9e50 --- /dev/null +++ b/windriver/titanium_cloud/swagger/tests.py @@ -0,0 +1,29 @@ +# 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. + +import unittest +import json +from django.test import Client +from rest_framework import status + + +class SampleViewTest(unittest.TestCase): + def setUp(self): + self.client = Client() + + def tearDown(self): + pass + + def test_sample(self): + response = self.client.get("/api/multicloud-titanium_cloud/v0/swagger.json") + self.assertEqual(status.HTTP_200_OK, response.status_code, response.content) +# resp_data = response.json() +# self.assertEqual({"status": "active"}, resp_data) diff --git a/windriver/titanium_cloud/swagger/urls.py b/windriver/titanium_cloud/swagger/urls.py new file mode 100644 index 00000000..21ef4bde --- /dev/null +++ b/windriver/titanium_cloud/swagger/urls.py @@ -0,0 +1,21 @@ +# 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. + +from django.conf.urls import patterns, url +from rest_framework.urlpatterns import format_suffix_patterns + +from titanium_cloud.swagger.views import SwaggerJsonView + +urlpatterns = [ + url(r'^api/multicloud-titanium_cloud/v0/swagger.json$', SwaggerJsonView.as_view()), +] + +urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/windriver/titanium_cloud/swagger/views.py b/windriver/titanium_cloud/swagger/views.py new file mode 100644 index 00000000..01f9374e --- /dev/null +++ b/windriver/titanium_cloud/swagger/views.py @@ -0,0 +1,45 @@ +# 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. + +import json +import logging +import os +import traceback + +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.swagger import views as newton_json_view + +logger = logging.getLogger(__name__) + + +class SwaggerJsonView(newton_json_view.SwaggerJsonView): + + def get(self, request): + ''' + reuse newton code and update the basePath + :param request: + :return: + ''' + + resp = super(SwaggerJsonView,self).get(request) + json_data = resp.data if resp else None + if json_data: + json_data["basePath"] = "/api/multicloud-titanium_cloud/v0/" + json_data["info"]["title"] = "Service NBI of MultiCloud plugin for OpenStack Newton" + return Response(data=json_data, status=200) + else: + return Response(data={'error':'internal error'}, status=500) + + diff --git a/windriver/titanium_cloud/urls.py b/windriver/titanium_cloud/urls.py new file mode 100644 index 00000000..664d8b3e --- /dev/null +++ b/windriver/titanium_cloud/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. + +from django.conf.urls import include, url + +from titanium_cloud.registration.views import registration +from newton.requests.views import tenants + +urlpatterns = [ + url(r'^', include('titanium_cloud.swagger.urls')), + url(r'^', include('titanium_cloud.samples.urls')), + url(r'^api/multicloud-titanium_cloud/v0/(?P<vimid>[0-9a-zA-Z_-]+)/registry/?$', + registration.Registry.as_view()), + url(r'^api/multicloud-titanium_cloud/v0/(?P<vimid>[0-9a-zA-Z_-]+)/?$', + registration.Registry.as_view()), + url(r'^api/multicloud-titanium_cloud/v0/(?P<vimid>[0-9a-zA-Z_-]+)/exten', + include('titanium_cloud.extensions.urls')), + url(r'^api/multicloud-titanium_cloud/v0/(?P<vimid>[0-9a-zA-Z_-]+)/', + include('titanium_cloud.proxy.urls')), + url(r'^api/multicloud-titanium_cloud/v0/(?P<vimid>[0-9a-zA-Z_-]+)/tenants/?$', + tenants.Tenants.as_view()), + url(r'^api/multicloud-titanium_cloud/v0/(?P<vimid>[0-9a-zA-Z_-]+)/' + '(?P<tenantid>[0-9a-zA-Z_-]{8,})/', include('titanium_cloud.requests.urls')), +] + + diff --git a/windriver/titanium_cloud/wsgi.py b/windriver/titanium_cloud/wsgi.py new file mode 100644 index 00000000..2962328d --- /dev/null +++ b/windriver/titanium_cloud/wsgi.py @@ -0,0 +1,19 @@ +# 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. + + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "titanium_cloud.settings") + +application = get_wsgi_application() diff --git a/windriver/tox.ini b/windriver/tox.ini new file mode 100644 index 00000000..764cf01c --- /dev/null +++ b/windriver/tox.ini @@ -0,0 +1,14 @@ +[tox] +envlist = py27,py35 +skipsdist = true + +[tox:jenkins] +downloadcache = ~/cache/pip + +[testenv] +setenv = + PYTHONPATH = {toxinidir}/../newton +deps = -r{toxinidir}/requirements.txt +commands = coverage run --branch manage.py test titanium_cloud + coverage html --omit=".tox*,*test*,*__init__.py" -d htmlcov + |