diff options
author | Ethan Lynn <ethanlynnl@vmware.com> | 2017-08-30 16:41:41 +0800 |
---|---|---|
committer | Ethan Lynn <ethanlynnl@vmware.com> | 2017-09-07 16:38:34 +0800 |
commit | 230456db33e911a4f0e1b3e5810198c5cc980948 (patch) | |
tree | a38bc82e19d821318aca7fff91a00b9ad6bb5252 | |
parent | 1df8516760032319e1630da60aa955ee5b977ab1 (diff) |
Add AAI adapter to vio plugin
Change-Id: I70f1cf0a297883b844689a4ddbc1bfde1f0ed2bc
issue-id: MULTICLOUD-81
Signed-off-by: Ethan Lynn <ethanlynnl@vmware.com>
-rw-r--r-- | vio/vio/pub/config/config.py | 20 | ||||
-rw-r--r-- | vio/vio/pub/msapi/extsys.py | 37 | ||||
-rw-r--r-- | vio/vio/pub/utils/restcall.py | 244 | ||||
-rw-r--r-- | vio/vio/pub/vim/drivers/vimsdk/compute.py | 4 | ||||
-rw-r--r-- | vio/vio/pub/vim/vimapi/nova/OperateHypervisor.py | 8 | ||||
-rw-r--r-- | vio/vio/swagger/urls.py | 10 | ||||
-rw-r--r-- | vio/vio/swagger/views/registry/__init__.py | 0 | ||||
-rw-r--r-- | vio/vio/swagger/views/registry/views.py | 179 |
8 files changed, 458 insertions, 44 deletions
diff --git a/vio/vio/pub/config/config.py b/vio/vio/pub/config/config.py index a31d63c..1231dd0 100644 --- a/vio/vio/pub/config/config.py +++ b/vio/vio/pub/config/config.py @@ -20,20 +20,28 @@ MSB_SERVICE_PORT = '10080' ROOT_PATH = os.path.dirname(os.path.dirname( os.path.dirname(os.path.abspath(__file__)))) +# [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' + # [REDIS] REDIS_HOST = '127.0.0.1' REDIS_PORT = '6379' REDIS_PASSWD = '' # [mysql] -DB_IP = '127.0.0.1' -DB_PORT = 3306 -DB_NAME = 'multivimvio' -DB_USER = 'root' -DB_PASSWD = 'password' +# DB_IP = '127.0.0.1' +# DB_PORT = 3306 +# DB_NAME = 'multivimvio' +# DB_USER = 'root' +# DB_PASSWD = 'password' # [register] -REG_TO_MSB_WHEN_START = True +REG_TO_MSB_WHEN_START = False REG_TO_MSB_REG_URL = "/api/microservices/v1/services" REG_TO_MSB_REG_PARAM = { "serviceName": "multicloud-vio", diff --git a/vio/vio/pub/msapi/extsys.py b/vio/vio/pub/msapi/extsys.py index 19ea80d..5584a3f 100644 --- a/vio/vio/pub/msapi/extsys.py +++ b/vio/vio/pub/msapi/extsys.py @@ -10,28 +10,33 @@ # 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 -from rest_framework import status -from vio.pub.exceptions import VimDriverVioException -from vio.pub.utils.restcall import req_by_msb +from vio.pub.utils.restcall import AAIClient logger = logging.getLogger(__name__) -def get_vims(): - ret = req_by_msb("/openoapi/extsys/v1/vims", "GET") - if ret[0] != 0: - logger.error("Status code is %s, detail is %s.", ret[2], ret[1]) - raise VimDriverVioException("Failed to query VIMs from extsys.") - return json.JSONDecoder().decode(ret[1]) +def split_vim_to_owner_region(vim_id): + split_vim = vim_id.split('_') + cloud_owner = split_vim[0] + cloud_region = "".join(split_vim[1:]) + return cloud_owner, cloud_region def get_vim_by_id(vim_id): - ret = req_by_msb("/openoapi/extsys/v1/vims/%s" % vim_id, "GET") - if ret[0] != 0: - logger.error("Status code is %s, detail is %s.", ret[2], ret[1]) - raise VimDriverVioException("Failed to query VIM with \ - id (%s) from extsys." % vim_id, status.HTTP_404_NOT_FOUND) - return json.JSONDecoder().decode(ret[1]) + cloud_owner, cloud_region = split_vim_to_owner_region(vim_id) + client = AAIClient(cloud_owner, cloud_region) + ret = client.get_vim(get_all=True) + ret['type'] = ret['cloud-type'] + ret['version'] = ret['cloud-region-version'] + ret['vimId'] = vim_id + ret['userName'] = ret['esr-system-info-list'][ + 'esr-system-info'][0]['username'] + ret['password'] = ret['esr-system-info-list'][ + 'esr-system-info'][0]['password'] + ret['tenant'] = ret['esr-system-info-list'][ + 'esr-system-info'][0]['default-tenant'] + ret['url'] = ret['esr-system-info-list'][ + 'esr-system-info'][0]['service-url'] + return ret diff --git a/vio/vio/pub/utils/restcall.py b/vio/vio/pub/utils/restcall.py index 590e52f..99ed9d9 100644 --- a/vio/vio/pub/utils/restcall.py +++ b/vio/vio/pub/utils/restcall.py @@ -16,9 +16,16 @@ import logging import urllib2 import uuid import httplib2 +import json +from vio.pub.config.config import AAI_SCHEMA_VERSION +from vio.pub.config.config import AAI_SERVICE_URL +from vio.pub.config.config import AAI_USERNAME +from vio.pub.config.config import AAI_PASSWORD from vio.pub.config.config import MSB_SERVICE_IP, MSB_SERVICE_PORT +from vio.pub.exceptions import VimDriverVioException + rest_no_auth, rest_oneway_auth, rest_bothway_auth = 0, 1, 2 HTTP_200_OK, HTTP_201_CREATED = '200', '201' HTTP_204_NO_CONTENT, HTTP_202_ACCEPTED = '204', '202' @@ -30,16 +37,22 @@ HTTP_401_UNAUTHORIZED, HTTP_400_BADREQUEST = '401', '400' logger = logging.getLogger(__name__) -def call_req(base_url, user, passwd, auth_type, resource, method, content=''): +def call_req(base_url, user, passwd, auth_type, resource, method, content='', + headers=None): callid = str(uuid.uuid1()) - logger.debug("[%s]call_req('%s','%s','%s',%s,'%s','%s','%s')" % ( - callid, base_url, user, passwd, auth_type, resource, method, content)) +# logger.debug("[%s]call_req('%s','%s','%s',%s,'%s','%s','%s')" % ( +# callid, base_url, user, passwd, auth_type, resource, method, content)) ret = None resp_status = '' + resp = "" + full_url = "" + try: full_url = combine_url(base_url, resource) - headers = {'content-type': 'application/json', - 'accept': 'application/json'} + if headers is None: + headers = {} + headers['content-type'] = 'application/json' + if user: headers['Authorization'] = 'Basic ' + \ ('%s:%s' % (user, passwd)).encode("base64") @@ -51,41 +64,40 @@ def call_req(base_url, user, passwd, auth_type, resource, method, content=''): auth_type == rest_no_auth)) http.follow_all_redirects = True try: + logger.debug("request=%s" % full_url) resp, resp_content = http.request( - full_url, method=method.upper(), - body=content, headers=headers) - resp_status, resp_body = resp['status'], resp_content.decode( - 'UTF-8') - logger.debug("[%s][%d]status=%s,resp_body=%s)" % - (callid, retry_times, resp_status, resp_body)) + full_url, method=method.upper(), body=content, + headers=headers) + resp_status = resp['status'] + resp_body = resp_content.decode('UTF-8') + if resp_status in status_ok_list: - ret = [0, resp_body, resp_status] + ret = [0, resp_body, resp_status, resp] else: - ret = [1, resp_body, resp_status] + ret = [1, resp_body, resp_status, resp] break except Exception as ex: if 'httplib.ResponseNotReady' in str(sys.exc_info()): - logger.debug("retry_times=%d", retry_times) logger.error(traceback.format_exc()) - ret = [1, "Unable to connect to %s" % - full_url, resp_status] + ret = [1, "Unable to connect to %s" % full_url, + resp_status, resp] continue raise ex except urllib2.URLError as err: - ret = [2, str(err), resp_status] + ret = [2, str(err), resp_status, resp] except Exception as ex: logger.error(traceback.format_exc()) logger.error("[%s]ret=%s" % (callid, str(sys.exc_info()))) res_info = str(sys.exc_info()) if 'httplib.ResponseNotReady' in res_info: - res_info = "The URL[%s] request failed \ - or is not responding." % full_url - ret = [3, res_info, resp_status] + res_info = ("The URL[%s] request failed or is not responding." % + full_url) + ret = [3, res_info, resp_status, resp] except: logger.error(traceback.format_exc()) - ret = [4, str(sys.exc_info()), resp_status] + ret = [4, str(sys.exc_info()), resp_status, resp] - logger.debug("[%s]ret=%s" % (callid, str(ret))) +# logger.debug("[%s]ret=%s" % (callid, str(ret))) return ret @@ -105,3 +117,191 @@ def combine_url(base_url, resource): else: full_url = base_url + '/' + resource return full_url + + +def get_res_from_aai(resource, content=''): + headers = { + 'X-FromAppId': 'MultiCloud', + 'X-TransactionId': '9001', + 'content-type': 'application/json', + 'accept': 'application/json' + } + base_url = "%s/%s" % (AAI_SERVICE_URL, AAI_SCHEMA_VERSION) + return call_req(base_url, AAI_USERNAME, AAI_PASSWORD, rest_no_auth, + resource, "GET", content, headers) + + +class AAIClient(object): + def __init__(self, cloud_owner, cloud_region): + self.base_url = "%s/%s" % (AAI_SERVICE_URL, AAI_SCHEMA_VERSION) + self.username = AAI_USERNAME + self.password = AAI_PASSWORD + self.default_headers = { + 'X-FromAppId': 'multicloud-openstack-vmware', + 'X-TransactionId': '9004', + 'content-type': 'application/json', + 'accept': 'application/json' + } + self.cloud_owner = cloud_owner + self.cloud_region = cloud_region + + def get_vim(self, get_all=False): + resource = ("/cloud-infrastructure/cloud-regions/cloud-region" + "/%s/%s" % (self.cloud_owner, self.cloud_region)) + if get_all: + resource = "%s?depth=all" % resource + resp = call_req(self.base_url, self.username, self.password, + rest_no_auth, resource, "GET", + headers=self.default_headers) + if resp[0] != 0: + raise VimDriverVioException( + status_code=404, + content="Failed to query VIM with id (%s_%s) from extsys." % ( + self.cloud_owner, self.cloud_region)) + return json.loads(resp[1]) + + def delete_vim(self): + resource = ("/cloud-infrastructure/cloud-regions/cloud-region" + "/%s/%s" % (self.cloud_owner, self.cloud_region)) + resp = call_req(self.base_url, self.username, self.password, + rest_no_auth, resource, "DELETE", + headers=self.default_headers) + if resp[0] != 0: + raise VimDriverVioException( + status_code=400, + content="Failed to delete cloud %s_%s: %s." % ( + self.cloud_owner, self.cloud_region, resp[1])) + return json.loads(resp[1]) + + def update_vim(self, content): + # update identity url + self.update_identity_url() + # update tenants + self.add_tenants(content) + # update flavors + self.add_images(content) + # update images + self.add_flavors(content) + # update pservers + self.add_pservers(content) + + def update_identity_url(self): + vim = self.get_vim() + vim['identity-url'] = ("http://%s/api/multicloud/v0/%s_%s/identity/" + "v3" % (MSB_SERVICE_IP, self.cloud_owner, + self.cloud_region)) + resource = ("/cloud-infrastructure/cloud-regions/cloud-region" + "/%s/%s" % (self.cloud_owner, self.cloud_region)) + call_req(self.base_url, self.username, self.password, + rest_no_auth, resource, "PUT", + content=json.dumps(vim), + headers=self.default_headers) + + def add_tenants(self, content): + for tenant in content['tenants']: + resource = ("/cloud-infrastructure/cloud-regions/cloud-region/" + "%s/%s/tenants/tenant/%s" % ( + self.cloud_owner, self.cloud_region, tenant['id'])) + body = {'tenant-name': tenant['name']} + call_req(self.base_url, self.username, self.password, + rest_no_auth, resource, "PUT", + content=json.dumps(body), + headers=self.default_headers) + + def add_flavors(self, content): + for flavor in content['flavors']: + resource = ("/cloud-infrastructure/cloud-regions/cloud-region/" + "%s/%s/flavors/flavor/%s" % ( + self.cloud_owner, self.cloud_region, flavor['id'])) + body = { + 'flavor-name': flavor['name'], + 'flavor-vcpus': flavor['vcpus'], + 'flavor-ram': flavor['ram'], + 'flavor-disk': flavor['disk'], + 'flavor-ephemeral': flavor['ephemeral'], + 'flavor-swap': flavor['swap'], + 'flavor-is-public': flavor['is_public'], + 'flavor-selflink': flavor['links'][0]['href'], + 'flavor-disabled': flavor['is_disabled'] + } + call_req(self.base_url, self.username, self.password, + rest_no_auth, resource, "PUT", + content=json.dumps(body), + headers=self.default_headers) + + def add_images(self, content): + for image in content['images']: + resource = ("/cloud-infrastructure/cloud-regions/cloud-region/" + "%s/%s/images/image/%s" % ( + self.cloud_owner, self.cloud_region, image['id'])) + body = { + 'image-name': image['name'], + # 'image-architecture': image[''], + 'image-os-distro': image['name'].split("-")[0], + 'image-os-version': image['name'].split("-")[1], + # 'application': image[''], + # 'application-vendor': image[''], + # 'application-version': image[''], + # TODO replace this with image proxy endpoint + 'image-selflink': "", + } + call_req(self.base_url, self.username, self.password, + rest_no_auth, resource, "PUT", + content=json.dumps(body), + headers=self.default_headers) + + def add_pservers(self, content): + for hypervisor in content['hypervisors']: + resource = ("/cloud-infrastructure/pservers/pserver/%s" % ( + hypervisor['name'])) + body = { + # 'ptnii-equip-name' + 'number-of-cpus': hypervisor['vcpus'], + 'disk-in-gigabytes': hypervisor['local_disk_size'], + 'ram-in-megabytes': hypervisor['memory_size'], + # 'equip-type' + # 'equip-vendor' + # 'equip-model' + # 'fqdn' + # 'pserver-selflink' + 'ipv4-oam-address': hypervisor['host_ip'], + # 'serial-number' + # 'ipaddress-v4-loopback-0' + # 'ipaddress-v6-loopback-0' + # 'ipaddress-v4-aim' + # 'ipaddress-v6-aim' + # 'ipaddress-v6-oam' + # 'inv-status' + 'pserver-id': hypervisor['id'], + # 'internet-topology' + } + call_req(self.base_url, self.username, self.password, + rest_no_auth, resource, "PUT", + content=json.dumps(body), + headers=self.default_headers) + # update relationship + resource = ("/cloud-infrastructure/pservers/pserver/%s/" + "relationship-list/relationship" % + hypervisor['name']) + related_link = ("%s/cloud-infrastructure/cloud-regions/" + "cloud-region/%s/%s" % ( + self.base_url, self.cloud_owner, + self.cloud_region)) + body = { + 'related-to': 'cloud-region', + 'related-link': related_link, + 'relationship-data': [ + { + 'relationship-key': 'cloud-region.cloud-owner', + 'relationship-value': self.cloud_owner + }, + { + 'relationship-key': 'cloud-region.cloud-region-id', + 'relationship-value': self.cloud_region + } + ] + } + call_req(self.base_url, self.username, self.password, + rest_no_auth, resource, "PUT", + content=json.dumps(body), + headers=self.default_headers) diff --git a/vio/vio/pub/vim/drivers/vimsdk/compute.py b/vio/vio/pub/vim/drivers/vimsdk/compute.py index 1ed4b25..23a3637 100644 --- a/vio/vio/pub/vim/drivers/vimsdk/compute.py +++ b/vio/vio/pub/vim/drivers/vimsdk/compute.py @@ -125,3 +125,7 @@ class ComputeClient(base.DriverBase): @sdk.translate_exception def get_hypervisor(self, hypervisor, **query): return self.conn.compute.get_hypervisor(hypervisor=hypervisor, **query) + + @sdk.translate_exception + def list_hypervisors(self, **query): + return self.conn.compute.hypervisors(**query) diff --git a/vio/vio/pub/vim/vimapi/nova/OperateHypervisor.py b/vio/vio/pub/vim/vimapi/nova/OperateHypervisor.py index d48b421..96e77bc 100644 --- a/vio/vio/pub/vim/vimapi/nova/OperateHypervisor.py +++ b/vio/vio/pub/vim/vimapi/nova/OperateHypervisor.py @@ -29,3 +29,11 @@ class OperateHypervisor(OperateNova): **kwargs) except exceptions.ResourceNotFound: return None + + def list_hypervisors(self, data, **query): + try: + return self.request('list_hypervisors', data, + project_id=data['project_id'], + **query) + except exceptions.ResourceNotFound: + return None diff --git a/vio/vio/swagger/urls.py b/vio/vio/swagger/urls.py index 920edb7..3448424 100644 --- a/vio/vio/swagger/urls.py +++ b/vio/vio/swagger/urls.py @@ -38,6 +38,10 @@ from vio.swagger.views.proxyplugin.neutron.views import NetWorkServer from vio.swagger.views.proxyplugin.volumn.views import VolumeServer from vio.swagger.views.proxyplugin.heat.views import HeatServer +# Registry +from vio.swagger.views.registry.views import Registry + + urlpatterns = [ url(r'^api/multicloud-vio/v0/swagger.json$', SwaggerJsonView.as_view()), url(r'^api/multicloud-vio/v0/(?P<vimid>[0-9a-zA-Z_-]+)/' @@ -96,6 +100,12 @@ urlpatterns = [ r'(?P<portid>[0-9a-zA-Z\-\_]+)$', DeletePortView.as_view()), + # Registry + url(r'^api/multicloud-vio/v0/(?P<vimid>[0-9a-z-A-Z\-\_]+)/registry$', + Registry.as_view()), + # url(r'^api/multicloud-vio/v0/(?P<vimid>[0-9a-z-A-Z\-\_]+)$', + # Registry.as_view()), + # proxy url(r'^api/multicloud-vio/v0/(?P<vimid>[0-9a-z-A-Z\-\_]+)/identity/v3', TokenView.as_view()), diff --git a/vio/vio/swagger/views/registry/__init__.py b/vio/vio/swagger/views/registry/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/vio/vio/swagger/views/registry/__init__.py diff --git a/vio/vio/swagger/views/registry/views.py b/vio/vio/swagger/views/registry/views.py new file mode 100644 index 0000000..cde3b5d --- /dev/null +++ b/vio/vio/swagger/views/registry/views.py @@ -0,0 +1,179 @@ +# Copyright (c) 2017 VMware, 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 import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from vio.pub.exceptions import VimDriverVioException +from vio.pub.msapi import extsys +from vio.pub.utils.restcall import AAIClient +from vio.pub.vim.vimapi.keystone import OperateTenant +from vio.pub.vim.vimapi.glance import OperateImage +from vio.pub.vim.vimapi.nova import OperateFlavors +from vio.pub.vim.vimapi.nova import OperateHypervisor + + +logger = logging.getLogger(__name__) + + +class Registry(APIView): + def _get_tenants(self, auth_info): + tenant_instance = OperateTenant.OperateTenant() + try: + projects = tenant_instance.get_projects(auth_info) + except Exception as e: + if hasattr(e, "http_status"): + return Response(data={'error': str(e)}, status=e.http_status) + else: + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + rsp = {"tenants": []} + for project in projects: + rsp['tenants'].append(project.to_dict()) + return rsp + + def _get_images(self, auth_info): + image_instance = OperateImage.OperateImage(auth_info) + try: + images = image_instance.get_vim_images() + except Exception as e: + if hasattr(e, "http_status"): + return Response(data={'error': str(e)}, status=e.http_status) + else: + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + rsp = {"images": []} + for image in images: + rsp['images'].append(image.to_dict()) + return rsp + + def _get_flavors(self, auth_info): + flavors_op = OperateFlavors.OperateFlavors() + try: + flavors = flavors_op.list_flavors( + auth_info, auth_info['tenant']) + except Exception as e: + if hasattr(e, "http_status"): + return Response(data={'error': str(e)}, status=e.http_status) + else: + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + rsp = {"flavors": []} + for flavor in flavors: + rsp['flavors'].append(flavor[0].to_dict()) + return rsp + + def _get_hypervisors(self, auth_info): + hypervisor_op = OperateHypervisor.OperateHypervisor() + try: + hypervisors = hypervisor_op.list_hypervisors(auth_info) + except Exception as e: + if hasattr(e, "http_status"): + return Response(data={'error': str(e)}, status=e.http_status) + else: + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + rsp = {"hypervisors": []} + for hypervisor in hypervisors: + rsp['hypervisors'].append(hypervisor.to_dict()) + return rsp + + def _find_tenant_id(self, name, tenants): + for tenant in tenants['tenants']: + if tenant['name'] == name: + return tenant['id'] + + def post(self, request, vimid): + try: + vim_info = extsys.get_vim_by_id(vimid) + except VimDriverVioException as e: + return Response(data={'error': str(e)}, status=e.status_code) + data = {} + data['vimId'] = vim_info['vimId'] + data['username'] = vim_info['userName'] + data['userName'] = vim_info['userName'] + data['password'] = vim_info['password'] + data['url'] = vim_info['url'] + data['project_name'] = vim_info['tenant'] + + rsp = {} + # get tenants + try: + print('Updating tenants') + tenants = self._get_tenants(data) + rsp.update(tenants) + data['tenant'] = self._find_tenant_id( + data['project_name'], tenants) + data['project_id'] = data['tenant'] + # set default tenant + # get images + print('Updating images') + images = self._get_images(data) + rsp.update(images) + # get flavors + print('Updating flavors') + flavors = self._get_flavors(data) + rsp.update(flavors) + # get hypervisors + print('Updating hypervisors') + hypervisors = self._get_hypervisors(data) + rsp.update(hypervisors) + # update A&AI + print('Put data into A&AI') + cloud_owner, cloud_region = extsys.split_vim_to_owner_region( + vimid) + aai_adapter = AAIClient(cloud_owner, cloud_region) + aai_adapter.update_vim(rsp) + except Exception as e: + return Response(data=e.message, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return Response(data="", status=status.HTTP_200_OK) + + def delete(self, request, vimid): + try: + vim_info = extsys.get_vim_by_id(vimid) + except VimDriverVioException as e: + return Response(data={'error': str(e)}, status=e.status_code) + + data = {} + data['vimId'] = vim_info['vimId'] + data['username'] = vim_info['userName'] + data['password'] = vim_info['password'] + data['url'] = vim_info['url'] + data['project_name'] = vim_info['tenant'] + + query = dict(request.query_params) + tenant_instance = OperateTenant.OperateTenant() + try: + projects = tenant_instance.get_projects(data, **query) + except Exception as e: + if hasattr(e, "http_status"): + return Response(data={'error': str(e)}, status=e.http_status) + else: + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + rsp = {} + rsp['vimId'] = vim_info['vimId'] + rsp['vimName'] = vim_info['name'] + rsp['tenants'] = [] + + for project in projects: + tenant = {} + tenant['id'] = project.id + tenant['name'] = project.name + rsp['tenants'].append(tenant) + return Response(data=rsp, status=status.HTTP_200_OK) |