First pass at feature parity. Includes Image ID hash

This commit is contained in:
Sandy Walsh
2011-01-03 15:46:55 -08:00
8 changed files with 87 additions and 9 deletions

View File

@@ -51,6 +51,10 @@ flags.DEFINE_string('os_api_ratelimiting',
'nova.api.openstack.ratelimiting.RateLimitingMiddleware', 'nova.api.openstack.ratelimiting.RateLimitingMiddleware',
'Default ratelimiting implementation for the Openstack API') 'Default ratelimiting implementation for the Openstack API')
flags.DEFINE_string('os_krm_mapping_file',
'krm_mapping.json',
'Location of OpenStack Flavor/OS:EC2 Kernel/Ramdisk/Machine JSON file.')
flags.DEFINE_bool('allow_admin_api', flags.DEFINE_bool('allow_admin_api',
False, False,
'When True, this API service will accept admin operations.') 'When True, this API service will accept admin operations.')
@@ -109,7 +113,7 @@ class APIRouter(wsgi.Router):
collection={'detail': 'GET'}) collection={'detail': 'GET'})
mapper.resource("flavor", "flavors", controller=flavors.Controller(), mapper.resource("flavor", "flavors", controller=flavors.Controller(),
collection={'detail': 'GET'}) collection={'detail': 'GET'})
mapper.resource("sharedipgroup", "sharedipgroups", mapper.resource("shared_ip_group", "shared_ip_groups",
controller=sharedipgroups.Controller()) controller=sharedipgroups.Controller())
super(APIRouter, self).__init__(mapper) super(APIRouter, self).__init__(mapper)

View File

@@ -15,7 +15,9 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import logging
import time import time
from webob import exc from webob import exc
from nova import wsgi from nova import wsgi
@@ -46,8 +48,8 @@ class Controller(wsgi.Controller):
def create(self, req, server_id): def create(self, req, server_id):
""" No actual update method required, since the existing API allows """ No actual update method required, since the existing API allows
both create and update through a POST """ both create and update through a POST """
return faults.Fault(exc.HTTPNotFound()) return faults.Fault(exc.HTTPNotImplemented())
def delete(self, req, server_id, id): def delete(self, req, server_id, id):
""" Deletes an existing backup schedule """ """ Deletes an existing backup schedule """
return faults.Fault(exc.HTTPNotFound()) return faults.Fault(exc.HTTPNotImplemented())

View File

@@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from nova import exception
def limited(items, req): def limited(items, req):
"""Return a slice of items according to requested offset and limit. """Return a slice of items according to requested offset and limit.
@@ -34,3 +36,25 @@ def limited(items, req):
limit = min(1000, limit) limit = min(1000, limit)
range_end = offset + limit range_end = offset + limit
return items[offset:range_end] return items[offset:range_end]
def get_image_id_from_image_hash(image_service, context, image_hash):
"""Given an Image ID Hash, return an objectstore Image ID.
image_service - reference to objectstore compatible image service.
context - security context for image service requests.
image_hash - hash of the image ID.
"""
# FIX(sandy): This is terribly inefficient. It pulls all images
# from objectstore in order to find the match. ObjectStore
# should have a numeric counterpart to the string ID.
try:
items = image_service.detail(context)
except NotImplementedError:
items = image_service.index(context)
for image in items:
image_id = image['imageId']
if abs(hash(image_id)) == int(image_hash):
return image_id
raise exception.NotFound(image_hash)

View File

@@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import logging
from webob import exc from webob import exc
from nova import flags from nova import flags
@@ -27,6 +29,7 @@ from nova.api.openstack import common
from nova.api.openstack import faults from nova.api.openstack import faults
from nova.compute import api as compute_api from nova.compute import api as compute_api
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@@ -89,6 +92,12 @@ def _filter_keys(item, keys):
return dict((k, v) for k, v in item.iteritems() if k in keys) return dict((k, v) for k, v in item.iteritems() if k in keys)
def _convert_image_id_to_hash(image):
image_id = abs(hash(image['imageId']))
image['imageId'] = image_id
image['id'] = image_id
class Controller(wsgi.Controller): class Controller(wsgi.Controller):
_serialization_metadata = { _serialization_metadata = {
@@ -113,6 +122,9 @@ class Controller(wsgi.Controller):
items = self._service.detail(req.environ['nova.context']) items = self._service.detail(req.environ['nova.context'])
except NotImplementedError: except NotImplementedError:
items = self._service.index(req.environ['nova.context']) items = self._service.index(req.environ['nova.context'])
for image in items:
_convert_image_id_to_hash(image)
items = common.limited(items, req) items = common.limited(items, req)
items = [_translate_keys(item) for item in items] items = [_translate_keys(item) for item in items]
items = [_translate_status(item) for item in items] items = [_translate_status(item) for item in items]
@@ -120,7 +132,12 @@ class Controller(wsgi.Controller):
def show(self, req, id): def show(self, req, id):
"""Return data about the given image id""" """Return data about the given image id"""
return dict(image=self._service.show(req.environ['nova.context'], id)) image_id = common.get_image_id_from_image_hash(self._service,
req.environ['nova.context'], id)
image = self._service.show(req.environ['nova.context'], image_id)
_convert_image_id_to_hash(image)
return dict(image=image)
def delete(self, req, id): def delete(self, req, id):
# Only public images are supported for now. # Only public images are supported for now.

View File

@@ -15,13 +15,16 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
import logging import logging
import traceback import traceback
from webob import exc from webob import exc
from nova import exception from nova import exception
from nova import flags
from nova import wsgi from nova import wsgi
from nova import utils
from nova.api.openstack import common from nova.api.openstack import common
from nova.api.openstack import faults from nova.api.openstack import faults
from nova.auth import manager as auth_manager from nova.auth import manager as auth_manager
@@ -35,6 +38,9 @@ LOG = logging.getLogger('server')
LOG.setLevel(logging.DEBUG) LOG.setLevel(logging.DEBUG)
FLAGS = flags.FLAGS
def _translate_detail_keys(inst): def _translate_detail_keys(inst):
""" Coerces into dictionary format, mapping everything to Rackspace-like """ Coerces into dictionary format, mapping everything to Rackspace-like
attributes for return""" attributes for return"""
@@ -81,6 +87,7 @@ class Controller(wsgi.Controller):
def __init__(self): def __init__(self):
self.compute_api = compute_api.ComputeAPI() self.compute_api = compute_api.ComputeAPI()
self._image_service = utils.import_object(FLAGS.image_service)
super(Controller, self).__init__() super(Controller, self).__init__()
def index(self, req): def index(self, req):
@@ -120,6 +127,18 @@ class Controller(wsgi.Controller):
return faults.Fault(exc.HTTPNotFound()) return faults.Fault(exc.HTTPNotFound())
return exc.HTTPAccepted() return exc.HTTPAccepted()
def _get_kernel_ramdisk_from_image(self, image_id):
mapping_filename = FLAGS.os_krm_mapping_file
with open(mapping_filename) as f:
mapping = json.load(f)
if mapping.has_key(image_id):
return mapping[image_id]
raise exception.NotFound(
_("No entry for image '%s' in mapping file '%s'") %
(image_id, mapping_filename))
def create(self, req): def create(self, req):
""" Creates a new server for a given user """ """ Creates a new server for a given user """
env = self._deserialize(req.body, req) env = self._deserialize(req.body, req)
@@ -128,10 +147,15 @@ class Controller(wsgi.Controller):
key_pair = auth_manager.AuthManager.get_key_pairs( key_pair = auth_manager.AuthManager.get_key_pairs(
req.environ['nova.context'])[0] req.environ['nova.context'])[0]
image_id = common.get_image_id_from_image_hash(self._image_service,
req.environ['nova.context'], env['server']['imageId'])
kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(image_id)
instances = self.compute_api.create_instances( instances = self.compute_api.create_instances(
req.environ['nova.context'], req.environ['nova.context'],
instance_types.get_by_flavor_id(env['server']['flavorId']), instance_types.get_by_flavor_id(env['server']['flavorId']),
env['server']['imageId'], image_id,
kernel_id = kernel_id,
ramdisk_id = ramdisk_id,
display_name=env['server']['name'], display_name=env['server']['name'],
description=env['server']['name'], description=env['server']['name'],
key_name=key_pair['name'], key_name=key_pair['name'],
@@ -163,6 +187,7 @@ class Controller(wsgi.Controller):
""" Multi-purpose method used to reboot, rebuild, and """ Multi-purpose method used to reboot, rebuild, and
resize a server """ resize a server """
input_dict = self._deserialize(req.body, req) input_dict = self._deserialize(req.body, req)
#TODO(sandy): rebuild/resize not supported.
try: try:
reboot_type = input_dict['reboot']['type'] reboot_type = input_dict['reboot']['type']
except Exception: except Exception:

View File

@@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import logging
from webob import exc from webob import exc
from nova import wsgi from nova import wsgi
@@ -29,7 +31,7 @@ def _translate_keys(inst):
def _translate_detail_keys(inst): def _translate_detail_keys(inst):
""" Coerces a shared IP group instance into proper dictionary format with """ Coerces a shared IP group instance into proper dictionary format with
correctly mapped attributes """ correctly mapped attributes """
return dict(sharedIpGroup=inst) return dict(sharedIpGroups=inst)
class Controller(wsgi.Controller): class Controller(wsgi.Controller):
@@ -46,6 +48,8 @@ class Controller(wsgi.Controller):
def show(self, req, id): def show(self, req, id):
""" Shows in-depth information on a specific Shared IP Group """ """ Shows in-depth information on a specific Shared IP Group """
if id == 'detail':
return _translate_detail_keys({})
return _translate_keys({}) return _translate_keys({})
def update(self, req, id): def update(self, req, id):
@@ -54,7 +58,7 @@ class Controller(wsgi.Controller):
def delete(self, req, id): def delete(self, req, id):
""" Deletes a Shared IP Group """ """ Deletes a Shared IP Group """
raise faults.Fault(exc.HTTPNotFound()) raise faults.Fault(exc.HTTPNotImplemented())
def detail(self, req, id): def detail(self, req, id):
""" Returns a complete list of Shared IP Groups """ """ Returns a complete list of Shared IP Groups """
@@ -62,4 +66,4 @@ class Controller(wsgi.Controller):
def create(self, req): def create(self, req):
""" Creates a new Shared IP group """ """ Creates a new Shared IP group """
raise faults.Fault(exc.HTTPNotFound()) raise faults.Fault(exc.HTTPNotImplemented())

View File

@@ -102,6 +102,7 @@ class ComputeAPI(base.Base):
ramdisk_id = None ramdisk_id = None
logging.debug("Creating a raw instance") logging.debug("Creating a raw instance")
# Make sure we have access to kernel and ramdisk (if not raw) # Make sure we have access to kernel and ramdisk (if not raw)
logging.debug("Using Kernel=%s, Ramdisk=%s" % (kernel_id, ramdisk_id))
if kernel_id: if kernel_id:
self.image_service.show(context, kernel_id) self.image_service.show(context, kernel_id)
if ramdisk_id: if ramdisk_id:

View File

@@ -360,7 +360,8 @@ class VMHelper(HelperBase):
if i >= 3 and i <= 11: if i >= 3 and i <= 11:
ref = node.childNodes ref = node.childNodes
# Name and Value # Name and Value
diags[ref[0].firstChild.data] = ref[6].firstChild.data if len(ref) > 6:
diags[ref[0].firstChild.data] = ref[6].firstChild.data
return diags return diags
except cls.XenAPI.Failure as e: except cls.XenAPI.Failure as e:
return {"Unable to retrieve diagnostics": e} return {"Unable to retrieve diagnostics": e}