First pass at feature parity. Includes Image ID hash
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user