Merge pull request #13 from cp16net/instance-api-improvments
Instance api improvments
This commit is contained in:
commit
fda3eb5ee9
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,3 +8,5 @@ guest-agent-files.txt
|
||||
reddwarf.egg*
|
||||
reddwarf/vcsversion.py
|
||||
*py*.egg
|
||||
.coverage
|
||||
covhtml/
|
||||
|
@ -35,22 +35,52 @@ keystone --endpoint http://localhost:35357/v2.0 --token be19c524ddc92109a224 use
|
||||
# These are the values
|
||||
REDDWARF_TENANT=reddwarf
|
||||
echo $REDDWARF_TENANT
|
||||
REDDWARF_USER=$(mysql keystone -e "select id from user where name='reddwarf';" | awk 'NR==2')
|
||||
echo $REDDWARF_USER
|
||||
REDDWARF_ROLE=$(mysql keystone -e "select id from role where name='reddwarf';" | awk 'NR==2')
|
||||
echo $REDDWARF_ROLE
|
||||
|
||||
# These all need to be set tenant did not work with the id but the name did match in the auth shim.
|
||||
# REDDWARF_TOKEN=
|
||||
REDDWARF_TOKEN=$(curl -d '{"auth":{"passwordCredentials":{"username": "reddwarf", "password": "REDDWARF-PASS"},"tenantName":"reddwarf"}}' -H "Content-type: application/json" http://localhost:35357/v2.0/tokens | python -mjson.tool | grep id | tr -s ' ' | cut -d ' ' -f 3 | sed s/\"/''/g | awk 'NR==2' | cut -d ',' -f 1)
|
||||
echo $REDDWARF_TOKEN
|
||||
|
||||
|
||||
# Now attempt a login
|
||||
curl -d '{"auth":{"passwordCredentials":{"username": "reddwarf", "password": "REDDWARF-PASS"},"tenantName":"reddwarf"}}' \
|
||||
-H "Content-type: application/json" http://localhost:35357/v2.0/tokens | python -mjson.tool
|
||||
#curl -d '{"auth":{"passwordCredentials":{"username": "reddwarf", "password": "REDDWARF-PASS"},"tenantName":"reddwarf"}}' \
|
||||
# -H "Content-type: application/json" http://localhost:35357/v2.0/tokens | python -mjson.tool
|
||||
|
||||
# now get a list of instances, which connects over python-novaclient to nova
|
||||
# NOTE THIS AUTH TOKEN NEEDS TO BE CHANGED
|
||||
# Also note that keystone uses the tenant id now and _not_ the name
|
||||
# curl -H"X-Auth-Token:$REDDWARF_TOKEN" http://0.0.0.0:8779/v0.1/$REDDWARF_TENANT/instances
|
||||
# curl -H"Content-type:application/json" -H"X-Auth-Token:$REDDWARF_TOKEN" \
|
||||
# http://0.0.0.0:8779/v0.1/$REDDWARF_TENANT/instances -d '{"name":"my_test","flavor":"1"}'
|
||||
# list instances
|
||||
# curl -H"X-Auth-Token:$REDDWARF_TOKEN" http://0.0.0.0:8779/v0.1/$REDDWARF_TENANT/instances | python -mjson.tool
|
||||
# old create instance:
|
||||
# curl -H"Content-type:application/json" -H"X-Auth-Token:$REDDWARF_TOKEN" http://0.0.0.0:8779/v0.1/$REDDWARF_TENANT/instances -d '{"name":"my_test","flavor":"1"}' | python -mjson.tool
|
||||
# create instance:
|
||||
# curl -H"Content-type:application/json" -H"X-Auth-Token:$REDDWARF_TOKEN" http://0.0.0.0:8779/v0.1/$REDDWARF_TENANT/instances -d '{"instance": {"databases": [{"character_set": "utf8", "collate": "utf8_general_ci", "name": "sampledb"}, {"name": "nextround"}], "flavorRef": "http://0.0.0.0:8779/v0.1/$REDDWARF_TENANT/flavors/1", "name": "json_rack_instance", "volume": {"size": "2"}}}'| python -mjson.tool
|
||||
# {
|
||||
# "instance": {
|
||||
# "databases": [
|
||||
# {
|
||||
# "character_set": "utf8",
|
||||
# "collate": "utf8_general_ci",
|
||||
# "name": "sampledb"
|
||||
# },
|
||||
# {
|
||||
# "name": "nextround"
|
||||
# }
|
||||
# ],
|
||||
# "flavorRef": "http://0.0.0.0:8779/v0.1/$REDDWARF_TENANT/flavors/1",
|
||||
# "name": "json_rack_instance",
|
||||
# "volume": {
|
||||
# "size": "2"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
|
||||
# DELETE INSTANCE
|
||||
# curl -H"X-Auth-Token:$REDDWARF_TOKEN" http://0.0.0.0:8779/v0.1/$REDDWARF_TENANT/instances/id -X DELETE | python -mjson.tool
|
||||
|
||||
|
||||
# update the etc/reddwarf/reddwarf.conf.sample
|
||||
# add this config setting
|
||||
@ -67,9 +97,24 @@ curl -d '{"auth":{"passwordCredentials":{"username": "reddwarf", "password": "RE
|
||||
|
||||
# ssh-keygen
|
||||
|
||||
# build the image for reddwarf
|
||||
# first time build the image for reddwarf
|
||||
# ./bootstrap/bootstrap.sh
|
||||
|
||||
##### re-add image manually #####
|
||||
VM_PATH=~/oneiric_mysql_image
|
||||
UBUNTU_DISTRO="ubuntu 11.10"
|
||||
UBUNTU_DISTRO_NAME=oneiric
|
||||
QCOW_IMAGE=`find $VM_PATH -name '*.qcow2'`
|
||||
function get_glance_id () {
|
||||
echo `$@ | awk '{print $6}'`
|
||||
}
|
||||
glance add name="oneiric_mysql_image" is_public=true container_format=ovf disk_format=qcow2 distro='"ubuntu 11.10"' -A $REDDWARF_TOKEN < $QCOW_IMAGE
|
||||
# GLANCE_IMAGEID=
|
||||
echo "updating your database - $GLANCE_IMAGEID"
|
||||
sqlite3 /src/reddwarf_test.sqlite "INSERT INTO service_images VALUES('1', 'database', '$GLANCE_IMAGEID');"
|
||||
#sqlite3 /src/reddwarf_test.sqlite "UPDATE service_images set image_id='$GLANCE_IMAGEID';"
|
||||
echo "done GLANCE IMAGE ID = $GLANCE_IMAGEID"
|
||||
|
||||
# add the image to the reddwarf database
|
||||
# get the image id from glance
|
||||
# glance index -A $REDDWARF_TOKEN
|
||||
|
@ -42,6 +42,13 @@ def stringify_keys(dictionary):
|
||||
return dict((str(key), value) for key, value in dictionary.iteritems())
|
||||
|
||||
|
||||
def exclude(key_values, *exclude_keys):
|
||||
if key_values is None:
|
||||
return None
|
||||
return dict((key, value) for key, value in key_values.iteritems()
|
||||
if key not in exclude_keys)
|
||||
|
||||
|
||||
def generate_uuid():
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
@ -116,7 +116,8 @@ class RemoteModelBase(ModelBase):
|
||||
|
||||
class Instance(RemoteModelBase):
|
||||
|
||||
_data_fields = ['name', 'status', 'updated', 'id', 'flavor']
|
||||
_data_fields = ['name', 'status', 'id', 'created', 'updated',
|
||||
'flavor', 'links', 'addresses']
|
||||
|
||||
def __init__(self, server=None, context=None, uuid=None):
|
||||
if server is None and context is None and uuid is None:
|
||||
@ -124,7 +125,12 @@ class Instance(RemoteModelBase):
|
||||
msg = "server, content, and uuid are not defined"
|
||||
raise InvalidModelError(msg)
|
||||
elif server is None:
|
||||
self._data_object = self.get_client(context).servers.get(uuid)
|
||||
try:
|
||||
self._data_object = self.get_client(context).servers.get(uuid)
|
||||
except nova_exceptions.NotFound, e:
|
||||
raise rd_exceptions.NotFound(uuid=uuid)
|
||||
except nova_exceptions.ClientException, e:
|
||||
raise rd_exceptions.ReddwarfError()
|
||||
else:
|
||||
self._data_object = server
|
||||
|
||||
@ -138,10 +144,13 @@ class Instance(RemoteModelBase):
|
||||
raise rd_exceptions.ReddwarfError()
|
||||
|
||||
@classmethod
|
||||
def create(cls, context, name, image_id, flavor):
|
||||
srv = cls.get_client(context).servers.create(name,
|
||||
def create(cls, context, image_id, body):
|
||||
# self.is_valid()
|
||||
LOG.info("instance body : '%s'\n\n" % body)
|
||||
flavorRef = body['instance']['flavorRef']
|
||||
srv = cls.get_client(context).servers.create(body['instance']['name'],
|
||||
image_id,
|
||||
flavor)
|
||||
flavorRef)
|
||||
return Instance(server=srv)
|
||||
|
||||
|
||||
|
@ -23,20 +23,42 @@ from reddwarf import rpc
|
||||
from reddwarf.common import config
|
||||
from reddwarf.common import context as rd_context
|
||||
from reddwarf.common import exception
|
||||
from reddwarf.common import utils
|
||||
from reddwarf.common import wsgi
|
||||
from reddwarf.database import models
|
||||
from reddwarf.database import views
|
||||
|
||||
CONFIG = config.Config
|
||||
LOG = logging.getLogger('reddwarf.database.service')
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseController(wsgi.Controller):
|
||||
"""Base controller class."""
|
||||
|
||||
exclude_attr = []
|
||||
exception_map = {
|
||||
webob.exc.HTTPUnprocessableEntity: [
|
||||
],
|
||||
webob.exc.HTTPBadRequest: [
|
||||
models.InvalidModelError,
|
||||
],
|
||||
webob.exc.HTTPNotFound: [
|
||||
exception.NotFound,
|
||||
models.ModelNotFoundError,
|
||||
],
|
||||
webob.exc.HTTPConflict: [
|
||||
],
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _extract_required_params(self, params, model_name):
|
||||
params = params or {}
|
||||
model_params = params.get(model_name, {})
|
||||
return utils.stringify_keys(utils.exclude(model_params,
|
||||
*self.exclude_attr))
|
||||
|
||||
|
||||
class InstanceController(BaseController):
|
||||
"""Controller for instance functionality"""
|
||||
@ -67,10 +89,11 @@ class InstanceController(BaseController):
|
||||
context = rd_context.ReddwarfContext(
|
||||
auth_tok=req.headers["X-Auth-Token"],
|
||||
tenant=tenant_id)
|
||||
# TODO(cp16net) : need to handle exceptions here if the delete fails
|
||||
models.Instance.delete(context=context, uuid=id)
|
||||
|
||||
# TODO(hub-cap): fixgure out why the result is coming back as None
|
||||
LOG.info("result of delete %s" % result)
|
||||
# LOG.info("result of delete %s" % result)
|
||||
# TODO(cp16net): need to set the return code correctly
|
||||
return wsgi.Result(202)
|
||||
|
||||
@ -87,15 +110,17 @@ class InstanceController(BaseController):
|
||||
# code. Or maybe we shouldnt due to the nature of changing images.
|
||||
# This needs discussion.
|
||||
# TODO(hub-cap): turn this into middleware
|
||||
LOG.info("Creating a database instance for tenant '%s'" % tenant_id)
|
||||
LOG.info("req : '%s'\n\n" % req)
|
||||
LOG.info("body : '%s'\n\n" % body)
|
||||
context = rd_context.ReddwarfContext(
|
||||
auth_tok=req.headers["X-Auth-Token"],
|
||||
tenant=tenant_id)
|
||||
database = models.ServiceImage.find_by(service_name="database")
|
||||
image_id = database['image_id']
|
||||
server = models.Instance.create(context,
|
||||
body['name'],
|
||||
image_id,
|
||||
body['flavor']).data()
|
||||
body).data()
|
||||
|
||||
# Now wait for the response from the create to do additional work
|
||||
#TODO(cp16net): need to set the return code correctly
|
||||
|
@ -21,16 +21,26 @@ class InstanceView(object):
|
||||
def __init__(self, instance):
|
||||
self.instance = instance
|
||||
|
||||
#TODO(hub-cap): fix the link generation
|
||||
def data(self):
|
||||
return {"instance": {
|
||||
"id": self.instance['id'],
|
||||
"name": self.instance['name'],
|
||||
"status": self.instance['status'],
|
||||
"links": "Links will be coming in the future"
|
||||
"created": self.instance['created'],
|
||||
"updated": self.instance['updated'],
|
||||
"flavor": self.instance['flavor'],
|
||||
"links": self._build_links(self.instance['links']),
|
||||
"addresses": self.instance['addresses'],
|
||||
},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _build_links(links):
|
||||
"""Build the links for the instance"""
|
||||
for link in links:
|
||||
link['href'] = link['href'].replace('servers', 'instances')
|
||||
return links
|
||||
|
||||
|
||||
class InstancesView(object):
|
||||
|
||||
|
@ -41,9 +41,26 @@ class TestInstance(tests.BaseTest):
|
||||
self.FAKE_SERVER.name = 'my_name'
|
||||
self.FAKE_SERVER.status = 'ACTIVE'
|
||||
self.FAKE_SERVER.updated = utils.utcnow()
|
||||
self.FAKE_SERVER.created = utils.utcnow()
|
||||
self.FAKE_SERVER.id = utils.generate_uuid()
|
||||
self.FAKE_SERVER.flavor = ('http://localhost/1234/flavors/',
|
||||
'52415800-8b69-11e0-9b19-734f1195ff37')
|
||||
self.FAKE_SERVER.links = [{
|
||||
"href": "http://localhost/1234/instances/123",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://localhost/1234/instances/123",
|
||||
"rel": "bookmark"
|
||||
}]
|
||||
self.FAKE_SERVER.addresses = {
|
||||
"private": [
|
||||
{
|
||||
"addr": "10.0.0.4",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
client = self.mock.CreateMock(novaclient.v1_1.Client)
|
||||
servers = self.mock.CreateMock(novaclient.v1_1.servers.ServerManager)
|
||||
@ -64,5 +81,8 @@ class TestInstance(tests.BaseTest):
|
||||
self.assertEqual(instance['name'], self.FAKE_SERVER.name)
|
||||
self.assertEqual(instance['status'], self.FAKE_SERVER.status)
|
||||
self.assertEqual(instance['updated'], self.FAKE_SERVER.updated)
|
||||
self.assertEqual(instance['created'], self.FAKE_SERVER.created)
|
||||
self.assertEqual(instance['id'], self.FAKE_SERVER.id)
|
||||
self.assertEqual(instance['flavor'], self.FAKE_SERVER.flavor)
|
||||
self.assertEqual(instance['links'], self.FAKE_SERVER.links)
|
||||
self.assertEqual(instance['addresses'], self.FAKE_SERVER.addresses)
|
||||
|
@ -16,9 +16,12 @@
|
||||
|
||||
import mox
|
||||
import logging
|
||||
import json
|
||||
import novaclient
|
||||
|
||||
from reddwarf import tests
|
||||
from reddwarf.common import config
|
||||
from reddwarf.common import utils
|
||||
from reddwarf.common import wsgi
|
||||
from reddwarf.database import models
|
||||
from reddwarf.database import service
|
||||
@ -49,18 +52,28 @@ class DummyApp(wsgi.Router):
|
||||
class TestInstanceController(ControllerTestBase):
|
||||
|
||||
DUMMY_INSTANCE_ID = "123"
|
||||
DUMMY_INSTANCE = {"id": DUMMY_INSTANCE_ID,
|
||||
"name": "DUMMY_NAME",
|
||||
"status": "BUILD",
|
||||
"created": "createtime",
|
||||
"updated": "updatedtime",
|
||||
"flavor": {},
|
||||
"links": [],
|
||||
"addresses": {}}
|
||||
|
||||
def setUp(self):
|
||||
self.instances_path = "/tenant/instances"
|
||||
super(TestInstanceController, self).setUp()
|
||||
|
||||
def test_show(self):
|
||||
# block = factory_models.IpBlockFactory()
|
||||
instance = mox.MockAnything()
|
||||
response = self.app.get("%s/%s" % (self.instances_path,
|
||||
self.DUMMY_INSTANCE_ID),
|
||||
headers={'X-Auth-Token': '123'})
|
||||
self.assertEqual(response.status_int, 404)
|
||||
|
||||
def test_show(self):
|
||||
self.mock.StubOutWithMock(models.Instance, 'data')
|
||||
models.Instance.data().AndReturn({"id": self.DUMMY_INSTANCE_ID,
|
||||
"name": "DUMMY_NAME",
|
||||
"status": "BUILD"})
|
||||
models.Instance.data().AndReturn(self.DUMMY_INSTANCE)
|
||||
self.mock.StubOutWithMock(models.Instance, '__init__')
|
||||
models.Instance.__init__(context=mox.IgnoreArg(), uuid=mox.IgnoreArg())
|
||||
self.mock.ReplayAll()
|
||||
@ -70,3 +83,82 @@ class TestInstanceController(ControllerTestBase):
|
||||
headers={'X-Auth-Token': '123'})
|
||||
|
||||
self.assertEqual(response.status_int, 201)
|
||||
|
||||
def test_index(self):
|
||||
self.mock.StubOutWithMock(models.Instances, 'data')
|
||||
models.Instances.data().AndReturn([self.DUMMY_INSTANCE])
|
||||
self.mock.StubOutWithMock(models.Instances, '__init__')
|
||||
models.Instances.__init__(mox.IgnoreArg())
|
||||
self.mock.ReplayAll()
|
||||
response = self.app.get("%s" % (self.instances_path),
|
||||
headers={'X-Auth-Token': '123'})
|
||||
self.assertEqual(response.status_int, 201)
|
||||
|
||||
def mock_out_client_create(self):
|
||||
"""Stubs out a fake server returned from novaclient.
|
||||
This is akin to calling Client.servers.get(uuid)
|
||||
and getting the server object back."""
|
||||
self.FAKE_SERVER = self.mock.CreateMock(object)
|
||||
self.FAKE_SERVER.name = 'my_name'
|
||||
self.FAKE_SERVER.status = 'ACTIVE'
|
||||
self.FAKE_SERVER.updated = utils.utcnow()
|
||||
self.FAKE_SERVER.created = utils.utcnow()
|
||||
self.FAKE_SERVER.id = utils.generate_uuid()
|
||||
self.FAKE_SERVER.flavor = 'http://localhost/1234/flavors/1234'
|
||||
self.FAKE_SERVER.links = [{
|
||||
"href": "http://localhost/1234/instances/123",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://localhost/1234/instances/123",
|
||||
"rel": "bookmark"
|
||||
}]
|
||||
self.FAKE_SERVER.addresses = {
|
||||
"private": [
|
||||
{
|
||||
"addr": "10.0.0.4",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
client = self.mock.CreateMock(novaclient.v1_1.Client)
|
||||
servers = self.mock.CreateMock(novaclient.v1_1.servers.ServerManager)
|
||||
servers.create(mox.IgnoreArg(),
|
||||
mox.IgnoreArg(),
|
||||
mox.IgnoreArg()).AndReturn(self.FAKE_SERVER)
|
||||
client.servers = servers
|
||||
self.mock.StubOutWithMock(models.RemoteModelBase, 'get_client')
|
||||
models.RemoteModelBase.get_client(mox.IgnoreArg()).AndReturn(client)
|
||||
|
||||
def test_create(self):
|
||||
self.mock.StubOutWithMock(models.Instance, 'data')
|
||||
models.Instance.data().AndReturn(self.DUMMY_INSTANCE)
|
||||
|
||||
self.mock.StubOutWithMock(models.ServiceImage, 'find_by')
|
||||
models.ServiceImage.find_by(service_name=mox.IgnoreArg()).AndReturn(
|
||||
{'image_id': 1234})
|
||||
|
||||
self.mock_out_client_create()
|
||||
self.mock.ReplayAll()
|
||||
|
||||
body = {
|
||||
"instance": {
|
||||
"databases": [
|
||||
{
|
||||
"character_set": "utf8",
|
||||
"collate": "utf8_general_ci",
|
||||
"name": "sampledb"
|
||||
},
|
||||
{
|
||||
"name": "nextround"
|
||||
}
|
||||
],
|
||||
"flavorRef": "http://localhost/v0.1/tenant/flavors/1",
|
||||
"name": "json_rack_instance",
|
||||
}
|
||||
}
|
||||
response = self.app.post_json("%s" % (self.instances_path), body=body,
|
||||
headers={'X-Auth-Token': '123'},
|
||||
)
|
||||
self.assertEqual(response.status_int, 201)
|
||||
|
Loading…
x
Reference in New Issue
Block a user