""" Copyright (c) 2017 SONATA-NFV and Paderborn University ALL RIGHTS RESERVED. 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. Neither the name of the SONATA-NFV, Paderborn University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. This work has been performed in the framework of the SONATA project, funded by the European Commission under Grant number 671517 through the Horizon 2020 and 5G-PPP programmes. The authors would like to acknowledge the contributions of their colleagues of the SONATA partner consortium (www.sonata-nfv.eu). """ from flask_restful import Resource from flask import Response, request from emuvim.api.openstack.openstack_dummies.base_openstack_dummy import BaseOpenstackDummy from emuvim.api.openstack.helper import get_host import logging import json LOG = logging.getLogger("api.openstack.glance") class GlanceDummyApi(BaseOpenstackDummy): def __init__(self, in_ip, in_port, compute): super(GlanceDummyApi, self).__init__(in_ip, in_port) self.compute = compute self.api.add_resource(GlanceListApiVersions, "/versions") self.api.add_resource(GlanceSchema, "/v2/schemas/image", "/v2/schemas/metadefs/namespace", "/v2/schemas/metadefs/resource_type") self.api.add_resource(GlanceListImagesApi, "/v1/images", "/v1/images/detail", "/v2/images", "/v2/images/detail", resource_class_kwargs={'api': self}) self.api.add_resource(GlanceImageByIdApi, "/v1/images/", "/v2/images/", resource_class_kwargs={'api': self}) self.api.add_resource(GlanceImageByDockerNameApi, "/v1/images//", "/v2/images//", resource_class_kwargs={'api': self}) class GlanceListApiVersions(Resource): def get(self): LOG.debug("API CALL: %s GET" % str(self.__class__.__name__)) resp = dict() resp['versions'] = dict() versions = [{ "status": "CURRENT", "id": "v2", "links": [ { "href": request.url_root + '/v2', "rel": "self" } ] }] resp['versions'] = versions return Response(json.dumps(resp), status=200, mimetype='application/json') class GlanceSchema(Resource): def get(self): LOG.debug("API CALL: %s GET" % str(self.__class__.__name__)) resp = dict() resp['name'] = 'someImageName' resp['properties'] = dict() # just an ugly hack to allow the openstack client to work return Response(json.dumps(resp), status=200, mimetype='application/json') class GlanceListImagesApi(Resource): def __init__(self, api): self.api = api def get(self): LOG.debug("API CALL: %s GET" % str(self.__class__.__name__)) try: resp = dict() resp['next'] = None resp['first'] = "/v2/images" resp['schema'] = "/v2/schemas/images" resp['images'] = list() limit = 18 c = 0 for image in self.api.compute.images.values(): f = dict() f['id'] = image.id f['name'] = str(image.name).replace(":latest", "") f['checksum'] = "2dad48f09e2a447a9bf852bcd93548c1" f['container_format'] = "docker" f['disk_format'] = "raw" f['size'] = 1 f['created_at'] = "2016-03-15T15:09:07.000000" f['deleted'] = False f['deleted_at'] = None f['is_public'] = True f['min_disk'] = 1 f['min_ram'] = 128 f['owner'] = "3dad48f09e2a447a9bf852bcd93548c1" f['properties'] = {} f['protected'] = False f['status'] = "active" f['updated_at'] = "2016-03-15T15:09:07.000000" f['virtual_size'] = 1 f['marker'] = None resp['images'].append(f) c += 1 if c > limit: # ugly hack to stop buggy glance client to do infinite requests break if "marker" in request.args: # ugly hack to fix pageination of openstack client resp['images'] = None return Response(json.dumps(resp), status=200, mimetype="application/json") except Exception as ex: LOG.exception(u"%s: Could not retrieve the list of images." % __name__) return ex.message, 500 def post(self): """ This one is a real fake! It does not really create anything and the mentioned image should already be registered with Docker. However, this function returns a reply that looks like the image was just created to make orchestrators, like OSM, happy. """ LOG.debug("API CALL: %s POST" % str(self.__class__.__name__)) try: body_data = json.loads(request.data) except: body_data = dict() # lets see what we should create img_name = request.headers.get("X-Image-Meta-Name") img_size = request.headers.get("X-Image-Meta-Size") img_disk_format = request.headers.get("X-Image-Meta-Disk-Format") img_is_public = request.headers.get("X-Image-Meta-Is-Public") img_container_format = request.headers.get("X-Image-Meta-Container-Format") # try to use body payload if header fields are empty if img_name is None: img_name = body_data.get("name") img_size = 1234 img_disk_format = body_data.get("disk_format") img_is_public = True if "public" in body_data.get("visibility") else False img_container_format = body_data.get("container_format") # try to find ID of already existing image (matched by name) img_id = None for image in self.api.compute.images.values(): if str(img_name) in image.name: img_id = image.id LOG.debug("Image name: %s" % img_name) LOG.debug("Image id: %s" % img_id) # build a response body that looks like a real one resp = dict() f = dict() f['id'] = img_id f['name'] = img_name f['checksum'] = "2dad48f09e2a447a9bf852bcd93548c1" f['container_format'] = img_container_format f['disk_format'] = img_disk_format f['size'] = img_size f['created_at'] = "2016-03-15T15:09:07.000000" f['deleted'] = False f['deleted_at'] = None f['is_public'] = img_is_public f['min_disk'] = 1 f['min_ram'] = 128 f['owner'] = "3dad48f09e2a447a9bf852bcd93548c1" f['properties'] = {} f['protected'] = False f['status'] = "active" f['updated_at'] = "2016-03-15T15:09:07.000000" f['virtual_size'] = 1 resp['image'] = f # build actual response with headers and everything r = Response(json.dumps(resp), status=201, mimetype="application/json") r.headers.add("Location", "http://%s:%d/v1/images/%s" % (get_host(request), self.api.port, img_id)) return r class GlanceImageByIdApi(Resource): def __init__(self, api): self.api = api def get(self, id): LOG.debug("API CALL: %s GET" % str(self.__class__.__name__)) try: resp = dict() for image in self.api.compute.images.values(): if image.id == id or image.name == id: resp['id'] = image.id resp['name'] = image.name return Response(json.dumps(resp), status=200, mimetype="application/json") response = Response("Image with id or name %s does not exists." % id, status=404) response.headers['Access-Control-Allow-Origin'] = '*' return response except Exception as ex: LOG.exception(u"%s: Could not retrieve image with id %s." % (__name__, id)) return Response(ex.message, status=500, mimetype='application/json') def put(self, id): LOG.debug("API CALL: %s " % str(self.__class__.__name__)) LOG.warning("Endpoint not implemented") return None class GlanceImageByDockerNameApi(Resource): def __init__(self, api): self.api = api def get(self, owner, container): logging.debug("API CALL: %s GET" % str(self.__class__.__name__)) try: name = "%s/%s" % (owner, container) if name in self.api.compute.images: image = self.api.compute.images[name] resp = dict() resp['id'] = image.id resp['name'] = image.name return Response(json.dumps(resp), status=200, mimetype="application/json") response = Response("Image with id or name %s does not exists." % id, status=404) response.headers['Access-Control-Allow-Origin'] = '*' return response except Exception as ex: logging.exception(u"%s: Could not retrieve image with id %s." % (__name__, id)) return Response(ex.message, status=500, mimetype='application/json')