From bf6e6e718cdc7488e2da87b21e258ccc065fe499 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Thu, 27 May 2010 23:05:26 -0700 Subject: initial commit --- nova/tests/CA/cacert.pem | 17 ++++ nova/tests/CA/private/cakey.pem | 15 +++ nova/tests/__init__.py | 27 ++++++ nova/tests/access_unittest.py | 60 ++++++++++++ nova/tests/api_integration.py | 50 ++++++++++ nova/tests/api_unittest.py | 189 ++++++++++++++++++++++++++++++++++++ nova/tests/bundle/1mb.manifest.xml | 1 + nova/tests/bundle/1mb.part.0 | Bin 0 -> 1024 bytes nova/tests/bundle/1mb.part.1 | 1 + nova/tests/cloud_unittest.py | 161 +++++++++++++++++++++++++++++++ nova/tests/datastore_unittest.py | 60 ++++++++++++ nova/tests/fake_flags.py | 26 +++++ nova/tests/future_unittest.py | 74 +++++++++++++++ nova/tests/keeper_unittest.py | 57 +++++++++++ nova/tests/network_unittest.py | 113 ++++++++++++++++++++++ nova/tests/node_unittest.py | 128 +++++++++++++++++++++++++ nova/tests/objectstore_unittest.py | 190 +++++++++++++++++++++++++++++++++++++ nova/tests/real_flags.py | 24 +++++ nova/tests/storage_unittest.py | 86 +++++++++++++++++ nova/tests/users_unittest.py | 137 ++++++++++++++++++++++++++ 20 files changed, 1416 insertions(+) create mode 100644 nova/tests/CA/cacert.pem create mode 100644 nova/tests/CA/private/cakey.pem create mode 100644 nova/tests/__init__.py create mode 100644 nova/tests/access_unittest.py create mode 100644 nova/tests/api_integration.py create mode 100644 nova/tests/api_unittest.py create mode 100644 nova/tests/bundle/1mb.manifest.xml create mode 100644 nova/tests/bundle/1mb.part.0 create mode 100644 nova/tests/bundle/1mb.part.1 create mode 100644 nova/tests/cloud_unittest.py create mode 100644 nova/tests/datastore_unittest.py create mode 100644 nova/tests/fake_flags.py create mode 100644 nova/tests/future_unittest.py create mode 100644 nova/tests/keeper_unittest.py create mode 100644 nova/tests/network_unittest.py create mode 100644 nova/tests/node_unittest.py create mode 100644 nova/tests/objectstore_unittest.py create mode 100644 nova/tests/real_flags.py create mode 100644 nova/tests/storage_unittest.py create mode 100644 nova/tests/users_unittest.py (limited to 'nova/tests') diff --git a/nova/tests/CA/cacert.pem b/nova/tests/CA/cacert.pem new file mode 100644 index 000000000..9ffb5bb80 --- /dev/null +++ b/nova/tests/CA/cacert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICyzCCAjSgAwIBAgIJANiqHZUcbScCMA0GCSqGSIb3DQEBBAUAME4xEjAQBgNV +BAoTCU5PVkEgUk9PVDEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzETMBEGA1UECBMK +Q2FsaWZvcm5pYTELMAkGA1UEBhMCVVMwHhcNMTAwNTI4MDExOTI1WhcNMTEwNTI4 +MDExOTI1WjBOMRIwEAYDVQQKEwlOT1ZBIFJPT1QxFjAUBgNVBAcTDU1vdW50YWlu +IFZpZXcxEzARBgNVBAgTCkNhbGlmb3JuaWExCzAJBgNVBAYTAlVTMIGfMA0GCSqG +SIb3DQEBAQUAA4GNADCBiQKBgQDobUnq8rpXA/HQZ2Uu9Me3SlqCayz3ws2wtvFQ +koWPUzpriIYPkpprz2EaVu07Zb9uJHvjcoY07nYntl4jR8S7PH4XZhlVFn8AQWzs +iThU4KJF71UfVM00dDrarSgVpyOIcFXO3iUvLoJj7+RUPjrWdLuJoMqnhicgLeHZ +LAZ8ewIDAQABo4GwMIGtMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFMh1RMlTVtt8 +EdESYpsTU08r0FnpMH4GA1UdIwR3MHWAFMh1RMlTVtt8EdESYpsTU08r0FnpoVKk +UDBOMRIwEAYDVQQKEwlOT1ZBIFJPT1QxFjAUBgNVBAcTDU1vdW50YWluIFZpZXcx +EzARBgNVBAgTCkNhbGlmb3JuaWExCzAJBgNVBAYTAlVTggkA2KodlRxtJwIwDQYJ +KoZIhvcNAQEEBQADgYEAq+YCgflK36HCdodNu2ya3O6UDRUE2dW8n96tAOmvHqmR +v38k8GIW0pjWDo+lZYnFmeJYd+QGcJl9fLzXxffV5k+rNCfr/gEYtznWLNUX7AZB +b/VC7L+yK9qz08C8n51TslXaf3fUGkfkQxsvEP7+hi0qavdd/8eTbdheWahYwWg= +-----END CERTIFICATE----- diff --git a/nova/tests/CA/private/cakey.pem b/nova/tests/CA/private/cakey.pem new file mode 100644 index 000000000..eee54cc38 --- /dev/null +++ b/nova/tests/CA/private/cakey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDobUnq8rpXA/HQZ2Uu9Me3SlqCayz3ws2wtvFQkoWPUzpriIYP +kpprz2EaVu07Zb9uJHvjcoY07nYntl4jR8S7PH4XZhlVFn8AQWzsiThU4KJF71Uf +VM00dDrarSgVpyOIcFXO3iUvLoJj7+RUPjrWdLuJoMqnhicgLeHZLAZ8ewIDAQAB +AoGBANQonmZ2Nh2jniFrn/LiwULP/ho6Fov6J6N8+n1focaYZCUwM58XZRmv7KUM +X/PuBnVVnDibm2HJodTSJM/zfODnGO15kdmJ9X23FkkdTyuvphO5tYF0ONARXdfX +9LbPcLYA14VSCZCKCye6mbv/xi0C/s7q6ZBoMl7XaeD9hgUxAkEA9lxQY/ZxcLV0 +Ae5I2spBbtuXEGns11YnKnppc59RrAono1gaDeYY2WZRwztIcD6VtUv7qkzH6ubo +shAG4fvnPQJBAPGFaDODs2ckPvxnILEbjpnZXGQqDCpQ3sVJ6nfu+qdAWS92ESNo +Y6DC8zFjFaQFbKy6Jxr1VsvYDXhF8cmy7hcCQHkLElSLGWGPRdhNA268QTn+mlJu +OPf0VHoCex1cAfzNYHxZJTP/AeaO501NK2I63cOd+aDK6M75dQtH5JnT8uECQQCg +jVydkhk6oV+1jiCvW3BKWbIPa9w2bRgJ8n8JRzYc5Kvk3wm5jfVcsvvTgtip9mkt +0XmZdCpEy9T4dRasTGP1AkBMhShiVP7+P+SIQlZtSn8ckTt9G6cefEjxsv0kVFZe +SjkUO0ZifahF8r3Q1eEUSzdXEvicEwONvcpc7MLwfSD7 +-----END RSA PRIVATE KEY----- diff --git a/nova/tests/__init__.py b/nova/tests/__init__.py new file mode 100644 index 000000000..a4ccbbaeb --- /dev/null +++ b/nova/tests/__init__.py @@ -0,0 +1,27 @@ +# Copyright [2010] [Anso Labs, LLC] +# +# 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. + +""" +:mod:`nova.tests` -- Nova Unittests +===================================================== + +.. automodule:: nova.tests + :platform: Unix +.. moduleauthor:: Jesse Andrews +.. moduleauthor:: Devin Carlen +.. moduleauthor:: Vishvananda Ishaya +.. moduleauthor:: Joshua McKenty +.. moduleauthor:: Manish Singh +.. moduleauthor:: Andy Smith +""" \ No newline at end of file diff --git a/nova/tests/access_unittest.py b/nova/tests/access_unittest.py new file mode 100644 index 000000000..ab0759c2d --- /dev/null +++ b/nova/tests/access_unittest.py @@ -0,0 +1,60 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright [2010] [Anso Labs, LLC] +# +# 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. + +import logging +import os +import unittest + +from nova import flags +from nova import test +from nova.auth import users +from nova.endpoint import cloud + +FLAGS = flags.FLAGS + +class AccessTestCase(test.BaseTestCase): + def setUp(self): + FLAGS.fake_libvirt = True + FLAGS.fake_storage = True + self.users = users.UserManager.instance() + super(AccessTestCase, self).setUp() + # Make a test project + # Make a test user + self.users.create_user('test1', 'access', 'secret') + + # Make the test user a member of the project + + def tearDown(self): + # Delete the test user + # Delete the test project + self.users.delete_user('test1') + pass + + def test_001_basic_user_access(self): + user = self.users.get_user('test1') + # instance-foo, should be using object and not owner_id + instance_id = "i-12345678" + self.assertTrue(user.is_authorized(instance_id, action="describe_instances")) + + def test_002_sysadmin_access(self): + user = self.users.get_user('test1') + bucket = "foo/bar/image" + self.assertFalse(user.is_authorized(bucket, action="register")) + self.users.add_role(user, "sysadmin") + + +if __name__ == "__main__": + # TODO: Implement use_fake as an option + unittest.main() diff --git a/nova/tests/api_integration.py b/nova/tests/api_integration.py new file mode 100644 index 000000000..d2e1026b8 --- /dev/null +++ b/nova/tests/api_integration.py @@ -0,0 +1,50 @@ +# Copyright [2010] [Anso Labs, LLC] +# +# 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. + +import unittest + +import boto +from boto.ec2.regioninfo import RegionInfo + +ACCESS_KEY = 'fake' +SECRET_KEY = 'fake' +CLC_IP = '127.0.0.1' +CLC_PORT = 8773 +REGION = 'test' + +def get_connection(): + return boto.connect_ec2 ( + aws_access_key_id=ACCESS_KEY, + aws_secret_access_key=SECRET_KEY, + is_secure=False, + region=RegionInfo(None, REGION, CLC_IP), + port=CLC_PORT, + path='/services/Cloud', + debug=99 + ) + +class APIIntegrationTests(unittest.TestCase): + def test_001_get_all_images(self): + conn = get_connection() + res = conn.get_all_images() + print res + + +if __name__ == '__main__': + unittest.main() + +#print conn.get_all_key_pairs() +#print conn.create_key_pair +#print conn.create_security_group('name', 'description') + diff --git a/nova/tests/api_unittest.py b/nova/tests/api_unittest.py new file mode 100644 index 000000000..fdbf088f9 --- /dev/null +++ b/nova/tests/api_unittest.py @@ -0,0 +1,189 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright [2010] [Anso Labs, LLC] +# +# 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. + +import httplib +import random +import StringIO + +from nova import vendor +import boto +from boto.ec2 import regioninfo +from tornado import httpserver +from twisted.internet import defer + +from nova import flags +from nova import test +from nova.auth import users +from nova.endpoint import api +from nova.endpoint import cloud + + +FLAGS = flags.FLAGS + + +# NOTE(termie): These are a bunch of helper methods and classes to short +# circuit boto calls and feed them into our tornado handlers, +# it's pretty damn circuitous so apologies if you have to fix +# a bug in it +def boto_to_tornado(method, path, headers, data, host, connection=None): + """ translate boto requests into tornado requests + + connection should be a FakeTornadoHttpConnection instance + """ + headers = httpserver.HTTPHeaders() + for k, v in headers.iteritems(): + headers[k] = v + + req = httpserver.HTTPRequest(method=method, + uri=path, + headers=headers, + body=data, + host=host, + remote_ip='127.0.0.1', + connection=connection) + return req + + +def raw_to_httpresponse(s): + """ translate a raw tornado http response into an httplib.HTTPResponse """ + sock = FakeHttplibSocket(s) + resp = httplib.HTTPResponse(sock) + resp.begin() + return resp + + +class FakeHttplibSocket(object): + """ a fake socket implementation for httplib.HTTPResponse, trivial """ + def __init__(self, s): + self.fp = StringIO.StringIO(s) + + def makefile(self, mode, other): + return self.fp + + +class FakeTornadoStream(object): + """ a fake stream to satisfy tornado's assumptions, trivial """ + def set_close_callback(self, f): + pass + + +class FakeTornadoConnection(object): + """ a fake connection object for tornado to pass to its handlers + + web requests are expected to write to this as they get data and call + finish when they are done with the request, we buffer the writes and + kick off a callback when it is done so that we can feed the result back + into boto. + """ + def __init__(self, d): + self.d = d + self._buffer = StringIO.StringIO() + + def write(self, chunk): + self._buffer.write(chunk) + + def finish(self): + s = self._buffer.getvalue() + self.d.callback(s) + + xheaders = None + + @property + def stream(self): + return FakeTornadoStream() + + +class FakeHttplibConnection(object): + """ a fake httplib.HTTPConnection for boto to use + + requests made via this connection actually get translated and routed into + our tornado app, we then wait for the response and turn it back into + the httplib.HTTPResponse that boto expects. + """ + def __init__(self, app, host, is_secure=False): + self.app = app + self.host = host + self.deferred = defer.Deferred() + + def request(self, method, path, data, headers): + req = boto_to_tornado + conn = FakeTornadoConnection(self.deferred) + request = boto_to_tornado(connection=conn, + method=method, + path=path, + headers=headers, + data=data, + host=self.host) + handler = self.app(request) + self.deferred.addCallback(raw_to_httpresponse) + + def getresponse(self): + @defer.inlineCallbacks + def _waiter(): + result = yield self.deferred + defer.returnValue(result) + d = _waiter() + # NOTE(termie): defer.returnValue above should ensure that + # this deferred has already been called by the time + # we get here, we are going to cheat and return + # the result of the callback + return d.result + + def close(self): + pass + + +class ApiEc2TestCase(test.BaseTestCase): + def setUp(self): + super(ApiEc2TestCase, self).setUp() + + self.users = users.UserManager.instance() + self.cloud = cloud.CloudController() + + self.host = '127.0.0.1' + + self.app = api.APIServerApplication(self.users, {'Cloud': self.cloud}) + self.ec2 = boto.connect_ec2( + aws_access_key_id='fake', + aws_secret_access_key='fake', + is_secure=False, + region=regioninfo.RegionInfo(None, 'test', self.host), + port=FLAGS.cc_port, + path='/services/Cloud') + + self.mox.StubOutWithMock(self.ec2, 'new_http_connection') + + def expect_http(self, host=None, is_secure=False): + http = FakeHttplibConnection( + self.app, '%s:%d' % (self.host, FLAGS.cc_port), False) + self.ec2.new_http_connection(host, is_secure).AndReturn(http) + return http + + def test_describe_instances(self): + self.expect_http() + self.mox.ReplayAll() + + self.assertEqual(self.ec2.get_all_instances(), []) + + + def test_get_all_key_pairs(self): + self.expect_http() + self.mox.ReplayAll() + keyname = "".join(random.choice("sdiuisudfsdcnpaqwertasd") for x in range(random.randint(4, 8))) + self.users.generate_key_pair('fake', keyname) + + rv = self.ec2.get_all_key_pairs() + self.assertTrue(filter(lambda k: k.name == keyname, rv)) + diff --git a/nova/tests/bundle/1mb.manifest.xml b/nova/tests/bundle/1mb.manifest.xml new file mode 100644 index 000000000..dc3315957 --- /dev/null +++ b/nova/tests/bundle/1mb.manifest.xml @@ -0,0 +1 @@ +2007-10-10euca-tools1.231337x86_641mb42machineda39a3ee5e6b4b0d3255bfef95601890afd807091048576113633a2ea00dc64083dd9a10eb5e233635b42a7beb1670ab75452087d9de74c60aba1cd27c136fda56f62beb581de128fb1f10d072b9e556fd25e903107a57827c21f6ee8a93a4ff55b11311fcef217e3eefb07e81f71e88216f43b4b54029c1f2549f2925a839a73947d2d5aeecec4a62ece4af9156d557ae907978298296d99154c11147fd8caf92447e90ce339928933d7579244c2f8ffb07cc0ea35f8738da8b90eff6c7a49671a84500e993e9462e4c36d5c19c0b3a2b397d035b4c0cce742b58e12552175d81d129b0425e9f71ebacb9aeb539fa9dd2ac36749fb82876f6902e5fb24b6ec19f35ec4c20acd50437fd30966e99c4d9a0647577970a8fa302314bd082c9715f071160c69bbfb070f51d2ba1076775f1d988ccde150e515088156b248e4b5a64e46c4fe064feeeedfe14511f7fde478a51acb89f9b2f6c84b60593e5c3f792ba6b01fed9bf2158fdac03086374883b39d13a3ca74497eeaaf579fc3f26effc73bfd9446a2a8c4061f0874bfaca058905180e22d3d8881551cb38f7606f19f00e4e19535dd234b66b31b77e9c7bad3885d9c9efa75c863631fd4f82a009e17d789066d9cc6032a436f05384832f6d9a3283d3e63eab04fa0da5c8c87db9b17e854e842c3fb416507d067a266b44538125ce732e486098e8ebd1ca91fa3079f007fce7d14957a9b7e57282407ead3c6eb68fe975df3d83190021b1mb.part.0c4413423cf7a57e71187e19bfd5cd4b514a642831mb.part.19d4262e6589393d09a11a0332af169887bc2e57d4e00b5ba28114dda4a9df7eeae94be847ec46117a09a1cbe41e578660642f0660dda1776b39fb3bf826b6cfec019e2a5e9c566728d186b7400ebc989a30670eb1db26ce01e68bd9d3f31290370077a85b81c66b63c1e0d5499bac115c06c17a21a81b6d3a67ebbce6c17019095af7ab07f3796c708cc843e58efc12ddc788c5e \ No newline at end of file diff --git a/nova/tests/bundle/1mb.part.0 b/nova/tests/bundle/1mb.part.0 new file mode 100644 index 000000000..15a1657c5 Binary files /dev/null and b/nova/tests/bundle/1mb.part.0 differ diff --git a/nova/tests/bundle/1mb.part.1 b/nova/tests/bundle/1mb.part.1 new file mode 100644 index 000000000..2f0406e2d --- /dev/null +++ b/nova/tests/bundle/1mb.part.1 @@ -0,0 +1 @@ +­´ˆà«€ç‰°Ƴ ¡ÀiDHW̽×JÈ8ïrV¼³h§X’·@Yj“~Ø ·Gû5û 3Nt«˜•H6Ñ$§Ëgö™é Lá¢+³æ¤X†pm¬@,øŽ>7ÚÊ×užp¼ aü`¥V2X@£#á¶ \ No newline at end of file diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py new file mode 100644 index 000000000..568a8dcd3 --- /dev/null +++ b/nova/tests/cloud_unittest.py @@ -0,0 +1,161 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright [2010] [Anso Labs, LLC] +# +# 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. + +import logging +import StringIO +import time +import unittest +from xml.etree import ElementTree + +from nova import vendor +import mox +from tornado import ioloop +from twisted.internet import defer + +from nova import flags +from nova import rpc +from nova import test +from nova.auth import users +from nova.compute import node +from nova.endpoint import api +from nova.endpoint import cloud + + +FLAGS = flags.FLAGS + + +class CloudTestCase(test.BaseTestCase): + def setUp(self): + super(CloudTestCase, self).setUp() + self.flags(fake_libvirt=True, + fake_storage=True, + fake_users=True, + redis_db=8) + + self.conn = rpc.Connection.instance() + logging.getLogger().setLevel(logging.DEBUG) + + # set up our cloud + self.cloud = cloud.CloudController() + self.cloud_consumer = rpc.AdapterConsumer(connection=self.conn, + topic=FLAGS.cloud_topic, + proxy=self.cloud) + self.injected.append(self.cloud_consumer.attach_to_tornado(self.ioloop)) + + # set up a node + self.node = node.Node() + self.node_consumer = rpc.AdapterConsumer(connection=self.conn, + topic=FLAGS.compute_topic, + proxy=self.node) + self.injected.append(self.node_consumer.attach_to_tornado(self.ioloop)) + + user_mocker = mox.Mox() + self.admin = user_mocker.CreateMock(users.User) + self.admin.is_authorized(mox.IgnoreArg()).AndReturn(True) + self.context = api.APIRequestContext(handler=None,user=self.admin) + + def test_console_output(self): + if FLAGS.fake_libvirt: + logging.debug("Can't test instances without a real virtual env.") + return + instance_id = 'foo' + inst = yield self.node.run_instance(instance_id) + output = yield self.cloud.get_console_output(self.context, [instance_id]) + logging.debug(output) + self.assert_(output) + rv = yield self.node.terminate_instance(instance_id) + + def test_run_instances(self): + if FLAGS.fake_libvirt: + logging.debug("Can't test instances without a real virtual env.") + return + image_id = FLAGS.default_image + instance_type = FLAGS.default_instance_type + max_count = 1 + kwargs = {'image_id': image_id, + 'instance_type': instance_type, + 'max_count': max_count} + rv = yield self.cloud.run_instances(self.context, **kwargs) + # TODO: check for proper response + instance = rv['reservationSet'][0][rv['reservationSet'][0].keys()[0]][0] + logging.debug("Need to watch instance %s until it's running..." % instance['instance_id']) + while True: + rv = yield defer.succeed(time.sleep(1)) + info = self.cloud._get_instance(instance['instance_id']) + logging.debug(info['state']) + if info['state'] == node.Instance.RUNNING: + break + self.assert_(rv) + + if not FLAGS.fake_libvirt: + time.sleep(45) # Should use boto for polling here + for reservations in rv['reservationSet']: + # for res_id in reservations.keys(): + # logging.debug(reservations[res_id]) + # for instance in reservations[res_id]: + for instance in reservations[reservations.keys()[0]]: + logging.debug("Terminating instance %s" % instance['instance_id']) + rv = yield self.node.terminate_instance(instance['instance_id']) + + def test_instance_update_state(self): + def instance(num): + return { + 'reservation_id': 'r-1', + 'instance_id': 'i-%s' % num, + 'image_id': 'ami-%s' % num, + 'private_dns_name': '10.0.0.%s' % num, + 'dns_name': '10.0.0%s' % num, + 'ami_launch_index': str(num), + 'instance_type': 'fake', + 'availability_zone': 'fake', + 'key_name': None, + 'kernel_id': 'fake', + 'ramdisk_id': 'fake', + 'groups': ['default'], + 'product_codes': None, + 'state': 0x01, + 'user_data': '' + } + + rv = self.cloud.format_instances(self.admin) + print rv + self.assert_(len(rv['reservationSet']) == 0) + + # simulate launch of 5 instances + # self.cloud.instances['pending'] = {} + #for i in xrange(5): + # inst = instance(i) + # self.cloud.instances['pending'][inst['instance_id']] = inst + + #rv = self.cloud.format_instances(self.admin) + #self.assert_(len(rv['reservationSet']) == 1) + #self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5) + + # report 4 nodes each having 1 of the instances + #for i in xrange(4): + # self.cloud.update_state('instances', {('node-%s' % i): {('i-%s' % i): instance(i)}}) + + # one instance should be pending still + #self.assert_(len(self.cloud.instances['pending'].keys()) == 1) + + # check that the reservations collapse + #rv = self.cloud.format_instances(self.admin) + #self.assert_(len(rv['reservationSet']) == 1) + #self.assert_(len(rv['reservationSet'][0]['instances_set']) == 5) + + # check that we can get metadata for each instance + #for i in xrange(4): + # data = self.cloud.get_metadata(instance(i)['private_dns_name']) + # self.assert_(data['meta-data']['ami-id'] == 'ami-%s' % i) diff --git a/nova/tests/datastore_unittest.py b/nova/tests/datastore_unittest.py new file mode 100644 index 000000000..4e4d8586a --- /dev/null +++ b/nova/tests/datastore_unittest.py @@ -0,0 +1,60 @@ +from nova import test +from nova import datastore +import random + +class KeeperTestCase(test.BaseTestCase): + """ + Basic persistence tests for Keeper datastore. + Generalize, then use these to support + migration to redis / cassandra / multiple stores. + """ + + def __init__(self, *args, **kwargs): + """ + Create a new keeper instance for test keys. + """ + super(KeeperTestCase, self).__init__(*args, **kwargs) + self.keeper = datastore.Keeper('test-') + + def tear_down(self): + """ + Scrub out test keeper data. + """ + pass + + def test_store_strings(self): + """ + Confirm that simple strings go in and come out safely. + Should also test unicode strings. + """ + randomstring = ''.join( + [random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-') + for _x in xrange(20)] + ) + self.keeper['test_string'] = randomstring + self.assertEqual(randomstring, self.keeper['test_string']) + + def test_store_dicts(self): + """ + Arbitrary dictionaries should be storable. + """ + test_dict = {'key_one': 'value_one'} + self.keeper['test_dict'] = test_dict + self.assertEqual(test_dict['key_one'], + self.keeper['test_dict']['key_one']) + + def test_sets(self): + """ + A keeper dict should be self-serializing. + """ + self.keeper.set_add('test_set', 'foo') + test_dict = {'arbitrary': 'dict of stuff'} + self.keeper.set_add('test_set', test_dict) + self.assertTrue(self.keeper.set_is_member('test_set', 'foo')) + self.assertFalse(self.keeper.set_is_member('test_set', 'bar')) + self.keeper.set_remove('test_set', 'foo') + self.assertFalse(self.keeper.set_is_member('test_set', 'foo')) + rv = self.keeper.set_fetch('test_set') + self.assertEqual(test_dict, rv.next()) + self.keeper.set_remove('test_set', test_dict) + diff --git a/nova/tests/fake_flags.py b/nova/tests/fake_flags.py new file mode 100644 index 000000000..3c7b0be52 --- /dev/null +++ b/nova/tests/fake_flags.py @@ -0,0 +1,26 @@ +# Copyright [2010] [Anso Labs, LLC] +# +# 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. + +from nova import flags + +FLAGS = flags.FLAGS + +FLAGS.fake_libvirt = True +FLAGS.fake_storage = True +FLAGS.fake_rabbit = True +FLAGS.fake_network = True +FLAGS.fake_users = True +FLAGS.keeper_backend = 'sqlite' +FLAGS.datastore_path = ':memory:' +FLAGS.verbose = True diff --git a/nova/tests/future_unittest.py b/nova/tests/future_unittest.py new file mode 100644 index 000000000..81d69dfff --- /dev/null +++ b/nova/tests/future_unittest.py @@ -0,0 +1,74 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright [2010] [Anso Labs, LLC] +# +# 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. + +import logging +import StringIO +import time +import unittest +from xml.etree import ElementTree + +from nova import vendor +import mox +from tornado import ioloop +from twisted.internet import defer + +from nova import cloud +from nova import exception +from nova import flags +from nova import node +from nova import rpc +from nova import test + + +FLAGS = flags.FLAGS + + +class AdminTestCase(test.BaseTestCase): + def setUp(self): + super(AdminTestCase, self).setUp() + self.flags(fake_libvirt=True, + fake_rabbit=True) + + self.conn = rpc.Connection.instance() + + logging.getLogger().setLevel(logging.INFO) + + # set up our cloud + self.cloud = cloud.CloudController() + self.cloud_consumer = rpc.AdapterConsumer(connection=self.conn, + topic=FLAGS.cloud_topic, + proxy=self.cloud) + self.injected.append(self.cloud_consumer.attach_to_tornado(self.ioloop)) + + # set up a node + self.node = node.Node() + self.node_consumer = rpc.AdapterConsumer(connection=self.conn, + topic=FLAGS.compute_topic, + proxy=self.node) + self.injected.append(self.node_consumer.attach_to_tornado(self.ioloop)) + + def test_flush_terminated(self): + # Launch an instance + + # Wait until it's running + + # Terminate it + + # Wait until it's terminated + + # Flush terminated nodes + + # ASSERT that it's gone + pass diff --git a/nova/tests/keeper_unittest.py b/nova/tests/keeper_unittest.py new file mode 100644 index 000000000..3896c9e57 --- /dev/null +++ b/nova/tests/keeper_unittest.py @@ -0,0 +1,57 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +import random + +from nova import datastore +from nova import test + +class KeeperTestCase(test.TrialTestCase): + """ + Basic persistence tests for Keeper datastore. + Generalize, then use these to support + migration to redis / cassandra / multiple stores. + """ + + def setUp(self): + super(KeeperTestCase, self).setUp() + self.keeper = datastore.Keeper('test') + + def tearDown(self): + super(KeeperTestCase, self).tearDown() + self.keeper.clear() + + def test_store_strings(self): + """ + Confirm that simple strings go in and come out safely. + Should also test unicode strings. + """ + randomstring = ''.join( + [random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-') + for _x in xrange(20)] + ) + self.keeper['test_string'] = randomstring + self.assertEqual(randomstring, self.keeper['test_string']) + + def test_store_dicts(self): + """ + Arbitrary dictionaries should be storable. + """ + test_dict = {'key_one': 'value_one'} + self.keeper['test_dict'] = test_dict + self.assertEqual(test_dict['key_one'], + self.keeper['test_dict']['key_one']) + + def test_sets(self): + """ + A keeper dict should be self-serializing. + """ + self.keeper.set_add('test_set', 'foo') + test_dict = {'arbitrary': 'dict of stuff'} + self.keeper.set_add('test_set', test_dict) + self.assertTrue(self.keeper.set_is_member('test_set', 'foo')) + self.assertFalse(self.keeper.set_is_member('test_set', 'bar')) + self.keeper.set_remove('test_set', 'foo') + self.assertFalse(self.keeper.set_is_member('test_set', 'foo')) + rv = self.keeper.set_fetch('test_set') + self.assertEqual(test_dict, rv.next()) + self.keeper.set_remove('test_set', test_dict) + diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py new file mode 100644 index 000000000..43c7831a7 --- /dev/null +++ b/nova/tests/network_unittest.py @@ -0,0 +1,113 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright [2010] [Anso Labs, LLC] +# +# 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. + +import logging +import unittest + +from nova import vendor +import IPy + +from nova import flags +from nova import test +from nova.compute import network +from nova.auth import users + + +class NetworkTestCase(test.TrialTestCase): + def setUp(self): + super(NetworkTestCase, self).setUp() + logging.getLogger().setLevel(logging.DEBUG) + self.manager = users.UserManager.instance() + for i in range(0, 6): + name = 'user%s' % i + if not self.manager.get_user(name): + self.manager.create_user(name, name, name) + self.network = network.NetworkController(netsize=16) + + def tearDown(self): + super(NetworkTestCase, self).tearDown() + for i in range(0, 6): + name = 'user%s' % i + self.manager.delete_user(name) + + def test_network_serialization(self): + net1 = network.Network(vlan=100, network="192.168.100.0/24", conn=None) + address = net1.allocate_ip("user0", "01:24:55:36:f2:a0") + net_json = str(net1) + net2 = network.Network.from_json(net_json) + self.assertEqual(net_json, str(net2)) + self.assertTrue(IPy.IP(address) in net2.network) + + def test_allocate_deallocate_address(self): + for flag in flags.FLAGS: + print "%s=%s" % (flag, flags.FLAGS.get(flag, None)) + (address, net_name) = self.network.allocate_address( + "user0", "01:24:55:36:f2:a0") + logging.debug("Was allocated %s" % (address)) + self.assertEqual(True, address in self._get_user_addresses("user0")) + rv = self.network.deallocate_address(address) + self.assertEqual(False, address in self._get_user_addresses("user0")) + + def test_range_allocation(self): + (address, net_name) = self.network.allocate_address( + "user0", "01:24:55:36:f2:a0") + (secondaddress, net_name) = self.network.allocate_address( + "user1", "01:24:55:36:f2:a0") + self.assertEqual(True, address in self._get_user_addresses("user0")) + self.assertEqual(True, + secondaddress in self._get_user_addresses("user1")) + self.assertEqual(False, address in self._get_user_addresses("user1")) + rv = self.network.deallocate_address(address) + self.assertEqual(False, address in self._get_user_addresses("user0")) + rv = self.network.deallocate_address(secondaddress) + self.assertEqual(False, + secondaddress in self._get_user_addresses("user1")) + + def test_subnet_edge(self): + (secondaddress, net_name) = self.network.allocate_address("user0") + for user in range(1,5): + user_id = "user%s" % (user) + (address, net_name) = self.network.allocate_address( + user_id, "01:24:55:36:f2:a0") + (address2, net_name) = self.network.allocate_address( + user_id, "01:24:55:36:f2:a0") + (address3, net_name) = self.network.allocate_address( + user_id, "01:24:55:36:f2:a0") + self.assertEqual(False, + address in self._get_user_addresses("user0")) + self.assertEqual(False, + address2 in self._get_user_addresses("user0")) + self.assertEqual(False, + address3 in self._get_user_addresses("user0")) + rv = self.network.deallocate_address(address) + rv = self.network.deallocate_address(address2) + rv = self.network.deallocate_address(address3) + rv = self.network.deallocate_address(secondaddress) + + def test_too_many_users(self): + for i in range(0, 30): + name = 'toomany-user%s' % i + self.manager.create_user(name, name, name) + (address, net_name) = self.network.allocate_address( + name, "01:24:55:36:f2:a0") + self.manager.delete_user(name) + + def _get_user_addresses(self, user_id): + rv = self.network.describe_addresses() + user_addresses = [] + for item in rv: + if item['user_id'] == user_id: + user_addresses.append(item['address']) + return user_addresses diff --git a/nova/tests/node_unittest.py b/nova/tests/node_unittest.py new file mode 100644 index 000000000..7a6115fcc --- /dev/null +++ b/nova/tests/node_unittest.py @@ -0,0 +1,128 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright [2010] [Anso Labs, LLC] +# +# 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. + +import logging +import StringIO +import time +import unittest +from xml.etree import ElementTree + +from nova import vendor +import mox +from tornado import ioloop +from twisted.internet import defer + +from nova import exception +from nova import flags +from nova import test +from nova import utils +from nova.compute import model +from nova.compute import node + +FLAGS = flags.FLAGS + + +class InstanceXmlTestCase(test.TrialTestCase): + # @defer.inlineCallbacks + def test_serialization(self): + # TODO: Reimplement this, it doesn't make sense in redis-land + return + + # instance_id = 'foo' + # first_node = node.Node() + # inst = yield first_node.run_instance(instance_id) + # + # # force the state so that we can verify that it changes + # inst._s['state'] = node.Instance.NOSTATE + # xml = inst.toXml() + # self.assert_(ElementTree.parse(StringIO.StringIO(xml))) + # + # second_node = node.Node() + # new_inst = node.Instance.fromXml(second_node._conn, pool=second_node._pool, xml=xml) + # self.assertEqual(new_inst.state, node.Instance.RUNNING) + # rv = yield first_node.terminate_instance(instance_id) + + +class NodeConnectionTestCase(test.TrialTestCase): + def setUp(self): + logging.getLogger().setLevel(logging.DEBUG) + super(NodeConnectionTestCase, self).setUp() + self.flags(fake_libvirt=True, + fake_storage=True, + fake_users=True, + redis_db=8) + self.node = node.Node() + + def create_instance(self): + instdir = model.InstanceDirectory() + inst = instdir.new() + # TODO(ja): add ami, ari, aki, user_data + inst['reservation_id'] = 'r-fakeres' + inst['launch_time'] = '10' + inst['owner_id'] = 'fake' + inst['node_name'] = FLAGS.node_name + inst['mac_address'] = utils.generate_mac() + inst['ami_launch_index'] = 0 + inst.save() + return inst['instance_id'] + + @defer.inlineCallbacks + def test_run_describe_terminate(self): + instance_id = self.create_instance() + + rv = yield self.node.run_instance(instance_id) + + rv = yield self.node.describe_instances() + self.assertEqual(rv[instance_id].name, instance_id) + + rv = yield self.node.terminate_instance(instance_id) + + rv = yield self.node.describe_instances() + self.assertEqual(rv, {}) + + @defer.inlineCallbacks + def test_reboot(self): + instance_id = self.create_instance() + rv = yield self.node.run_instance(instance_id) + + rv = yield self.node.describe_instances() + logging.debug("describe_instances returns %s" % (rv)) + self.assertEqual(rv[instance_id].name, instance_id) + + yield self.node.reboot_instance(instance_id) + + rv = yield self.node.describe_instances() + self.assertEqual(rv[instance_id].name, instance_id) + rv = yield self.node.terminate_instance(instance_id) + + @defer.inlineCallbacks + def test_console_output(self): + instance_id = self.create_instance() + rv = yield self.node.run_instance(instance_id) + + console = yield self.node.get_console_output(instance_id) + self.assert_(console) + rv = yield self.node.terminate_instance(instance_id) + + @defer.inlineCallbacks + def test_run_instance_existing(self): + instance_id = self.create_instance() + rv = yield self.node.run_instance(instance_id) + + rv = yield self.node.describe_instances() + self.assertEqual(rv[instance_id].name, instance_id) + + self.assertRaises(exception.Error, self.node.run_instance, instance_id) + rv = yield self.node.terminate_instance(instance_id) diff --git a/nova/tests/objectstore_unittest.py b/nova/tests/objectstore_unittest.py new file mode 100644 index 000000000..5f41d47a0 --- /dev/null +++ b/nova/tests/objectstore_unittest.py @@ -0,0 +1,190 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright [2010] [Anso Labs, LLC] +# +# 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. + +import glob +import hashlib +import logging +import os +import shutil +import tempfile + +from nova import vendor + +from nova import flags +from nova import rpc +from nova import objectstore +from nova import test +from nova.auth import users + +FLAGS = flags.FLAGS + + +oss_tempdir = tempfile.mkdtemp(prefix='test_oss-') + + +# delete tempdirs from previous runs (we don't delete after test to allow +# checking the contents after running tests) +for path in glob.glob(os.path.abspath(os.path.join(oss_tempdir, '../test_oss-*'))): + if path != oss_tempdir: + shutil.rmtree(path) + + +# create bucket/images path +os.makedirs(os.path.join(oss_tempdir, 'images')) +os.makedirs(os.path.join(oss_tempdir, 'buckets')) + +class ObjectStoreTestCase(test.BaseTestCase): + def setUp(self): + super(ObjectStoreTestCase, self).setUp() + self.flags(fake_users=True, + buckets_path=os.path.join(oss_tempdir, 'buckets'), + images_path=os.path.join(oss_tempdir, 'images'), + ca_path=os.path.join(os.path.dirname(__file__), 'CA')) + self.conn = rpc.Connection.instance() + logging.getLogger().setLevel(logging.DEBUG) + + self.um = users.UserManager.instance() + + def test_buckets(self): + try: + self.um.create_user('user1') + except: pass + try: + self.um.create_user('user2') + except: pass + try: + self.um.create_user('admin_user', admin=True) + except: pass + + objectstore.bucket.Bucket.create('new_bucket', self.um.get_user('user1')) + bucket = objectstore.bucket.Bucket('new_bucket') + + # creator is authorized to use bucket + self.assert_(bucket.is_authorized(self.um.get_user('user1'))) + + # another user is not authorized + self.assert_(bucket.is_authorized(self.um.get_user('user2')) == False) + + # admin is authorized to use bucket + self.assert_(bucket.is_authorized(self.um.get_user('admin_user'))) + + # new buckets are empty + self.assert_(bucket.list_keys()['Contents'] == []) + + # storing keys works + bucket['foo'] = "bar" + + self.assert_(len(bucket.list_keys()['Contents']) == 1) + + self.assert_(bucket['foo'].read() == 'bar') + + # md5 of key works + self.assert_(bucket['foo'].md5 == hashlib.md5('bar').hexdigest()) + + # deleting non-empty bucket throws exception + exception = False + try: + bucket.delete() + except: + exception = True + + self.assert_(exception) + + # deleting key + del bucket['foo'] + + # deleting empty button + bucket.delete() + + # accessing deleted bucket throws exception + exception = False + try: + objectstore.bucket.Bucket('new_bucket') + except: + exception = True + + self.assert_(exception) + self.um.delete_user('user1') + self.um.delete_user('user2') + self.um.delete_user('admin_user') + + def test_images(self): + try: + self.um.create_user('image_creator') + except: pass + image_user = self.um.get_user('image_creator') + + # create a bucket for our bundle + objectstore.bucket.Bucket.create('image_bucket', image_user) + bucket = objectstore.bucket.Bucket('image_bucket') + + # upload an image manifest/parts + bundle_path = os.path.join(os.path.dirname(__file__), 'bundle') + for path in glob.glob(bundle_path + '/*'): + bucket[os.path.basename(path)] = open(path, 'rb').read() + + # register an image + objectstore.image.Image.create('i-testing', 'image_bucket/1mb.manifest.xml', image_user) + + # verify image + my_img = objectstore.image.Image('i-testing') + result_image_file = os.path.join(my_img.path, 'image') + self.assertEqual(os.stat(result_image_file).st_size, 1048576) + + sha = hashlib.sha1(open(result_image_file).read()).hexdigest() + self.assertEqual(sha, '3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3') + + # verify image permissions + try: + self.um.create_user('new_user') + except: pass + new_user = self.um.get_user('new_user') + self.assert_(my_img.is_authorized(new_user) == False) + + self.um.delete_user('new_user') + self.um.delete_user('image_creator') + +# class ApiObjectStoreTestCase(test.BaseTestCase): +# def setUp(self): +# super(ApiObjectStoreTestCase, self).setUp() +# FLAGS.fake_users = True +# FLAGS.buckets_path = os.path.join(tempdir, 'buckets') +# FLAGS.images_path = os.path.join(tempdir, 'images') +# FLAGS.ca_path = os.path.join(os.path.dirname(__file__), 'CA') +# +# self.users = users.UserManager.instance() +# self.app = handler.Application(self.users) +# +# self.host = '127.0.0.1' +# +# self.conn = boto.s3.connection.S3Connection( +# aws_access_key_id=user.access, +# aws_secret_access_key=user.secret, +# is_secure=False, +# calling_format=boto.s3.connection.OrdinaryCallingFormat(), +# port=FLAGS.s3_port, +# host=FLAGS.s3_host) +# +# self.mox.StubOutWithMock(self.ec2, 'new_http_connection') +# +# def tearDown(self): +# FLAGS.Reset() +# super(ApiObjectStoreTestCase, self).tearDown() +# +# def test_describe_instances(self): +# self.expect_http() +# self.mox.ReplayAll() +# +# self.assertEqual(self.ec2.get_all_instances(), []) diff --git a/nova/tests/real_flags.py b/nova/tests/real_flags.py new file mode 100644 index 000000000..68fe8dc5b --- /dev/null +++ b/nova/tests/real_flags.py @@ -0,0 +1,24 @@ +# Copyright [2010] [Anso Labs, LLC] +# +# 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. + +from nova import flags + +FLAGS = flags.FLAGS + +FLAGS.fake_libvirt = False +FLAGS.fake_storage = False +FLAGS.fake_rabbit = False +FLAGS.fake_network = False +FLAGS.fake_users = False +FLAGS.verbose = False diff --git a/nova/tests/storage_unittest.py b/nova/tests/storage_unittest.py new file mode 100644 index 000000000..31966d2d5 --- /dev/null +++ b/nova/tests/storage_unittest.py @@ -0,0 +1,86 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright [2010] [Anso Labs, LLC] +# +# 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. + +import logging +import StringIO +import time +import unittest +from xml.etree import ElementTree + +from nova import vendor +import mox +from tornado import ioloop +from twisted.internet import defer + +from nova import exception +from nova import flags +from nova import test +from nova.compute import node +from nova.volume import storage + + +FLAGS = flags.FLAGS + + +class StorageTestCase(test.TrialTestCase): + def setUp(self): + logging.getLogger().setLevel(logging.DEBUG) + super(StorageTestCase, self).setUp() + self.mynode = node.Node() + self.mystorage = None + self.flags(fake_libvirt=True, + fake_storage=True, + redis_db=8) + if FLAGS.fake_storage: + self.mystorage = storage.FakeBlockStore() + else: + self.mystorage = storage.BlockStore() + + @test.skip_if_fake + def test_run_create_volume(self): + vol_size = '0' + user_id = 'fake' + volume_id = self.mystorage.create_volume(vol_size, user_id) + # rv = self.mystorage.describe_volumes() + + # Volumes have to be sorted by timestamp in order to work here... + # TODO(termie): get_volume returns differently than create_volume + self.assertEqual(volume_id, + self.mystorage.get_volume(volume_id)['volume_id']) + + rv = self.mystorage.delete_volume(volume_id) + self.assertRaises(exception.Error, + self.mystorage.get_volume, + volume_id) + + @test.skip_if_fake + def test_run_attach_detach_volume(self): + # Create one volume and one node to test with + instance_id = "storage-test" + # TODO(joshua) - Redo this test, can't make fake instances this way any more + # rv = self.mynode.run_instance(instance_id) + vol_size = "5" + user_id = "fake" + volume_id = self.mystorage.create_volume(vol_size, user_id) + rv = self.mystorage.attach_volume(volume_id, + instance_id, + "/dev/sdf") + volume_obj = self.mystorage.get_volume(volume_id) + self.assertEqual(volume_obj['status'], "attached") + # TODO(???): assert that it's attached to the right instance + + rv = self.mystorage.detach_volume(volume_id) + volume_obj = self.mystorage.get_volume(volume_id) + self.assertEqual(volume_obj['status'], "available") diff --git a/nova/tests/users_unittest.py b/nova/tests/users_unittest.py new file mode 100644 index 000000000..70f508b35 --- /dev/null +++ b/nova/tests/users_unittest.py @@ -0,0 +1,137 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright [2010] [Anso Labs, LLC] +# +# 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. + +import logging +import unittest + +from nova import vendor +from M2Crypto import BIO +from M2Crypto import RSA +from M2Crypto import X509 + +from nova import crypto +from nova import flags +from nova import test +from nova import utils +from nova.auth import users +from nova.endpoint import cloud + + +FLAGS = flags.FLAGS + + +class UserTestCase(test.BaseTestCase): + def setUp(self): + super(UserTestCase, self).setUp() + self.flags(fake_libvirt=True, + fake_storage=True, + redis_db=8) + self.users = users.UserManager.instance() + + def test_001_can_create_user(self): + self.users.create_user('test1', 'access', 'secret') + + def test_002_can_get_user(self): + user = self.users.get_user('test1') + + def test_003_can_retreive_properties(self): + user = self.users.get_user('test1') + self.assertEqual('test1', user.id) + self.assertEqual('access', user.access) + self.assertEqual('secret', user.secret) + + def test_004_signature_is_valid(self): + #self.assertTrue(self.users.authenticate( **boto.generate_url ... ? ? ? )) + pass + #raise NotImplementedError + + def test_005_can_get_credentials(self): + return + credentials = self.users.get_user('test1').get_credentials() + self.assertEqual(credentials, + 'export EC2_ACCESS_KEY="access"\n' + + 'export EC2_SECRET_KEY="secret"\n' + + 'export EC2_URL="http://127.0.0.1:8773/services/Cloud"\n' + + 'export S3_URL="http://127.0.0.1:3333/"\n' + + 'export EC2_USER_ID="test1"\n') + + def test_006_test_key_storage(self): + user = self.users.get_user('test1') + user.create_key_pair('public', 'key', 'fingerprint') + key = user.get_key_pair('public') + self.assertEqual('key', key.public_key) + self.assertEqual('fingerprint', key.fingerprint) + + def test_007_test_key_generation(self): + user = self.users.get_user('test1') + private_key, fingerprint = user.generate_key_pair('public2') + key = RSA.load_key_string(private_key, callback=lambda: None) + bio = BIO.MemoryBuffer() + public_key = user.get_key_pair('public2').public_key + key.save_pub_key_bio(bio) + converted = crypto.ssl_pub_to_ssh_pub(bio.read()) + # assert key fields are equal + print converted + self.assertEqual(public_key.split(" ")[1].strip(), + converted.split(" ")[1].strip()) + + def test_008_can_list_key_pairs(self): + keys = self.users.get_user('test1').get_key_pairs() + self.assertTrue(filter(lambda k: k.name == 'public', keys)) + self.assertTrue(filter(lambda k: k.name == 'public2', keys)) + + def test_009_can_delete_key_pair(self): + self.users.get_user('test1').delete_key_pair('public') + keys = self.users.get_user('test1').get_key_pairs() + self.assertFalse(filter(lambda k: k.name == 'public', keys)) + + def test_010_can_list_users(self): + users = self.users.get_users() + self.assertTrue(filter(lambda u: u.id == 'test1', users)) + + def test_011_can_generate_x509(self): + # MUST HAVE RUN CLOUD SETUP BY NOW + self.cloud = cloud.CloudController() + self.cloud.setup() + private_key, signed_cert_string = self.users.get_user('test1').generate_x509_cert() + logging.debug(signed_cert_string) + + # Need to verify that it's signed by the right intermediate CA + full_chain = crypto.fetch_ca(username='test1', chain=True) + int_cert = crypto.fetch_ca(username='test1', chain=False) + cloud_cert = crypto.fetch_ca() + logging.debug("CA chain:\n\n =====\n%s\n\n=====" % full_chain) + signed_cert = X509.load_cert_string(signed_cert_string) + chain_cert = X509.load_cert_string(full_chain) + int_cert = X509.load_cert_string(int_cert) + cloud_cert = X509.load_cert_string(cloud_cert) + self.assertTrue(signed_cert.verify(chain_cert.get_pubkey())) + self.assertTrue(signed_cert.verify(int_cert.get_pubkey())) + + if not FLAGS.use_intermediate_ca: + self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey())) + else: + self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey())) + + def test_012_can_delete_user(self): + self.users.delete_user('test1') + users = self.users.get_users() + if users != None: + self.assertFalse(filter(lambda u: u.id == 'test1', users)) + + +if __name__ == "__main__": + # TODO: Implement use_fake as an option + unittest.main() -- cgit