summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--vio/images/empty.txt0
-rw-r--r--vio/vio/api_v2/api_definition/__init__.py0
-rw-r--r--vio/vio/api_v2/api_definition/hosts.yaml72
-rw-r--r--vio/vio/api_v2/api_definition/networks.yaml91
-rw-r--r--vio/vio/api_v2/api_definition/utils.py31
-rw-r--r--vio/vio/api_v2/api_router/controller_builder.py218
-rw-r--r--vio/vio/api_v2/api_router/swagger_json.py23
-rw-r--r--vio/vio/api_v2/api_router/v0_controller.py35
-rw-r--r--vio/vio/pub/vim/drivers/vimsdk/image_v2.py12
-rw-r--r--vio/vio/pub/vim/vimapi/glance/OperateImage.py13
-rw-r--r--vio/vio/swagger/urls.py9
-rw-r--r--vio/vio/swagger/views/image/views.py103
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)