diff --git a/.gitignore b/.gitignore index ce9b613f12..4fe11bc5e8 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ guest-agent-files.txt reddwarf.egg* reddwarf/vcsversion.py *py*.egg +.coverage +covhtml/ diff --git a/development/development_enviroment.sh b/development/development_enviroment.sh index 7f69d25f35..cb08a98817 100644 --- a/development/development_enviroment.sh +++ b/development/development_enviroment.sh @@ -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 diff --git a/reddwarf/common/utils.py b/reddwarf/common/utils.py index b3412d1864..8f04053f50 100644 --- a/reddwarf/common/utils.py +++ b/reddwarf/common/utils.py @@ -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()) diff --git a/reddwarf/database/models.py b/reddwarf/database/models.py index 614ee7285b..085d18408e 100644 --- a/reddwarf/database/models.py +++ b/reddwarf/database/models.py @@ -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) diff --git a/reddwarf/database/service.py b/reddwarf/database/service.py index a72527c043..22d92c5368 100644 --- a/reddwarf/database/service.py +++ b/reddwarf/database/service.py @@ -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 diff --git a/reddwarf/database/views.py b/reddwarf/database/views.py index edb0befb12..a99e558250 100644 --- a/reddwarf/database/views.py +++ b/reddwarf/database/views.py @@ -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): diff --git a/reddwarf/tests/unit/test_database_models.py b/reddwarf/tests/unit/test_database_models.py index 67c88f0c48..6117a94ca1 100644 --- a/reddwarf/tests/unit/test_database_models.py +++ b/reddwarf/tests/unit/test_database_models.py @@ -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) diff --git a/reddwarf/tests/unit/test_database_service.py b/reddwarf/tests/unit/test_database_service.py index 7f197ff638..f7205854e2 100644 --- a/reddwarf/tests/unit/test_database_service.py +++ b/reddwarf/tests/unit/test_database_service.py @@ -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)