diff options
Diffstat (limited to 'vio')
-rw-r--r-- | vio/images/empty.txt | 0 | ||||
-rw-r--r-- | vio/vio/api_v2/api_definition/__init__.py | 0 | ||||
-rw-r--r-- | vio/vio/api_v2/api_definition/hosts.yaml | 72 | ||||
-rw-r--r-- | vio/vio/api_v2/api_definition/networks.yaml | 91 | ||||
-rw-r--r-- | vio/vio/api_v2/api_definition/utils.py | 31 | ||||
-rw-r--r-- | vio/vio/api_v2/api_router/controller_builder.py | 218 | ||||
-rw-r--r-- | vio/vio/api_v2/api_router/swagger_json.py | 23 | ||||
-rw-r--r-- | vio/vio/api_v2/api_router/v0_controller.py | 35 | ||||
-rw-r--r-- | vio/vio/pub/vim/drivers/vimsdk/image_v2.py | 12 | ||||
-rw-r--r-- | vio/vio/pub/vim/vimapi/glance/OperateImage.py | 13 | ||||
-rw-r--r-- | vio/vio/swagger/urls.py | 9 | ||||
-rw-r--r-- | vio/vio/swagger/views/image/views.py | 103 |
12 files changed, 602 insertions, 5 deletions
diff --git a/vio/images/empty.txt b/vio/images/empty.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/vio/images/empty.txt diff --git a/vio/vio/api_v2/api_definition/__init__.py b/vio/vio/api_v2/api_definition/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/vio/vio/api_v2/api_definition/__init__.py diff --git a/vio/vio/api_v2/api_definition/hosts.yaml b/vio/vio/api_v2/api_definition/hosts.yaml new file mode 100644 index 0000000..89662f9 --- /dev/null +++ b/vio/vio/api_v2/api_definition/hosts.yaml @@ -0,0 +1,72 @@ +--- + info: + version: "1.0.0" + title: "Multi Cloud Host" + description: "Definition of Host API" + termsOfService: "http://swagger.io/terms/" + schemes: + - "http" + produces: + - "application/json" + paths: + /{vimid}/{tenantid}/hosts/{hostid}: + parameters: + - type: string + name: vimid + - type: string + format: uuid + name: tenantid + - type: string + name: hostid + in: path + required: true + get: + produces: + - "application/json" + responses: + "200": + schema: + $ref: "#/definitions/host" + get_all: + produces: + - "application/json" + responses: + "200": + schema: + type: "array" + items: + $ref: "#/definitions/host" + vim_path: "/compute/os-hypervisors" + definitions: + host: + plural_vim_resource: "hypervisors" + vim_resource: "hypervisor" + plural: "hosts" + properties: + name: + type: string + required: true + source: hypervisor.hypervisor_hostname + id: + type: string + required: true + source: hypervisor.id + status: + type: string + source: hypervisor.status + state: + type: string + source: hypervisor.state + cpu: + type: integer + minimal: 1 + source: hypervisor.vcpus + action: copy + disk_gb: + type: integer + minimal: 0 + source: hypervisor.local_gb + memory_mb: + type: integer + minimal: 0 + source: hypervisor.memory_mb diff --git a/vio/vio/api_v2/api_definition/networks.yaml b/vio/vio/api_v2/api_definition/networks.yaml new file mode 100644 index 0000000..2be9943 --- /dev/null +++ b/vio/vio/api_v2/api_definition/networks.yaml @@ -0,0 +1,91 @@ +--- + info: + version: "1.0.0" + title: "Multi Cloud Network" + description: "Definition of Host API" + termsOfService: "http://swagger.io/terms/" + schemes: + - "http" + produces: + - "application/json" + paths: + /{vimid}/{tenantid}/networks/{networkid}: + parameters: + - type: string + name: vimid + - type: string + format: uuid + name: tenantid + - type: string + name: networkid + in: path + required: true + get: + produces: + - "application/json" + responses: + "200": + schema: + $ref: "#/definitions/network" + get_all: + produces: + - "application/json" + responses: + "200": + schema: + type: "array" + items: + $ref: "#/definitions/network" + post: + produces: + - "application/json" + responses: + "200": + schema: + $ref: "#/definitions/network" + delete: + responses: "204" + vim_path: "/network/v2.0/networks" + definitions: + network: + plural_vim_resource: "networks" + vim_resource: "network" + plural: "networks" + properties: + name: + type: string + required: true + source: network.name + id: + type: string + source: network.id + status: + type: string + source: network.status + segmentationId: + type: string + source: network.provider:segmentation_id + default: None + physicalNetwork: + type: string + source: network.provider:physical_network + default: None + networkType: + type: string + source: network.provider:network_type + default: None + tenantId: + type: string + source: network.tenant_id + shared: + type: boolean + source: network.shared + required: true + routerExternal: + type: boolean + source: network.router:external + required: true + vlanTransparent: + type: boolean + source: network.vlan_transparent + default: false diff --git a/vio/vio/api_v2/api_definition/utils.py b/vio/vio/api_v2/api_definition/utils.py new file mode 100644 index 0000000..faf1fb9 --- /dev/null +++ b/vio/vio/api_v2/api_definition/utils.py @@ -0,0 +1,31 @@ +# 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 pkg_resources +import yaml + + +def get_definition_list(): + """ Get API Definition from YAML files. """ + + api_def = [] + definition_dir = __name__[:__name__.rfind(".")] + for f in pkg_resources.resource_listdir(definition_dir, '.'): + if not f.endswith(".yaml"): + continue + + with pkg_resources.resource_stream(definition_dir, f) as fd: + # TODO(xiaohhui): Should add exception handler to inform user of + # potential error. + api_def.append(yaml.safe_load(fd)) + + return api_def diff --git a/vio/vio/api_v2/api_router/controller_builder.py b/vio/vio/api_v2/api_router/controller_builder.py new file mode 100644 index 0000000..4e6d39d --- /dev/null +++ b/vio/vio/api_v2/api_router/controller_builder.py @@ -0,0 +1,218 @@ +# 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 +from keystoneauth1.identity import v2 as keystone_v2 +from keystoneauth1.identity import v3 as keystone_v3 +from keystoneauth1 import session +import pecan +from pecan import rest + +from vio.api_v2.api_definition import utils +from vio.pub import exceptions +from vio.pub.msapi import extsys + + +def _get_vim_auth_session(vim_id, tenant_id): + """ Get the auth session to backend VIM """ + + try: + vim = extsys.get_vim_by_id(vim_id) + except exceptions.VimDriverVioException as e: + return pecan.abort(500, str(e)) + + params = { + "auth_url": vim["url"], + "username": vim["userName"], + "password": vim["password"], + } + params["tenant_id"] = tenant_id + + if '/v2' in params["auth_url"]: + auth = keystone_v2.Password(**params) + else: + params["user_domain_name"] = vim["domain"] + params["project_domain_name"] = vim["domain"] + + if 'tenant_id' in params: + params["project_id"] = params.pop("tenant_id") + if 'tenant_name' in params: + params["project_name"] = params.pop("tenant_name") + if '/v3' not in params["auth_url"]: + params["auth_url"] = params["auth_url"] + "/v3", + auth = keystone_v3.Password(**params) + + return session.Session(auth=auth) + + +def _convert_default_value(default): + if default == "None": + return None + + if default == "true": + return True + + if default == "false": + return False + + return default + + +def _convert_vim_res_to_mc_res(vim_resource, res_properties): + mc_resource = {} + for key in res_properties: + vim_res, attr = res_properties[key]["source"].split('.') + + if attr not in vim_resource[vim_res]: + if res_properties[key].get("required"): + raise Exception("Required field %s is missed in VIM " + "resource %s", (attr, vim_resource)) + else: + if "default" in res_properties[key]: + mc_resource[key] = _convert_default_value( + res_properties[key]["default"]) + + # None required fields missed, just skip. + continue + + action = res_properties[key].get("action", "copy") + # TODO(xiaohhui): Actions should be in constants. + if action == "copy": + mc_resource[key] = vim_resource[vim_res][attr] + + return mc_resource + + +def _convert_mc_res_to_vim_res(mc_resource, res_properties): + vim_resource = {} + for key in res_properties: + vim_res, attr = res_properties[key]["source"].split('.') + + if key not in mc_resource: + if res_properties[key].get("required"): + raise Exception("Required field %s is missed in MultiCloud " + "resource %s", (key, mc_resource)) + else: + # None required fields missed, just skip. + continue + + action = res_properties[key].get("action", "copy") + # TODO(xiaohhui): Actions should be in constants. + if action == "copy": + vim_resource[attr] = mc_resource[key] + + return vim_resource + + +def _build_api_controller(api_meta): + # Assume that only one path + path, path_meta = api_meta['paths'].items()[0] + # url path is behind third slash. The first is vimid, the second is + # tenantid. + path = path.split("/")[3] + controller_name = path.upper() + "Controller" + delimiter = path_meta["vim_path"].find("/", 1) + service_type = path_meta["vim_path"][1:delimiter] + resource_url = path_meta["vim_path"][delimiter:] + + # Assume that only one resource + name, resource_meta = api_meta['definitions'].items()[0] + resource_properties = resource_meta['properties'] + + controller_meta = {} + if "get" in path_meta: + # Add get method to controller. + @pecan.expose("json") + def _get(self, vim_id, tenant_id, resource_id): + """ General GET """ + session = _get_vim_auth_session(vim_id, tenant_id) + service = {'service_type': service_type, + 'interface': 'public'} + full_url = resource_url + "/%s" % resource_id + resp = session.get(full_url, endpoint_filter=service) + mc_res = _convert_vim_res_to_mc_res(resp.json(), + resource_properties) + return {"vimName": vim_id, + name: mc_res, + "tenantId": tenant_id, + "vimid": vim_id} + + controller_meta["get"] = _get + + if "get_all" in path_meta: + # Add get_all method to controller + @pecan.expose("json") + def _get_all(self, vim_id, tenant_id): + """ General GET all """ + session = _get_vim_auth_session(vim_id, tenant_id) + service = {'service_type': service_type, + 'interface': 'public'} + resp = session.get(resource_url, endpoint_filter=service) + vim_res = resp.json()[resource_meta['plural_vim_resource']] + mc_res = [_convert_vim_res_to_mc_res( + {resource_meta['vim_resource']: v}, + resource_properties) + for v in vim_res] + return {"vimName": vim_id, + resource_meta['plural']: mc_res, + "tenantId": tenant_id, + "vimid": vim_id} + + controller_meta["get_all"] = _get_all + + if "post" in path_meta: + # Add post method to controller + @pecan.expose("json") + def _post(self, vim_id, tenant_id): + """ General POST """ + session = _get_vim_auth_session(vim_id, tenant_id) + service = {'service_type': service_type, + 'interface': 'public'} + vim_res = _convert_mc_res_to_vim_res(pecan.request.json_body, + resource_properties) + + req_body = json.JSONEncoder().encode( + {resource_meta['vim_resource']: vim_res}) + resp = session.post(resource_url, + data=req_body, + endpoint_filter=service) + mc_res = _convert_vim_res_to_mc_res(resp.json(), + resource_properties) + mc_res.update({"vimName": vim_id, + "vimId": vim_id, + "tenantId": tenant_id, + "returnCode": 0}) + return mc_res + + controller_meta["post"] = _post + + if "delete" in path_meta: + # Add delete method to controller + @pecan.expose("json") + def _delete(self, vim_id, tenant_id, resource_id): + """ General DELETE """ + session = _get_vim_auth_session(vim_id, tenant_id) + service = {'service_type': service_type, + 'interface': 'public'} + full_url = resource_url + "/%s" % resource_id + session.delete(full_url, endpoint_filter=service) + + controller_meta["delete"] = _delete + + return path, type(controller_name, (rest.RestController,), controller_meta) + + +def insert_dynamic_controller(root_controller): + api_defs = utils.get_definition_list() + for d in api_defs: + path, con_class = _build_api_controller(d) + setattr(root_controller, path, con_class()) diff --git a/vio/vio/api_v2/api_router/swagger_json.py b/vio/vio/api_v2/api_router/swagger_json.py new file mode 100644 index 0000000..e5eda63 --- /dev/null +++ b/vio/vio/api_v2/api_router/swagger_json.py @@ -0,0 +1,23 @@ +# 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 pecan +from pecan import rest + +from vio.swagger import utils + + +class SwaggerJson(rest.RestController): + + @pecan.expose("json") + def get(self): + return utils.get_swagger_json_data() diff --git a/vio/vio/api_v2/api_router/v0_controller.py b/vio/vio/api_v2/api_router/v0_controller.py index 7c7ab18..d7d428d 100644 --- a/vio/vio/api_v2/api_router/v0_controller.py +++ b/vio/vio/api_v2/api_router/v0_controller.py @@ -11,12 +11,37 @@ # limitations under the License. import pecan +from pecan import rest -from vio.swagger import utils +from vio.api_v2.api_router import controller_builder +from vio.api_v2.api_router import swagger_json -class V0_Controller(object): +class V0_Controller(rest.RestController): - @pecan.expose('json', route="swagger.json") - def swagger_json(self): - return utils.get_swagger_json_data() + def get(self, vim_id, tenant_id): + """ Placeholder for sub controllers. """ + pecan.abort(405) + + def put(self, vim_id, tenant_id): + """ Placeholder for sub controllers. """ + pecan.abort(405) + + def post(self, vim_id, tenant_id): + """ Placeholder for sub controllers. """ + pecan.abort(405) + + def delete(self, vim_id, tenant_id): + """ Placeholder for sub controllers. """ + pecan.abort(405) + + def get_all(self, vim_id, tenant_id): + """ Placeholder for sub controllers. """ + pecan.abort(405) + + +pecan.route(V0_Controller, "swagger.json", swagger_json.SwaggerJson()) + + +# Insert API stem from yaml files. +controller_builder.insert_dynamic_controller(V0_Controller) diff --git a/vio/vio/pub/vim/drivers/vimsdk/image_v2.py b/vio/vio/pub/vim/drivers/vimsdk/image_v2.py index f185e35..a7034cf 100644 --- a/vio/vio/pub/vim/drivers/vimsdk/image_v2.py +++ b/vio/vio/pub/vim/drivers/vimsdk/image_v2.py @@ -65,3 +65,15 @@ class GlanceClient(base.DriverBase): def upload_image(self, data, image): image.data = data image.upload(self.session) + + @sdk.translate_exception + def create_image_file(self, file_name, image_type): + + img = self._proxy._create(_image.Image, disk_format=image_type, + container_format='bare', name=file_name) + return img + + @sdk.translate_exception + def download_image(self, image): + img = image.download(self.session, stream=True) + return img diff --git a/vio/vio/pub/vim/vimapi/glance/OperateImage.py b/vio/vio/pub/vim/vimapi/glance/OperateImage.py index 35deb4c..b26a184 100644 --- a/vio/vio/pub/vim/vimapi/glance/OperateImage.py +++ b/vio/vio/pub/vim/vimapi/glance/OperateImage.py @@ -102,3 +102,16 @@ class OperateImage(baseclient): except Exception: pass return image + + def create_vim_image_file(self, vimid, tenantid, file_name, + file_dest, image_type): + + image = self.glance(self.param).create_image_file(file_name, + image_type) + self.glance(self.param).upload_image(open(file_dest), image) + return image + + def download_vim_image(self, image): + + image_data = self.glance(self.param).download_image(image) + return image_data diff --git a/vio/vio/swagger/urls.py b/vio/vio/swagger/urls.py index 2dcae22..3871599 100644 --- a/vio/vio/swagger/urls.py +++ b/vio/vio/swagger/urls.py @@ -20,6 +20,8 @@ from vio.swagger.views.swagger_json import SwaggerJsonView from vio.swagger.views.tenant.views import ListTenantsView from vio.swagger.views.image.views import CreateListImagesView from vio.swagger.views.image.views import GetDeleteImageView +from vio.swagger.views.image.views import CreateImageFileView +from vio.swagger.views.image.views import GetImageFileView from vio.swagger.views.volume.views import CreateListVolumeView from vio.swagger.views.volume.views import GetDeleteVolumeView from vio.swagger.views.server.views import ListServersView, GetServerView @@ -99,6 +101,13 @@ urlpatterns = [ r'(?P<tenantid>[0-9a-zA-Z_-]+)/images/(?P<imageid>[0-9a-zA-Z_-]+)$', GetDeleteImageView.as_view()), url(r'^api/multicloud-vio/v0/(?P<vimid>[0-9a-zA-Z_-]+)/' + r'(?P<tenantid>[0-9a-zA-Z_-]+)/images/file$', + CreateImageFileView.as_view()), + url(r'^api/multicloud-vio/v0/(?P<vimid>[0-9a-zA-Z_-]+)/' + r'(?P<tenantid>[0-9a-zA-Z_-]+)/images/file/' + r'(?P<imageid>[0-9a-zA-Z_-]+)$', + GetImageFileView.as_view()), + url(r'^api/multicloud-vio/v0/(?P<vimid>[0-9a-zA-Z_-]+)/' r'(?P<tenantid>[0-9a-zA-Z_-]+)/volumes$', CreateListVolumeView.as_view()), url(r'^api/multicloud-vio/v0/(?P<vimid>[0-9a-zA-Z_-]+)/' diff --git a/vio/vio/swagger/views/image/views.py b/vio/vio/swagger/views/image/views.py index 26914cf..52d14ef 100644 --- a/vio/vio/swagger/views/image/views.py +++ b/vio/vio/swagger/views/image/views.py @@ -11,6 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import json +import random +import string +import sys from rest_framework import status from rest_framework.response import Response @@ -135,3 +138,103 @@ class CreateListImagesView(APIView): else: return Response(data={'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +class CreateImageFileView(APIView): + + def post(self, request, vimid, tenantid): + + try: + vim_info = extsys.get_vim_by_id(vimid) + vim_info['tenant'] = tenantid + except VimDriverVioException as e: + return Response(data={'error': str(e)}, status=e.status_code) + + vim_rsp = image_utils.vim_formatter(vim_info, tenantid) + image_instance = OperateImage.OperateImage(vim_info) + + try: + image_file = request.FILES['file'] + + random_name = ''.join(random.sample( + string.ascii_letters + + string.digits, 4)) + file_name = image_file.name[:image_file.name.rfind('.')] + + "_" + random_name + + image_file.name[image_file.name.find('.'):] + + file_dest = sys.path[0] + '/images/' + file_name + with open(file_dest, 'wb+') as temp_file: + for chunk in image_file.chunks(): + temp_file.write(chunk) + temp_file.close() + + image_type = image_file.name[image_file.name.find('.') + 1:] + + except Exception as e: + return Response(data={'error': str(e)}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + try: + image = image_instance.create_vim_image_file( + vimid, tenantid, + file_name[:file_name.rfind('.')], + file_dest, + image_type) + rsp = image_utils.image_formatter(image) + rsp.update(vim_rsp) + rsp['returnCode'] = '1' + + return Response(data={'status': rsp}, + status=status.HTTP_201_CREATED) + 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) + + +class GetImageFileView(APIView): + + def post(self, request, vimid, tenantid, imageid): + try: + vim_info = extsys.get_vim_by_id(vimid) + vim_info['tenant'] = tenantid + except VimDriverVioException as e: + return Response(data={'error': str(e)}, status=e.status_code) + + try: + req_body = json.loads(request.body) + except Exception as e: + return Response(data={'error': 'Fail to decode request body.'}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR) + image_instance = OperateImage.OperateImage(vim_info) + try: + image = image_instance.find_vim_image(imageid) + except Exception as e: + return Response(data={'error': 'the image does not exist'}, + status=status.HTTP_404_NOT_FOUND) + + try: + image_data = image_instance.download_vim_image(image) + + imagePath = req_body.get('imagePath') + if imagePath[-1:] is not '/': + imagePath += '/' + file_name = "%s%s.%s" % (imagePath, image.name, image.disk_format) + image_file = open(file_name, 'w+') + + for chunk in image_data: + image_file.write(chunk) + image_file.close() + + return Response(data={'status': 'donwload OK'}, + status=status.HTTP_200_OK) + + 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) |