diff --git a/docs/source/index.rst b/docs/source/index.rst index 1594af993..305324b00 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -40,10 +40,28 @@ How to use Horizon in your own projects. intro quickstart + topics/branding +Developer Docs +============== + +For those wishing to develop Horizon itself, or go in-depth with building +your own :class:`~horizon.Dashboard` or :class:`~horizon.Panel` classes, +the following documentation is provided. + +General information +------------------- + +Brief guides to areas of interest and importance when developing Horizon. + +.. toctree:: + :maxdepth: 1 + + contributing + testing Topic Guides -============ +------------ Information on how to work with specific areas of Horizon can be found in the following topic guides. @@ -52,25 +70,7 @@ the following topic guides. :maxdepth: 1 topics/tables - -Developer Reference -=================== - -For those wishing to develop Horizon itself, or go in-depth with building -your own :class:`~horizon.Dashboard` or :class:`~horizon.Panel` classes, -the following documentation is provided. - -Topics ------- - -Brief guides to areas of interest and importance when developing Horizon. - -.. toctree:: - :maxdepth: 1 - - branding - contributing - testing + topics/testing API Reference ------------- @@ -90,6 +90,7 @@ In-depth documentation for Horizon and it's APIs. ref/context_processors ref/decorators ref/exceptions + ref/test Source Code Reference --------------------- diff --git a/docs/source/ref/test.rst b/docs/source/ref/test.rst new file mode 100644 index 000000000..ccf3f0462 --- /dev/null +++ b/docs/source/ref/test.rst @@ -0,0 +1,17 @@ +======================== +Horizon TestCase Classes +======================== + +.. module:: horizon.test + +Horizon provides several useful base classes for testing both views and +API functions. + +.. autoclass:: TestCase + :members: + +.. autoclass:: APITestCase + :members: + +.. autoclass:: BaseAdminViewTests + :members: diff --git a/docs/source/testing.rst b/docs/source/testing.rst index a03a88580..b3b150382 100644 --- a/docs/source/testing.rst +++ b/docs/source/testing.rst @@ -1,22 +1,6 @@ -.. - Copyright 2012 OpenStack, LLC - All Rights Reserved. - - 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. - -=============== -Testing Horizon -=============== +======================= +Horizon's tests and you +======================= How to run the tests ==================== @@ -41,22 +25,15 @@ To run the tests:: :doc:`ref/run_tests` Full reference for the ``run_tests.sh`` script. -How to write good tests -======================= +Writing tests +============= Horizon uses Django's unit test machinery (which extends Python's ``unittest2`` -library) as the core of it's test suite. As such, all tests for the Python code +library) as the core of its test suite. As such, all tests for the Python code should be written as unit tests. No doctests please. -A few pointers for writing good tests: - - * Write tests as you go--If you save them to the end you'll write less of - them and they'll often miss large chunks of code. - * Keep it as simple as possible--Make sure each test tests one thing - and tests it thoroughly. - * Think about all the possible inputs your code could have--It's usually - the edge cases that end up revealing bugs. - * Use ``coverage.py`` to find out what code is *not* being tested. - In general new code without unit tests will not be accepted, and every bugfix *must* include a regression test. + +For a much more in-depth discussion of testing, see the :doc:`testing topic +guide `. diff --git a/docs/source/branding.rst b/docs/source/topics/branding.rst similarity index 92% rename from docs/source/branding.rst rename to docs/source/topics/branding.rst index c62e033d8..a1ba83fff 100644 --- a/docs/source/branding.rst +++ b/docs/source/topics/branding.rst @@ -1,6 +1,7 @@ +============================== Change the branding of Horizon ------------------------------- -======================= +============================== + Changing the Page Title ======================= @@ -11,7 +12,6 @@ to ``local_settings.py`` with the value being the desired name. The file ``local_settings.py`` can be found at the Horizon directory path of ``horizon/openstack-dashboard/local/local_settings.py``. -======================= Changing the Page Logo ======================= diff --git a/docs/source/topics/testing.rst b/docs/source/topics/testing.rst new file mode 100644 index 000000000..36aada601 --- /dev/null +++ b/docs/source/topics/testing.rst @@ -0,0 +1,276 @@ +=================== +Testing Topic Guide +=================== + +Having good tests in place is absolutely critical for ensuring a stable, +maintainable codebase. Hopefully that doesn't need any more explanation. + +However, what defines a "good" test is not always obvious, and there are +a lot of common pitfalls that can easily shoot your test suite in the +foot. + +If you already know everything about testing but are fed up with trying to +debug why a specific test failed, you can skip the intro and jump +stright to :ref:`debugging_unit_tests`. + +An overview of testing +====================== + +There are three main types of tests, each with their associated pros and cons: + +Unit tests +---------- + +These are isolated, stand-alone tests with no external dependencies. They are +written from the a perspective of "knowing the code", and test the assumptions +of the codebase and the developer. + +Pros: + +* Generally lightweight and fast. +* Can be run anywhere, anytime since they have no external dependencies. + +Cons: + +* Easy to be lax in writing them, or lazy in constructing them. +* Can't test interactions with live external services. + +Functional tests +---------------- + +These are generally also isolated tests, though sometimes they may interact +with other services running locally. The key difference between functional +tests and unit tests, however, is that functional tests are written from the +perspective of the user (who knows nothing about the code) and only knows +what they put in and what they get back. Essentially this is a higher-level +testing of "does the result match the spec?". + +Pros: + +* Ensures that your code *always* meets the stated functional requirements. +* Verifies things from an "end user" perspective, which helps to ensure + a high-quality experience. +* Designing your code with a functional testing perspective in mind helps + keep a higher-level viewpoint in mind. + +Cons: + +* Requires an additional layer of thinking to define functional requirements + in terms of inputs and outputs. +* Often requires writing a separate set of tests and/or using a different + testing framework from your unit tests. +* Don't offer any insight into the quality or status of the underlying code, + only verifies that it works or it doesn't. + +Integration Tests +----------------- + +This layer of testing involves testing all of the components that your +codebase interacts with or relies on in conjunction. This is equivalent to +"live" testing, but in a repeatable manner. + +Pros: + +* Catches *many* bugs that unit and functional tests will not. +* Doesn't rely on assumptions about the inputs and outputs. +* Will warn you when changes in external components break your code. + +Cons: + +* Difficult and time-consuming to create a repeatable test environment. +* Did I mention that setting it up is a pain? + +So what should I write? +----------------------- + +A few simple guidelines: + +#. Every bug fix should have a regression test. Period. + +#. When writing a new feature, think about writing unit tests to verify + the behavior step-by-step as you write the feature. Every time you'd + go to run your code by hand and verify it manually, think "could I + write a test to do this instead?". That way when the feature is done + and you're ready to commit it you've already got a whole set of tests + that are more thorough than anything you'd write after the fact. + +#. Write tests that hit every view in your application. Even if they + don't assert a single thing about the code, it tells you that your + users aren't getting fatal errors just by interacting with your code. + +What makes a good unit test? +============================ + +Limiting our focus just to unit tests, there are a number of things you can +do to make your unit tests as useful, maintainable, and unburdensome as +possible. + +Test data +--------- + +Use a single, consistent set of test data. Grow it over time, but do everything +you can not to fragment it. It quickly becomes unmaintainable and perniciously +out-of-sync with reality. + +Make your test data as accurate to reality as possible. Supply *all* the +attributes of an object, provide objects in all the various states you may want +to test. + +If you do the first suggestion above *first* it makes the second one far less +painful. Write once, use everywhere. + +To make your life even easier, if your codebase doesn't have a built-in +ORM-like function to manage your test data you can consider buidling (or +borrowing) one yourself. Being able to do simple retrieval queries on your +test data is incredibly valuable. + +Mocking +------- + +Mocking is the practice of providing stand-ins for objects or pieces of code +you don't need to test. While convenient, they should be used with *extreme* +caution. + +Why? Because overuse of mocks can rapidly land you in a situation where you're +not testing any real code. All you've done is verified that your mocking +framework returns what you tell it to. This problem can be very tricky to +recognize, since you may be mocking things in ``setUp`` methods, other modules, +etc. + +A good rule of thumb is to mock as close to the source as possible. If you have +a function call that calls an external API in a view , mock out the external +API, not the whole function. If you mock the whole function you've suddenly +lost test coverage for an entire chunk of code *inside* your codebase. Cut the +ties cleanly right where your system ends and the external world begins. + +Similarly, don't mock return values when you could construct a real return +value of the correct type with the correct attributes. You're just adding +another point of potential failure by exercising your mocking framework instead +of real code. Following the suggestions for testing above will make this a lot +less burdensome. + +Assertions and verification +--------------------------- + +Think long and hard about what you really want to verify in your unit test. In +particular, think about what custom logic your code executes. + +A common pitfall is to take a known test object, pass it through your code, +and then verify the properties of that object on the output. This is all well +and good, except if you're verifying properties that were untouched by your +code. What you want to check are the pieces that were *changed*, *added*, or +*removed*. Don't check the object's id attribute unless you have reason to +suspect it's not the object you started with. But if you added a new attribute +to it, be damn sure you verify that came out right. + +It's also very common to avoid testing things you really care about because +it's more difficult. Verifying that the proper messages were displayed to the +user after an action, testing for form errors, making sure exception handling +is tested... these types of things aren't always easy, but they're extremely +necessary. + +To that end, Horizon includes several custom assertions to make these tasks +easier. :meth:`~horizon.test.TestCase.assertNoFormErrors`, +:meth:`~horizon.test.TestCase.assertMessageCount`, and +:meth:`~horizon.test.TestCase.asertNoMessages` all exist for exactly these +purposes. Moreover, they provide useful output when things go wrong so you're +not left scratching your head wondering why your view test didn't redirect +as expected when you posted a form. + +.. _debugging_unit_tests: + +Debugging Unit Tests +==================== + +Tips and tricks +--------------- + +#. Use :meth:`~horizon.test.TestCase.assertNoFormErrors` immediately after + your ``client.post`` call for tests that handle form views. This will + immediately fail if your form POST failed due to a validation error and + tell you what the error was. + +#. Use :meth:`~horizon.test.TestCase.assertMessageCount` and + :meth:`~horizon.test.TestCase.asertNoMessages` when a piece of code is + failing inexplicably. Since the core error handlers attach user-facing + error messages (and since the core logging is silenced during test runs) + these methods give you the dual benefit of verifying the output you expect + while clearly showing you the problematic error message if they fail. + +#. Use Python's ``pdb`` module liberally. Many people don't realize it works + just as well in a test case as it does in a live view. Simply inserting + ``import pdb; pdb.set_trace()`` anywhere in your codebase will drop the + interpreter into an interactive shell so you can explore your test + environment and see which of your assumptions about the code isn't, + in fact, flawlessly correct. + +Common pitfalls +--------------- + +There are a number of typical (and non-obvious) ways to break the unit tests. +Some common things to look for: + +#. Make sure you stub out the method exactly as it's called in the code + being tested. For example, if your real code calls + ``api.keystone.tenant_get``, stubbing out ``api.tenant_get`` (available + for legacy reasons) will fail. + +#. When defining the expected input to a stubbed call, make sure the + arguments are *identical*, this includes ``str`` vs. ``int`` differences. + +#. Make sure your test data are completely in line with the expected inputs. + Again, ``str`` vs. ``int`` or missing properties on test objects will + kill your tests. + +#. Make sure there's nothing amiss in your templates (particularly the + ``{% url %}`` tag and its arguments). This often comes up when refactoring + views or renaming context variables. It can easily result in errors that + you might not stumble across while clicking around the development server. + +#. Make sure you're not redirecting to views that no longer exist, e.g. + the ``index`` view for a panel that got combined (such as instances & + volumes). + +#. Make sure your mock calls are in order before calling ``mox.ReplayAll``. + The order matters. + +#. Make sure you repeat any stubbed out method calls that happen more than + once. They don't automatically repeat, you have to explicitly define them. + While this is a nuisance, it makes you acutely aware of how many API + calls are involved in a particular function. + +Understanding the output from ``mox`` +------------------------------------- + +Horizon uses ``mox`` as it's mocking framework of choice, and while it +offers many nice features, its output when a test fails can be quite +mysterious. + +Unexpected Method Call +~~~~~~~~~~~~~~~~~~~~~~ + +This occurs when you stubbed out a piece of code, and it was subsequently +called in a way that you didn't specify it would be. There are two reasons +this tends to come up: + +#. You defined the expected call, but a subtle difference crept in. This + may be a string versus integer difference, a string versus unicode + difference, a slightly off date/time, or passing a name instead of an id. + +#. The method is actually being called *multiple times*. Since mox uses + a call stack internally, it simply pops off the expected method calls to + verify them. That means once a call is used once, it's gone. An easy way + to see if this is the case is simply to copy and paste your method call a + second time to see if the error changes. If it does, that means your method + is being called more times than you think it is. + +Expected Method Never Called +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This one is the opposite of the unexpected method call. This one means you +tol mox to expect a call and it didn't happen. This is almost always the +result of an error in the conditions of the test. Using the +:meth:`~horizon.test.TestCase.assertNoFormErrors` and +:meth:`~horizon.test.TestCase.assertMessageCount` will make it readily apparent +what the problem is in the majority of cases. If not, then use ``pdb`` and +start interrupting the code flow to see where things are getting off track. diff --git a/horizon/horizon/api/base.py b/horizon/horizon/api/base.py index fa680728b..5635686a9 100644 --- a/horizon/horizon/api/base.py +++ b/horizon/horizon/api/base.py @@ -69,7 +69,7 @@ class APIDictWrapper(object): def __getattr__(self, attr): try: return self._apidict[attr] - except KeyError, e: + except KeyError: msg = 'Unknown attribute "%(attr)s" on APIResource object ' \ 'of type "%(cls)s"' % {'attr': attr, 'cls': self.__class__} LOG.debug(msg) @@ -90,9 +90,10 @@ class APIDictWrapper(object): def get_service_from_catalog(catalog, service_type): - for service in catalog: - if service['type'] == service_type: - return service + if catalog: + for service in catalog: + if service['type'] == service_type: + return service return None diff --git a/horizon/horizon/api/glance.py b/horizon/horizon/api/glance.py index 16a635be0..694705da6 100644 --- a/horizon/horizon/api/glance.py +++ b/horizon/horizon/api/glance.py @@ -25,34 +25,42 @@ import urlparse from glance import client as glance_client -from horizon.api.base import * +from horizon.api.base import APIDictWrapper, url_for LOG = logging.getLogger(__name__) class Image(APIDictWrapper): - """Simple wrapper around glance image dictionary""" + """ + Wrapper around glance image dictionary to make it object-like and provide + access to image properties. + """ _attrs = ['checksum', 'container_format', 'created_at', 'deleted', 'deleted_at', 'disk_format', 'id', 'is_public', 'location', 'name', 'properties', 'size', 'status', 'updated_at', 'owner'] def __getattr__(self, attrname): if attrname == "properties": - return ImageProperties(super(Image, self).__getattr__(attrname)) + if not hasattr(self, "_properties"): + properties_dict = super(Image, self).__getattr__(attrname) + self._properties = ImageProperties(properties_dict) + return self._properties else: return super(Image, self).__getattr__(attrname) class ImageProperties(APIDictWrapper): - """Simple wrapper around glance image properties dictionary""" + """ + Wrapper around glance image properties dictionary to make it object-like. + """ _attrs = ['architecture', 'image_location', 'image_state', 'kernel_id', 'project_id', 'ramdisk_id', 'image_type'] -def glance_api(request): +def glanceclient(request): o = urlparse.urlparse(url_for(request, 'image')) - LOG.debug('glance_api connection created for host "%s:%d"' % + LOG.debug('glanceclient connection created for host "%s:%d"' % (o.hostname, o.port)) return glance_client.Client(o.hostname, o.port, @@ -60,11 +68,11 @@ def glance_api(request): def image_create(request, image_meta, image_file): - return Image(glance_api(request).add_image(image_meta, image_file)) + return Image(glanceclient(request).add_image(image_meta, image_file)) def image_delete(request, image_id): - return glance_api(request).delete_image(image_id) + return glanceclient(request).delete_image(image_id) def image_get(request, image_id): @@ -72,7 +80,7 @@ def image_get(request, image_id): Returns the actual image file from Glance for image with supplied identifier """ - return glance_api(request).get_image(image_id)[1] + return glanceclient(request).get_image(image_id)[1] def image_get_meta(request, image_id): @@ -80,16 +88,16 @@ def image_get_meta(request, image_id): Returns an Image object populated with metadata for image with supplied identifier. """ - return Image(glance_api(request).get_image_meta(image_id)) + return Image(glanceclient(request).get_image_meta(image_id)) def image_list_detailed(request): - return [Image(i) for i in glance_api(request).get_images_detailed()] + return [Image(i) for i in glanceclient(request).get_images_detailed()] def image_update(request, image_id, image_meta=None): image_meta = image_meta and image_meta or {} - return Image(glance_api(request).update_image(image_id, + return Image(glanceclient(request).update_image(image_id, image_meta=image_meta)) @@ -97,5 +105,5 @@ def snapshot_list_detailed(request): filters = {} filters['property-image_type'] = 'snapshot' filters['is_public'] = 'none' - return [Image(i) for i in glance_api(request) + return [Image(i) for i in glanceclient(request) .get_images_detailed(filters=filters)] diff --git a/horizon/horizon/api/keystone.py b/horizon/horizon/api/keystone.py index 75352072f..60c96ceec 100644 --- a/horizon/horizon/api/keystone.py +++ b/horizon/horizon/api/keystone.py @@ -27,7 +27,6 @@ from keystoneclient.v2_0 import client as keystone_client from keystoneclient.v2_0 import tokens from horizon import exceptions -from horizon.api import APIResourceWrapper LOG = logging.getLogger(__name__) @@ -39,21 +38,6 @@ def _get_endpoint_url(request): getattr(settings, 'OPENSTACK_KEYSTONE_URL')) -class Token(APIResourceWrapper): - """Simple wrapper around keystoneclient.tokens.Tenant""" - _attrs = ['id', 'user', 'serviceCatalog', 'tenant'] - - -class User(APIResourceWrapper): - """Simple wrapper around keystoneclient.users.User""" - _attrs = ['email', 'enabled', 'id', 'tenantId', 'name'] - - -class Services(APIResourceWrapper): - _attrs = ['disabled', 'host', 'id', 'last_update', 'stats', 'type', 'up', - 'zone'] - - def keystoneclient(request, username=None, password=None, tenant_id=None, token_id=None, endpoint=None, endpoint_type=None): """Returns a client connected to the Keystone backend. @@ -159,7 +143,7 @@ def token_create(request, tenant, username, password): token = c.tokens.authenticate(username=username, password=password, tenant_id=tenant) - return Token(token) + return token def token_create_scoped(request, tenant, token): @@ -184,17 +168,19 @@ def token_create_scoped(request, tenant, token): c.management_url = c.service_catalog.url_for(service_type='identity', endpoint_type='publicURL') scoped_token = tokens.Token(tokens.TokenManager, raw_token) - return Token(scoped_token) + return scoped_token def user_list(request, tenant_id=None): - return [User(u) for u in - keystoneclient(request).users.list(tenant_id=tenant_id)] + return keystoneclient(request).users.list(tenant_id=tenant_id) def user_create(request, user_id, email, password, tenant_id, enabled): - return User(keystoneclient(request).users.create( - user_id, password, email, tenant_id, enabled)) + return keystoneclient(request).users.create(user_id, + password, + email, + tenant_id, + enabled) def user_delete(request, user_id): @@ -202,25 +188,23 @@ def user_delete(request, user_id): def user_get(request, user_id): - return User(keystoneclient(request).users.get(user_id)) + return keystoneclient(request).users.get(user_id) def user_update_email(request, user_id, email): - return User(keystoneclient(request).users.update_email(user_id, email)) + return keystoneclient(request).users.update_email(user_id, email) def user_update_enabled(request, user_id, enabled): - return User(keystoneclient(request).users.update_enabled(user_id, enabled)) + return keystoneclient(request).users.update_enabled(user_id, enabled) def user_update_password(request, user_id, password): - return User(keystoneclient(request).users \ - .update_password(user_id, password)) + return keystoneclient(request).users.update_password(user_id, password) def user_update_tenant(request, user_id, tenant_id): - return User(keystoneclient(request).users \ - .update_tenant(user_id, tenant_id)) + return keystoneclient(request).users.update_tenant(user_id, tenant_id) def role_list(request): diff --git a/horizon/horizon/api/nova.py b/horizon/horizon/api/nova.py index 810f53485..5d8034334 100644 --- a/horizon/horizon/api/nova.py +++ b/horizon/horizon/api/nova.py @@ -27,7 +27,7 @@ from novaclient.v1_1 import client as nova_client from novaclient.v1_1 import security_group_rules as nova_rules from novaclient.v1_1.servers import REBOOT_HARD -from horizon.api.base import * +from horizon.api.base import APIResourceWrapper, APIDictWrapper, url_for LOG = logging.getLogger(__name__) @@ -38,43 +38,16 @@ INSTANCE_ACTIVE_STATE = 'ACTIVE' VOLUME_STATE_AVAILABLE = "available" -class Flavor(APIResourceWrapper): - """Simple wrapper around novaclient.flavors.Flavor""" - _attrs = ['disk', 'id', 'links', 'name', 'ram', 'vcpus'] - - -class FloatingIp(APIResourceWrapper): - """Simple wrapper for floating ip pools""" - _attrs = ['ip', 'fixed_ip', 'instance_id', 'id', 'pool'] - - -class FloatingIpPool(APIResourceWrapper): - """Simple wrapper for floating ips""" - _attrs = ['name'] - - -class KeyPair(APIResourceWrapper): - """Simple wrapper around novaclient.keypairs.Keypair""" - _attrs = ['fingerprint', 'name', 'private_key'] - - -class VirtualInterface(APIResourceWrapper): - _attrs = ['id', 'mac_address'] - - -class Volume(APIResourceWrapper): - """Nova Volume representation""" - _attrs = ['id', 'status', 'displayName', 'size', 'volumeType', 'createdAt', - 'attachments', 'displayDescription'] - - class VNCConsole(APIDictWrapper): - """Simple wrapper for floating ips""" + """ + Wrapper for the "console" dictionary returned by the + novaclient.servers.get_vnc_console method. + """ _attrs = ['url', 'type'] class Quota(object): - """ Basic wrapper for individual limits in a quota.""" + """ Wrapper for individual limits in a quota. """ def __init__(self, name, limit): self.name = name self.limit = limit @@ -84,7 +57,10 @@ class Quota(object): class QuotaSet(object): - """ Basic wrapper for quota sets.""" + """ + Wrapper for novaclient.quotas.QuotaSet objects which wraps the individual + quotas inside Quota objects. + """ def __init__(self, apiresource): self.items = [] for k in apiresource._info.keys(): @@ -167,7 +143,10 @@ class Usage(APIResourceWrapper): class SecurityGroup(APIResourceWrapper): - """Simple wrapper around novaclient.security_groups.SecurityGroup""" + """ + Wrapper around novaclient.security_groups.SecurityGroup which wraps its + rules in SecurityGroupRule objects and allows access to them. + """ _attrs = ['id', 'name', 'description', 'tenant_id'] @property @@ -185,7 +164,7 @@ class SecurityGroup(APIResourceWrapper): class SecurityGroupRule(APIResourceWrapper): - """ Simple wrapper for individual rules in a SecurityGroup. """ + """ Wrapper for individual rules in a SecurityGroup. """ _attrs = ['id', 'ip_protocol', 'from_port', 'to_port', 'ip_range'] def __unicode__(self): @@ -207,14 +186,14 @@ def novaclient(request): return c -def server_vnc_console(request, instance_id, type='novnc'): +def server_vnc_console(request, instance_id, console_type='novnc'): return VNCConsole(novaclient(request).servers.get_vnc_console(instance_id, - type)['console']) + console_type)['console']) def flavor_create(request, name, memory, vcpu, disk, flavor_id): - return Flavor(novaclient(request).flavors.create( - name, int(memory), int(vcpu), int(disk), flavor_id)) + return novaclient(request).flavors.create(name, int(memory), int(vcpu), + int(disk), flavor_id) def flavor_delete(request, flavor_id): @@ -222,26 +201,25 @@ def flavor_delete(request, flavor_id): def flavor_get(request, flavor_id): - return Flavor(novaclient(request).flavors.get(flavor_id)) + return novaclient(request).flavors.get(flavor_id) def flavor_list(request): - return [Flavor(f) for f in novaclient(request).flavors.list()] + return novaclient(request).flavors.list() def tenant_floating_ip_list(request): """ Fetches a list of all floating ips. """ - return [FloatingIp(ip) for ip in novaclient(request).floating_ips.list()] + return novaclient(request).floating_ips.list() def floating_ip_pools_list(request): """ Fetches a list of all floating ip pools. """ - return [FloatingIpPool(pool) - for pool in novaclient(request).floating_ip_pools.list()] + return novaclient(request).floating_ip_pools.list() def tenant_floating_ip_get(request, floating_ip_id): @@ -271,11 +249,11 @@ def snapshot_create(request, instance_id, name): def keypair_create(request, name): - return KeyPair(novaclient(request).keypairs.create(name)) + return novaclient(request).keypairs.create(name) def keypair_import(request, name, public_key): - return KeyPair(novaclient(request).keypairs.create(name, public_key)) + return novaclient(request).keypairs.create(name, public_key) def keypair_delete(request, keypair_id): @@ -283,7 +261,7 @@ def keypair_delete(request, keypair_id): def keypair_list(request): - return [KeyPair(key) for key in novaclient(request).keypairs.list()] + return novaclient(request).keypairs.list() def server_create(request, name, image, flavor, key_name, user_data, @@ -336,9 +314,7 @@ def server_resume(request, instance_id): novaclient(request).servers.resume(instance_id) -def server_reboot(request, - instance_id, - hardness=REBOOT_HARD): +def server_reboot(request, instance_id, hardness=REBOOT_HARD): server = server_get(request, instance_id) server.reboot(hardness) @@ -347,24 +323,22 @@ def server_update(request, instance_id, name): return novaclient(request).servers.update(instance_id, name=name) -def server_add_floating_ip(request, server, address): +def server_add_floating_ip(request, server, floating_ip): """ Associates floating IP to server's fixed IP. """ server = novaclient(request).servers.get(server) - fip = novaclient(request).floating_ips.get(address) - - return novaclient(request).servers.add_floating_ip(server, fip) + fip = novaclient(request).floating_ips.get(floating_ip) + return novaclient(request).servers.add_floating_ip(server.id, fip.id) -def server_remove_floating_ip(request, server, address): +def server_remove_floating_ip(request, server, floating_ip): """ Removes relationship between floating and server's fixed ip. """ - fip = novaclient(request).floating_ips.get(address) + fip = novaclient(request).floating_ips.get(floating_ip) server = novaclient(request).servers.get(fip.instance_id) - - return novaclient(request).servers.remove_floating_ip(server, fip) + return novaclient(request).servers.remove_floating_ip(server.id, fip.id) def tenant_quota_get(request, tenant_id): @@ -427,11 +401,11 @@ def virtual_interfaces_list(request, instance_id): def volume_list(request): - return [Volume(vol) for vol in novaclient(request).volumes.list()] + return novaclient(request).volumes.list() def volume_get(request, volume_id): - return Volume(novaclient(request).volumes.get(volume_id)) + return novaclient(request).volumes.get(volume_id) def volume_instance_list(request, instance_id): @@ -439,8 +413,9 @@ def volume_instance_list(request, instance_id): def volume_create(request, size, name, description): - return Volume(novaclient(request).volumes.create( - size, display_name=name, display_description=description)) + return novaclient(request).volumes.create(size, + display_name=name, + display_description=description) def volume_delete(request, volume_id): @@ -448,8 +423,9 @@ def volume_delete(request, volume_id): def volume_attach(request, volume_id, instance_id, device): - novaclient(request).volumes.create_server_volume( - instance_id, volume_id, device) + novaclient(request).volumes.create_server_volume(instance_id, + volume_id, + device) def volume_detach(request, instance_id, attachment_id): diff --git a/horizon/horizon/api/quantum.py b/horizon/horizon/api/quantum.py index 10945b2da..10e4a9d83 100644 --- a/horizon/horizon/api/quantum.py +++ b/horizon/horizon/api/quantum.py @@ -22,25 +22,24 @@ from __future__ import absolute_import import logging +import urlparse -from django.conf import settings from quantum import client as quantum_client +from horizon.api.base import url_for from horizon.api import nova -from horizon.api.base import * LOG = logging.getLogger(__name__) -def quantum_api(request): - if hasattr(request, 'user'): - tenant = request.user.tenant_id - else: - tenant = settings.QUANTUM_TENANT +# FIXME(gabriel): Add object wrappers for Quantum client. The quantum client +# returns plain dicts (a la Glance) which we should wrap. - return quantum_client.Client(settings.QUANTUM_URL, settings.QUANTUM_PORT, - False, tenant, 'json') +def quantum_api(request): + tenant = request.user.tenant_id + url = urlparse.urlparse(url_for(request, 'network')) + return quantum_client.Client(url.hostname, url.port, False, tenant, 'json') def quantum_list_networks(request): diff --git a/horizon/horizon/api/swift.py b/horizon/horizon/api/swift.py index d9ee616af..e9b93fde5 100644 --- a/horizon/horizon/api/swift.py +++ b/horizon/horizon/api/swift.py @@ -23,29 +23,15 @@ import logging import cloudfiles from django.conf import settings -from horizon.api.base import * +from horizon import exceptions +from horizon.api.base import url_for LOG = logging.getLogger(__name__) -class Container(APIResourceWrapper): - """Simple wrapper around cloudfiles.container.Container""" - _attrs = ['name', 'size_used', 'object_count', ] - - -class SwiftObject(APIResourceWrapper): - _attrs = ['name', 'container', 'size', 'metadata', 'last_modified', - 'metadata'] - - def sync_metadata(self): - self._apiresource.sync_metadata() - - class SwiftAuthentication(object): - """Auth container to pass CloudFiles storage URL and token from - session. - """ + """ Auth container in the format CloudFiles expects. """ def __init__(self, storage_url, auth_token): self.storage_url = storage_url self.auth_token = auth_token @@ -55,11 +41,10 @@ class SwiftAuthentication(object): def swift_api(request): - LOG.debug('object store connection created using token "%s"' - ' and url "%s"' % - (request.session['token'], url_for(request, 'object-store'))) - auth = SwiftAuthentication(url_for(request, 'object-store'), - request.session['token']) + endpoint = url_for(request, 'object-store') + LOG.debug('Swift connection created using token "%s" and url "%s"' + % (request.session['token'], endpoint)) + auth = SwiftAuthentication(endpoint, request.session['token']) return cloudfiles.get_connection(auth=auth) @@ -83,9 +68,8 @@ def swift_object_exists(request, container_name, object_name): def swift_get_containers(request, marker=None): limit = getattr(settings, 'API_RESULT_LIMIT', 1000) - containers = [Container(c) for c in swift_api(request).get_all_containers( - limit=limit + 1, - marker=marker)] + containers = swift_api(request).get_all_containers(limit=limit + 1, + marker=marker) if(len(containers) > limit): return (containers[0:-1], True) else: @@ -94,9 +78,8 @@ def swift_get_containers(request, marker=None): def swift_create_container(request, name): if swift_container_exists(request, name): - raise Exception('Container with name %s already exists.' % (name)) - - return Container(swift_api(request).create_container(name)) + raise exceptions.AlreadyExists(name, 'container') + return swift_api(request).create_container(name) def swift_delete_container(request, name): @@ -106,9 +89,9 @@ def swift_delete_container(request, name): def swift_get_objects(request, container_name, prefix=None, marker=None): limit = getattr(settings, 'API_RESULT_LIMIT', 1000) container = swift_api(request).get_container(container_name) - objects = [SwiftObject(o) for o in - container.get_objects(prefix=prefix, marker=marker, - limit=limit + 1)] + objects = container.get_objects(prefix=prefix, + marker=marker, + limit=limit + 1) if(len(objects) > limit): return (objects[0:-1], True) else: @@ -120,8 +103,7 @@ def swift_copy_object(request, orig_container_name, orig_object_name, container = swift_api(request).get_container(orig_container_name) if swift_object_exists(request, new_container_name, new_object_name): - raise Exception('Object with name %s already exists in container %s' - % (new_object_name, new_container_name)) + raise exceptions.AlreadyExists(new_object_name, 'object') orig_obj = container.get_object(orig_object_name) return orig_obj.copy_to(new_container_name, new_object_name) diff --git a/horizon/horizon/context_processors.py b/horizon/horizon/context_processors.py index 09b2556dd..843588e6c 100644 --- a/horizon/horizon/context_processors.py +++ b/horizon/horizon/context_processors.py @@ -25,8 +25,6 @@ import logging from django.conf import settings -from horizon import api - LOG = logging.getLogger(__name__) diff --git a/horizon/horizon/dashboards/nova/access_and_security/floating_ips/forms.py b/horizon/horizon/dashboards/nova/access_and_security/floating_ips/forms.py index 44b4fa942..73a64306b 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/floating_ips/forms.py +++ b/horizon/horizon/dashboards/nova/access_and_security/floating_ips/forms.py @@ -48,16 +48,18 @@ class FloatingIpAssociate(forms.SelfHandlingForm): label=_("Instance")) def handle(self, request, data): + ip_id = int(data['floating_ip_id']) try: api.server_add_floating_ip(request, data['instance_id'], - data['floating_ip_id']) + ip_id) LOG.info('Associating Floating IP "%s" with Instance "%s"' % (data['floating_ip'], data['instance_id'])) - messages.info(request, _('Successfully associated Floating IP \ - %(ip)s with Instance: %(inst)s' - % {"ip": data['floating_ip'], - "inst": data['instance_id']})) + messages.success(request, + _('Successfully associated Floating IP %(ip)s ' + 'with Instance: %(inst)s') + % {"ip": data['floating_ip'], + "inst": data['instance_id']}) except novaclient_exceptions.ClientException, e: LOG.exception("ClientException in FloatingIpAssociate") messages.error(request, _('Error associating Floating IP: %s') % e) diff --git a/horizon/horizon/dashboards/nova/access_and_security/floating_ips/tables.py b/horizon/horizon/dashboards/nova/access_and_security/floating_ips/tables.py index 3bb91c754..b37de302c 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/floating_ips/tables.py +++ b/horizon/horizon/dashboards/nova/access_and_security/floating_ips/tables.py @@ -20,9 +20,9 @@ import logging from django import shortcuts from django.contrib import messages from django.utils.translation import ugettext as _ -from novaclient import exceptions as novaclient_exceptions from horizon import api +from horizon import exceptions from horizon import tables @@ -76,13 +76,12 @@ class DisassociateIP(tables.Action): fip = table.get_object_by_id(int(obj_id)) api.server_remove_floating_ip(request, fip.instance_id, fip.id) LOG.info('Disassociating Floating IP "%s".' % obj_id) - messages.info(request, - _('Successfully disassociated Floating IP: %s') - % obj_id) - except novaclient_exceptions.ClientException, e: - LOG.exception("ClientException in FloatingIpAssociate") - messages.error(request, _('Error disassociating Floating IP: %s') - % e.message) + messages.success(request, + _('Successfully disassociated Floating IP: %s') + % obj_id) + except: + exceptions.handle(request, + _('Unable to disassociate floating IP.')) return shortcuts.redirect('horizon:nova:access_and_security:index') diff --git a/horizon/horizon/dashboards/nova/access_and_security/floating_ips/tests.py b/horizon/horizon/dashboards/nova/access_and_security/floating_ips/tests.py index b6f926994..74586f4bd 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/floating_ips/tests.py +++ b/horizon/horizon/dashboards/nova/access_and_security/floating_ips/tests.py @@ -29,165 +29,129 @@ from horizon import test INDEX_URL = reverse('horizon:nova:access_and_security:index') +NAMESPACE = "horizon:nova:access_and_security:floating_ips" -class FloatingIpViewTests(test.BaseViewTests): - - def setUp(self): - super(FloatingIpViewTests, self).setUp() - keypair = api.KeyPair(None) - keypair.name = 'keyName' - self.keypairs = (keypair,) - - server = api.Server(None, self.request) - server.id = 1 - server.name = 'serverName' - self.server = server - self.servers = (server, ) - - floating_ip = api.FloatingIp(None) - floating_ip.id = 1 - floating_ip.fixed_ip = '10.0.0.4' - floating_ip.instance_id = 1 - floating_ip.ip = '58.58.58.58' - - self.floating_ip = floating_ip - self.floating_ips = [floating_ip, ] - - security_group = api.SecurityGroup(None) - security_group.id = '1' - security_group.name = 'default' - self.security_groups = (security_group,) - +class FloatingIpViewTests(test.TestCase): def test_associate(self): + floating_ip = self.floating_ips.first() self.mox.StubOutWithMock(api, 'server_list') - api.server_list = self.mox.CreateMockAnything() - api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers) - self.mox.StubOutWithMock(api, 'tenant_floating_ip_get') - api.tenant_floating_ip_get = self.mox.CreateMockAnything() - api.tenant_floating_ip_get(IsA(http.HttpRequest), str(1)).\ - AndReturn(self.floating_ip) + api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list()) + api.tenant_floating_ip_get(IsA(http.HttpRequest), + floating_ip.id).AndReturn(floating_ip) self.mox.ReplayAll() - res = self.client.get( - reverse('horizon:nova:access_and_security:floating_ips:associate', - args=[1])) + url = reverse('%s:associate' % NAMESPACE, args=[floating_ip.id]) + res = self.client.get(url) self.assertTemplateUsed(res, 'nova/access_and_security/floating_ips/associate.html') def test_associate_post(self): + floating_ip = self.floating_ips.first() + server = self.servers.first() self.mox.StubOutWithMock(api, 'server_list') - api.server_list = self.mox.CreateMockAnything() - api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers) - self.mox.StubOutWithMock(api, 'tenant_floating_ip_list') - api.tenant_floating_ip_list(IsA(http.HttpRequest)).\ - AndReturn(self.floating_ips) - self.mox.StubOutWithMock(api, 'server_add_floating_ip') - api.server_add_floating_ip = self.mox.CreateMockAnything() - api.server_add_floating_ip(IsA(http.HttpRequest), IsA(unicode), - IsA(unicode)).\ - AndReturn(None) self.mox.StubOutWithMock(api, 'tenant_floating_ip_get') - api.tenant_floating_ip_get = self.mox.CreateMockAnything() - api.tenant_floating_ip_get(IsA(http.HttpRequest), str(1)).\ - AndReturn(self.floating_ip) + api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list()) + api.tenant_floating_ip_list(IsA(http.HttpRequest)) \ + .AndReturn(self.floating_ips.list()) + api.server_add_floating_ip(IsA(http.HttpRequest), + server.id, + floating_ip.id) + api.tenant_floating_ip_get(IsA(http.HttpRequest), + floating_ip.id).AndReturn(floating_ip) self.mox.ReplayAll() - res = self.client.post( - reverse('horizon:nova:access_and_security:floating_ips:associate', - args=[1]), - {'instance_id': 1, - 'floating_ip_id': self.floating_ip.id, - 'floating_ip': self.floating_ip.ip, - 'method': 'FloatingIpAssociate'}) - + form_data = {'instance_id': server.id, + 'floating_ip_id': floating_ip.id, + 'floating_ip': floating_ip.ip, + 'method': 'FloatingIpAssociate'} + url = reverse('%s:associate' % NAMESPACE, args=[floating_ip.id]) + res = self.client.post(url, form_data) self.assertRedirects(res, INDEX_URL) def test_associate_post_with_exception(self): + floating_ip = self.floating_ips.first() + server = self.servers.first() self.mox.StubOutWithMock(api, 'server_list') - api.server_list = self.mox.CreateMockAnything() - api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers) - self.mox.StubOutWithMock(api, 'tenant_floating_ip_list') - api.tenant_floating_ip_list(IsA(http.HttpRequest)).\ - AndReturn(self.floating_ips) self.mox.StubOutWithMock(api, 'security_group_list') - api.security_group_list(IsA(http.HttpRequest)).\ - AndReturn(self.security_groups) self.mox.StubOutWithMock(api.nova, 'keypair_list') - api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) - self.mox.StubOutWithMock(api, 'server_add_floating_ip') - api.server_add_floating_ip = self.mox.CreateMockAnything() - exception = novaclient_exceptions.ClientException('ClientException', - message='clientException') - api.server_add_floating_ip(IsA(http.HttpRequest), IsA(unicode), - IsA(unicode)).\ - AndRaise(exception) - self.mox.StubOutWithMock(api, 'tenant_floating_ip_get') - api.tenant_floating_ip_get = self.mox.CreateMockAnything() - api.tenant_floating_ip_get(IsA(http.HttpRequest), IsA(unicode)).\ - AndReturn(self.floating_ip) + api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list()) + api.tenant_floating_ip_list(IsA(http.HttpRequest)) \ + .AndReturn(self.floating_ips.list()) + api.security_group_list(IsA(http.HttpRequest)) \ + .AndReturn(self.security_groups.list()) + api.nova.keypair_list(IsA(http.HttpRequest)) \ + .AndReturn(self.keypairs.list()) + exc = novaclient_exceptions.ClientException('ClientException') + api.server_add_floating_ip(IsA(http.HttpRequest), + server.id, + floating_ip.id).AndRaise(exc) + api.tenant_floating_ip_get(IsA(http.HttpRequest), + floating_ip.id).AndReturn(floating_ip) self.mox.ReplayAll() - res = self.client.post(reverse( - 'horizon:nova:access_and_security:floating_ips:associate', - args=[1]), + url = reverse('%s:associate' % NAMESPACE, args=[floating_ip.id]) + res = self.client.post(url, {'instance_id': 1, - 'floating_ip_id': self.floating_ip.id, - 'floating_ip': self.floating_ip.ip, + 'floating_ip_id': floating_ip.id, + 'floating_ip': floating_ip.ip, 'method': 'FloatingIpAssociate'}) self.assertRaises(novaclient_exceptions.ClientException) - self.assertRedirects(res, INDEX_URL) def test_disassociate_post(self): + floating_ip = self.floating_ips.first() + server = self.servers.first() self.mox.StubOutWithMock(api.nova, 'keypair_list') self.mox.StubOutWithMock(api, 'security_group_list') self.mox.StubOutWithMock(api, 'tenant_floating_ip_list') self.mox.StubOutWithMock(api, 'tenant_floating_ip_get') self.mox.StubOutWithMock(api, 'server_remove_floating_ip') - api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) - api.security_group_list(IsA(http.HttpRequest)).\ - AndReturn(self.security_groups) - api.tenant_floating_ip_list(IsA(http.HttpRequest)).\ - AndReturn(self.floating_ips) - - api.server_remove_floating_ip(IsA(http.HttpRequest), IsA(int), - IsA(int)).\ - AndReturn(None) + api.nova.keypair_list(IsA(http.HttpRequest)) \ + .AndReturn(self.keypairs.list()) + api.security_group_list(IsA(http.HttpRequest)) \ + .AndReturn(self.security_groups.list()) + api.tenant_floating_ip_list(IsA(http.HttpRequest)) \ + .AndReturn(self.floating_ips.list()) + api.server_remove_floating_ip(IsA(http.HttpRequest), + server.id, + floating_ip.id) self.mox.ReplayAll() - action = "floating_ips__disassociate__%s" % self.floating_ip.id + action = "floating_ips__disassociate__%s" % floating_ip.id res = self.client.post(INDEX_URL, {"action": action}) + self.assertMessageCount(success=1) self.assertRedirectsNoFollow(res, INDEX_URL) def test_disassociate_post_with_exception(self): + floating_ip = self.floating_ips.first() + server = self.servers.first() self.mox.StubOutWithMock(api.nova, 'keypair_list') self.mox.StubOutWithMock(api, 'security_group_list') self.mox.StubOutWithMock(api, 'tenant_floating_ip_list') self.mox.StubOutWithMock(api, 'tenant_floating_ip_get') self.mox.StubOutWithMock(api, 'server_remove_floating_ip') + api.nova.keypair_list(IsA(http.HttpRequest)) \ + .AndReturn(self.keypairs.list()) + api.security_group_list(IsA(http.HttpRequest)) \ + .AndReturn(self.security_groups.list()) + api.tenant_floating_ip_list(IsA(http.HttpRequest)) \ + .AndReturn(self.floating_ips.list()) - api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) - api.security_group_list(IsA(http.HttpRequest)).\ - AndReturn(self.security_groups) - api.tenant_floating_ip_list(IsA(http.HttpRequest)).\ - AndReturn(self.floating_ips) - - exception = novaclient_exceptions.ClientException('ClientException', - message='clientException') + exc = novaclient_exceptions.ClientException('ClientException') api.server_remove_floating_ip(IsA(http.HttpRequest), - IsA(int), - IsA(int)).AndRaise(exception) + server.id, + floating_ip.id).AndRaise(exc) self.mox.ReplayAll() - action = "floating_ips__disassociate__%s" % self.floating_ip.id + action = "floating_ips__disassociate__%s" % floating_ip.id res = self.client.post(INDEX_URL, {"action": action}) self.assertRaises(novaclient_exceptions.ClientException) self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/horizon/horizon/dashboards/nova/access_and_security/floating_ips/views.py b/horizon/horizon/dashboards/nova/access_and_security/floating_ips/views.py index c3a3377dd..2422e9746 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/floating_ips/views.py +++ b/horizon/horizon/dashboards/nova/access_and_security/floating_ips/views.py @@ -24,11 +24,11 @@ Views for managing Nova floating IPs. """ import logging -from django import http -from django.contrib import messages +from django.core.urlresolvers import reverse from django.utils.translation import ugettext as _ from horizon import api +from horizon import exceptions from horizon import forms from .forms import FloatingIpAssociate, FloatingIpAllocate @@ -42,19 +42,24 @@ class AssociateView(forms.ModalFormView): context_object_name = 'floating_ip' def get_object(self, *args, **kwargs): - ip_id = kwargs['ip_id'] + ip_id = int(kwargs['ip_id']) try: return api.tenant_floating_ip_get(self.request, ip_id) - except Exception as e: - LOG.exception('Error fetching floating ip with id "%s".' % ip_id) - messages.error(self.request, - _('Unable to associate floating ip: %s') % e) - raise http.Http404("Floating IP %s not available." % ip_id) + except: + redirect = reverse('horizon:nova:access_and_security:index') + exceptions.handle(self.request, + _('Unable to associate floating IP.'), + redirect=redirect) def get_initial(self): - instances = [(server.id, 'id: %s, name: %s' % - (server.id, server.name)) - for server in api.server_list(self.request)] + try: + servers = api.server_list(self.request) + except: + redirect = reverse('horizon:nova:access_and_security:index') + exceptions.handle(self.request, + _('Unable to retrieve instance list.'), + redirect=redirect) + instances = [(server.id, server.name) for server in servers] return {'floating_ip_id': self.object.id, 'floating_ip': self.object.ip, 'instances': instances} @@ -71,6 +76,6 @@ class AllocateView(forms.ModalFormView): pool_list = [(pool.name, pool.name) for pool in api.floating_ip_pools_list(self.request)] else: - pool_list = [(None, _("There are no Floating IP Pools"))] + pool_list = [(None, _("No floating IP pools available."))] return {'tenant_id': self.request.user.tenant_id, 'pool_list': pool_list} diff --git a/horizon/horizon/dashboards/nova/access_and_security/keypairs/tests.py b/horizon/horizon/dashboards/nova/access_and_security/keypairs/tests.py index c1d8296aa..51ab78a07 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/keypairs/tests.py +++ b/horizon/horizon/dashboards/nova/access_and_security/keypairs/tests.py @@ -30,97 +30,67 @@ from horizon import test INDEX_VIEW_URL = reverse('horizon:nova:access_and_security:index') -class KeyPairViewTests(test.BaseViewTests): - def setUp(self): - super(KeyPairViewTests, self).setUp() - keypair = api.KeyPair(None) - keypair.name = 'keyName' - self.keypairs = (keypair,) - +class KeyPairViewTests(test.TestCase): def test_delete_keypair(self): - KEYPAIR_ID = self.keypairs[0].name - formData = {'action': 'keypairs__delete__%s' % KEYPAIR_ID} + keypair = self.keypairs.first() self.mox.StubOutWithMock(api.nova, 'keypair_list') self.mox.StubOutWithMock(api.nova, 'keypair_delete') - - api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) - api.nova.keypair_delete(IsA(http.HttpRequest), unicode(KEYPAIR_ID)) - + api.nova.keypair_list(IsA(http.HttpRequest)) \ + .AndReturn(self.keypairs.list()) + api.nova.keypair_delete(IsA(http.HttpRequest), keypair.name) self.mox.ReplayAll() + formData = {'action': 'keypairs__delete__%s' % keypair.name} res = self.client.post(INDEX_VIEW_URL, formData) - self.assertRedirectsNoFollow(res, INDEX_VIEW_URL) def test_delete_keypair_exception(self): - KEYPAIR_ID = self.keypairs[0].name - formData = {'action': 'keypairs__delete__%s' % KEYPAIR_ID} - + keypair = self.keypairs.first() self.mox.StubOutWithMock(api.nova, 'keypair_list') self.mox.StubOutWithMock(api.nova, 'keypair_delete') - - api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) - exception = novaclient_exceptions.ClientException('clientException', - message='clientException') - api.nova.keypair_delete(IsA(http.HttpRequest), unicode(KEYPAIR_ID)) \ - .AndRaise(exception) - + api.nova.keypair_list(IsA(http.HttpRequest)) \ + .AndReturn(self.keypairs.list()) + exc = novaclient_exceptions.ClientException('clientException') + api.nova.keypair_delete(IsA(http.HttpRequest), keypair.name) \ + .AndRaise(exc) self.mox.ReplayAll() + formData = {'action': 'keypairs__delete__%s' % keypair.name} res = self.client.post(INDEX_VIEW_URL, formData) - self.assertRedirectsNoFollow(res, INDEX_VIEW_URL) def test_create_keypair_get(self): res = self.client.get( reverse('horizon:nova:access_and_security:keypairs:create')) - self.assertTemplateUsed(res, 'nova/access_and_security/keypairs/create.html') def test_create_keypair_post(self): - KEYPAIR_NAME = 'newKeypair' - PRIVATE_KEY = 'privateKey' - - newKeyPair = self.mox.CreateMock(api.KeyPair) - newKeyPair.name = KEYPAIR_NAME - newKeyPair.private_key = PRIVATE_KEY - - formData = {'method': 'CreateKeypair', - 'name': KEYPAIR_NAME, - } + keypair = self.keypairs.first() + keypair.private_key = "secret" self.mox.StubOutWithMock(api, 'keypair_create') api.keypair_create(IsA(http.HttpRequest), - KEYPAIR_NAME).AndReturn(newKeyPair) - + keypair.name).AndReturn(keypair) self.mox.ReplayAll() - res = self.client.post( - reverse('horizon:nova:access_and_security:keypairs:create'), - formData) - + formData = {'method': 'CreateKeypair', + 'name': keypair.name} + url = reverse('horizon:nova:access_and_security:keypairs:create') + res = self.client.post(url, formData) self.assertTrue(res.has_header('Content-Disposition')) def test_create_keypair_exception(self): - KEYPAIR_NAME = 'newKeypair' - - formData = {'method': 'CreateKeypair', - 'name': KEYPAIR_NAME, - } - - exception = novaclient_exceptions.ClientException('clientException', - message='clientException') + keypair = self.keypairs.first() + exc = novaclient_exceptions.ClientException('clientException') self.mox.StubOutWithMock(api, 'keypair_create') - api.keypair_create(IsA(http.HttpRequest), - KEYPAIR_NAME).AndRaise(exception) - + api.keypair_create(IsA(http.HttpRequest), keypair.name).AndRaise(exc) self.mox.ReplayAll() - res = self.client.post( - reverse('horizon:nova:access_and_security:keypairs:create'), - formData) + formData = {'method': 'CreateKeypair', + 'name': keypair.name} + url = reverse('horizon:nova:access_and_security:keypairs:create') + res = self.client.post(url, formData) - self.assertRedirectsNoFollow(res, - reverse('horizon:nova:access_and_security:keypairs:create')) + self.assertRedirectsNoFollow(res, url) diff --git a/horizon/horizon/dashboards/nova/access_and_security/security_groups/forms.py b/horizon/horizon/dashboards/nova/access_and_security/security_groups/forms.py index dc971105d..0ba14011a 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/security_groups/forms.py +++ b/horizon/horizon/dashboards/nova/access_and_security/security_groups/forms.py @@ -27,6 +27,7 @@ from django.utils.translation import ugettext as _ from novaclient import exceptions as novaclient_exceptions from horizon import api +from horizon import exceptions from horizon import forms @@ -40,20 +41,15 @@ class CreateGroup(forms.SelfHandlingForm): def handle(self, request, data): try: - LOG.info('Add security_group: "%s"' % data) - api.security_group_create(request, data['name'], data['description']) messages.success(request, _('Successfully created security_group: %s') % data['name']) - return shortcuts.redirect( - 'horizon:nova:access_and_security:index') - except novaclient_exceptions.ClientException, e: - LOG.exception("ClientException in CreateGroup") - messages.error(request, _('Error creating security group: %s') % - e.message) + except: + exceptions.handle(request, _('Unable to create security group.')) + return shortcuts.redirect('horizon:nova:access_and_security:index') class AddRule(forms.SelfHandlingForm): @@ -66,7 +62,7 @@ class AddRule(forms.SelfHandlingForm): # TODO (anthony) source group support # group_id = forms.CharField() - security_group_id = forms.CharField(widget=forms.HiddenInput()) + security_group_id = forms.IntegerField(widget=forms.HiddenInput()) tenant_id = forms.CharField(widget=forms.HiddenInput()) def handle(self, request, data): diff --git a/horizon/horizon/dashboards/nova/access_and_security/security_groups/tests.py b/horizon/horizon/dashboards/nova/access_and_security/security_groups/tests.py index 0f06cf598..2fd4de008 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/security_groups/tests.py +++ b/horizon/horizon/dashboards/nova/access_and_security/security_groups/tests.py @@ -22,257 +22,195 @@ from django import http from django.conf import settings from django.core.urlresolvers import reverse from novaclient import exceptions as novaclient_exceptions -from novaclient.v1_1 import security_group_rules as nova_rules from mox import IsA from horizon import api from horizon import test from .tables import SecurityGroupsTable, RulesTable -SECGROUP_ID = '2' INDEX_URL = reverse('horizon:nova:access_and_security:index') SG_CREATE_URL = \ reverse('horizon:nova:access_and_security:security_groups:create') -SG_EDIT_RULE_URL = \ - reverse('horizon:nova:access_and_security:security_groups:edit_rules', - args=[SECGROUP_ID]) def strip_absolute_base(uri): return uri.split(settings.TESTSERVER, 1)[-1] -class SecurityGroupsViewTests(test.BaseViewTests): +class SecurityGroupsViewTests(test.TestCase): def setUp(self): super(SecurityGroupsViewTests, self).setUp() - - sg1 = api.SecurityGroup(None) - sg1.id = 1 - sg1.name = 'default' - - sg2 = api.SecurityGroup(None) - sg2.id = 2 - sg2.name = 'group_2' - - rule = {'id': 1, - 'ip_protocol': u"tcp", - 'from_port': "80", - 'to_port': "80", - 'parent_group_id': "2", - 'ip_range': {'cidr': "0.0.0.0/32"}} - manager = nova_rules.SecurityGroupRuleManager - rule_obj = nova_rules.SecurityGroupRule(manager, rule) - self.rules = [rule_obj] - sg1.rules = self.rules - sg2.rules = self.rules - - self.security_groups = (sg1, sg2) + sec_group = self.security_groups.first() + self.edit_url = reverse('horizon:nova:access_and_security:' + 'security_groups:edit_rules', + args=[sec_group.id]) def test_create_security_groups_get(self): res = self.client.get(SG_CREATE_URL) - self.assertTemplateUsed(res, 'nova/access_and_security/security_groups/create.html') def test_create_security_groups_post(self): - SECGROUP_NAME = 'fakegroup' - SECGROUP_DESC = 'fakegroup_desc' - - new_group = self.mox.CreateMock(api.SecurityGroup) - new_group.name = SECGROUP_NAME - - formData = {'method': 'CreateGroup', - 'tenant_id': self.TEST_TENANT, - 'name': SECGROUP_NAME, - 'description': SECGROUP_DESC, - } - + sec_group = self.security_groups.first() self.mox.StubOutWithMock(api, 'security_group_create') api.security_group_create(IsA(http.HttpRequest), - SECGROUP_NAME, SECGROUP_DESC).AndReturn(new_group) - + sec_group.name, + sec_group.description).AndReturn(sec_group) self.mox.ReplayAll() + formData = {'method': 'CreateGroup', + 'tenant_id': self.tenant.id, + 'name': sec_group.name, + 'description': sec_group.description} res = self.client.post(SG_CREATE_URL, formData) - self.assertRedirectsNoFollow(res, INDEX_URL) def test_create_security_groups_post_exception(self): - SECGROUP_NAME = 'fakegroup' - SECGROUP_DESC = 'fakegroup_desc' - - exception = novaclient_exceptions.ClientException('ClientException', - message='ClientException') + sec_group = self.security_groups.first() + self.mox.StubOutWithMock(api, 'security_group_create') + exc = novaclient_exceptions.ClientException('ClientException') + api.security_group_create(IsA(http.HttpRequest), + sec_group.name, + sec_group.description).AndRaise(exc) + self.mox.ReplayAll() formData = {'method': 'CreateGroup', - 'tenant_id': self.TEST_TENANT, - 'name': SECGROUP_NAME, - 'description': SECGROUP_DESC, - } - - self.mox.StubOutWithMock(api, 'security_group_create') - api.security_group_create(IsA(http.HttpRequest), - SECGROUP_NAME, SECGROUP_DESC).AndRaise(exception) - - self.mox.ReplayAll() - + 'tenant_id': self.tenant.id, + 'name': sec_group.name, + 'description': sec_group.description} res = self.client.post(SG_CREATE_URL, formData) - - self.assertTemplateUsed(res, - 'nova/access_and_security/security_groups/create.html') + self.assertMessageCount(error=1) + self.assertRedirectsNoFollow(res, INDEX_URL) def test_edit_rules_get(self): + sec_group = self.security_groups.first() self.mox.StubOutWithMock(api, 'security_group_get') - api.security_group_get(IsA(http.HttpRequest), SECGROUP_ID).AndReturn( - self.security_groups[1]) - + api.security_group_get(IsA(http.HttpRequest), + sec_group.id).AndReturn(sec_group) self.mox.ReplayAll() - res = self.client.get(SG_EDIT_RULE_URL) - + res = self.client.get(self.edit_url) self.assertTemplateUsed(res, 'nova/access_and_security/security_groups/edit_rules.html') self.assertItemsEqual(res.context['security_group'].name, - self.security_groups[1].name) + sec_group.name) def test_edit_rules_get_exception(self): - exception = novaclient_exceptions.ClientException('ClientException', - message='ClientException') + sec_group = self.security_groups.first() self.mox.StubOutWithMock(api, 'security_group_get') - api.security_group_get(IsA(http.HttpRequest), SECGROUP_ID) \ - .AndRaise(exception) - + exc = novaclient_exceptions.ClientException('ClientException') + api.security_group_get(IsA(http.HttpRequest), + sec_group.id).AndRaise(exc) self.mox.ReplayAll() - res = self.client.get(SG_EDIT_RULE_URL) - + res = self.client.get(self.edit_url) self.assertRedirects(res, INDEX_URL) def test_edit_rules_add_rule(self): - RULE_ID = '1' - FROM_PORT = '-1' - TO_PORT = '-1' - IP_PROTOCOL = 'icmp' - CIDR = '0.0.0.0/0' - - new_rule = self.mox.CreateMock(api.SecurityGroup) - new_rule.from_port = FROM_PORT - new_rule.to_port = TO_PORT - new_rule.ip_protocol = IP_PROTOCOL - new_rule.cidr = CIDR - new_rule.security_group_id = SECGROUP_ID - new_rule.id = RULE_ID - - formData = {'method': 'AddRule', - 'tenant_id': self.TEST_TENANT, - 'security_group_id': SECGROUP_ID, - 'from_port': FROM_PORT, - 'to_port': TO_PORT, - 'ip_protocol': IP_PROTOCOL, - 'cidr': CIDR} + sec_group = self.security_groups.first() + rule = self.security_group_rules.first() self.mox.StubOutWithMock(api, 'security_group_rule_create') api.security_group_rule_create(IsA(http.HttpRequest), - SECGROUP_ID, IP_PROTOCOL, FROM_PORT, TO_PORT, CIDR)\ - .AndReturn(new_rule) - + sec_group.id, + rule.ip_protocol, + rule.from_port, + rule.to_port, + rule.ip_range['cidr']).AndReturn(rule) self.mox.ReplayAll() - res = self.client.post(SG_EDIT_RULE_URL, formData) - + formData = {'method': 'AddRule', + 'tenant_id': self.tenant.id, + 'security_group_id': sec_group.id, + 'from_port': rule.from_port, + 'to_port': rule.to_port, + 'ip_protocol': rule.ip_protocol, + 'cidr': rule.ip_range['cidr']} + res = self.client.post(self.edit_url, formData) self.assertRedirectsNoFollow(res, INDEX_URL) def test_edit_rules_add_rule_exception(self): - exception = novaclient_exceptions.ClientException('ClientException', - message='ClientException') - - FROM_PORT = '-1' - TO_PORT = '-1' - IP_PROTOCOL = 'icmp' - CIDR = '0.0.0.0/0' - - formData = {'method': 'AddRule', - 'tenant_id': self.TEST_TENANT, - 'security_group_id': SECGROUP_ID, - 'from_port': FROM_PORT, - 'to_port': TO_PORT, - 'ip_protocol': IP_PROTOCOL, - 'cidr': CIDR} + sec_group = self.security_groups.first() + rule = self.security_group_rules.first() + exc = novaclient_exceptions.ClientException('ClientException') self.mox.StubOutWithMock(api, 'security_group_rule_create') api.security_group_rule_create(IsA(http.HttpRequest), - SECGROUP_ID, IP_PROTOCOL, FROM_PORT, - TO_PORT, CIDR).AndRaise(exception) - + sec_group.id, + rule.ip_protocol, + rule.from_port, + rule.to_port, + rule.ip_range['cidr']).AndRaise(exc) self.mox.ReplayAll() - res = self.client.post(SG_EDIT_RULE_URL, formData) - + formData = {'method': 'AddRule', + 'tenant_id': self.tenant.id, + 'security_group_id': sec_group.id, + 'from_port': rule.from_port, + 'to_port': rule.to_port, + 'ip_protocol': rule.ip_protocol, + 'cidr': rule.ip_range['cidr']} + res = self.client.post(self.edit_url, formData) self.assertRedirectsNoFollow(res, INDEX_URL) def test_edit_rules_delete_rule(self): - RULE_ID = 1 + sec_group = self.security_groups.first() + rule = self.security_group_rules.first() self.mox.StubOutWithMock(api, 'security_group_rule_delete') - api.security_group_rule_delete(IsA(http.HttpRequest), RULE_ID) - + api.security_group_rule_delete(IsA(http.HttpRequest), rule.id) self.mox.ReplayAll() - form_data = {"action": "rules__delete__%s" % RULE_ID} - req = self.factory.post(SG_EDIT_RULE_URL, form_data) - table = RulesTable(req, self.rules) + form_data = {"action": "rules__delete__%s" % rule.id} + req = self.factory.post(self.edit_url, form_data) + table = RulesTable(req, sec_group.rules) handled = table.maybe_handle() - - self.assertEqual(strip_absolute_base(handled['location']), - INDEX_URL) + self.assertEqual(strip_absolute_base(handled['location']), INDEX_URL) def test_edit_rules_delete_rule_exception(self): - RULE_ID = 1 + rule = self.security_group_rules.first() self.mox.StubOutWithMock(api, 'security_group_rule_delete') - - exception = novaclient_exceptions.ClientException('ClientException', - message='ClientException') - api.security_group_rule_delete(IsA(http.HttpRequest), RULE_ID) \ - .AndRaise(exception) - + exc = novaclient_exceptions.ClientException('ClientException') + api.security_group_rule_delete(IsA(http.HttpRequest), + rule.id).AndRaise(exc) self.mox.ReplayAll() - form_data = {"action": "rules__delete__%s" % RULE_ID} - req = self.factory.post(SG_EDIT_RULE_URL, form_data) - table = RulesTable(req, self.rules) + form_data = {"action": "rules__delete__%s" % rule.id} + req = self.factory.post(self.edit_url, form_data) + table = RulesTable(req, self.security_group_rules.list()) handled = table.maybe_handle() - self.assertEqual(strip_absolute_base(handled['location']), INDEX_URL) def test_delete_group(self): - self.mox.StubOutWithMock(api, 'security_group_delete') - api.security_group_delete(IsA(http.HttpRequest), 2) + sec_group = self.security_groups.get(name="other_group") + self.mox.StubOutWithMock(api, 'security_group_delete') + api.security_group_delete(IsA(http.HttpRequest), sec_group.id) self.mox.ReplayAll() - form_data = {"action": "security_groups__delete__%s" % '2'} + form_data = {"action": "security_groups__delete__%s" % sec_group.id} req = self.factory.post(INDEX_URL, form_data) - table = SecurityGroupsTable(req, self.security_groups) + table = SecurityGroupsTable(req, self.security_groups.list()) handled = table.maybe_handle() - self.assertEqual(strip_absolute_base(handled['location']), INDEX_URL) def test_delete_group_exception(self): + sec_group = self.security_groups.get(name="other_group") + self.mox.StubOutWithMock(api, 'security_group_delete') - exception = novaclient_exceptions.ClientException('ClientException', - message='ClientException') - api.security_group_delete(IsA(http.HttpRequest), 2).\ - AndRaise(exception) + exc = novaclient_exceptions.ClientException('ClientException') + api.security_group_delete(IsA(http.HttpRequest), + sec_group.id).AndRaise(exc) self.mox.ReplayAll() - form_data = {"action": "security_groups__delete__%s" % '2'} + form_data = {"action": "security_groups__delete__%s" % sec_group.id} req = self.factory.post(INDEX_URL, form_data) - table = SecurityGroupsTable(req, self.security_groups) + table = SecurityGroupsTable(req, self.security_groups.list()) handled = table.maybe_handle() self.assertEqual(strip_absolute_base(handled['location']), diff --git a/horizon/horizon/dashboards/nova/access_and_security/security_groups/views.py b/horizon/horizon/dashboards/nova/access_and_security/security_groups/views.py index 8414cb985..4edf2ba0d 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/security_groups/views.py +++ b/horizon/horizon/dashboards/nova/access_and_security/security_groups/views.py @@ -43,10 +43,10 @@ class EditRulesView(tables.DataTableView): template_name = 'nova/access_and_security/security_groups/edit_rules.html' def get_data(self): - security_group_id = self.kwargs['security_group_id'] + security_group_id = int(self.kwargs['security_group_id']) try: self.object = api.security_group_get(self.request, - security_group_id) + security_group_id) rules = [api.nova.SecurityGroupRule(rule) for rule in self.object.rules] except novaclient_exceptions.ClientException, e: diff --git a/horizon/horizon/dashboards/nova/access_and_security/tests.py b/horizon/horizon/dashboards/nova/access_and_security/tests.py index 50c4453eb..c100ae2ed 100644 --- a/horizon/horizon/dashboards/nova/access_and_security/tests.py +++ b/horizon/horizon/dashboards/nova/access_and_security/tests.py @@ -26,43 +26,19 @@ from horizon import api from horizon import test -class AccessAndSecurityTests(test.BaseViewTests): - def setUp(self): - super(AccessAndSecurityTests, self).setUp() - keypair = api.KeyPair(None) - keypair.name = 'keyName' - self.keypairs = (keypair,) - - server = api.Server(None, self.request) - server.id = 1 - server.name = 'serverName' - self.server = server - self.servers = (server, ) - - floating_ip = api.FloatingIp(None) - floating_ip.id = 1 - floating_ip.fixed_ip = '10.0.0.4' - floating_ip.instance_id = 1 - floating_ip.ip = '58.58.58.58' - - self.floating_ip = floating_ip - self.floating_ips = (floating_ip,) - - security_group = api.SecurityGroup(None) - security_group.id = '1' - security_group.name = 'default' - self.security_groups = (security_group,) - +class AccessAndSecurityTests(test.TestCase): def test_index(self): + keypairs = self.keypairs.list() + sec_groups = self.security_groups.list() + floating_ips = self.floating_ips.list() self.mox.StubOutWithMock(api, 'tenant_floating_ip_list') self.mox.StubOutWithMock(api, 'security_group_list') self.mox.StubOutWithMock(api.nova, 'keypair_list') - api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) - api.tenant_floating_ip_list(IsA(http.HttpRequest)).\ - AndReturn(self.floating_ips) - api.security_group_list(IsA(http.HttpRequest)).\ - AndReturn(self.security_groups) + api.nova.keypair_list(IsA(http.HttpRequest)).AndReturn(keypairs) + api.tenant_floating_ip_list(IsA(http.HttpRequest)) \ + .AndReturn(floating_ips) + api.security_group_list(IsA(http.HttpRequest)).AndReturn(sec_groups) self.mox.ReplayAll() @@ -70,9 +46,8 @@ class AccessAndSecurityTests(test.BaseViewTests): reverse('horizon:nova:access_and_security:index')) self.assertTemplateUsed(res, 'nova/access_and_security/index.html') - self.assertItemsEqual(res.context['keypairs_table'].data, - self.keypairs) + self.assertItemsEqual(res.context['keypairs_table'].data, keypairs) self.assertItemsEqual(res.context['security_groups_table'].data, - self.security_groups) + sec_groups) self.assertItemsEqual(res.context['floating_ips_table'].data, - self.floating_ips) + floating_ips) diff --git a/horizon/horizon/dashboards/nova/containers/tests.py b/horizon/horizon/dashboards/nova/containers/tests.py index 2e0d56b89..95e9ec3a3 100644 --- a/horizon/horizon/dashboards/nova/containers/tests.py +++ b/horizon/horizon/dashboards/nova/containers/tests.py @@ -28,257 +28,202 @@ from mox import IsA from horizon import api from horizon import test from .tables import ContainersTable, ObjectsTable +from . import forms CONTAINER_INDEX_URL = reverse('horizon:nova:containers:index') -class ContainerViewTests(test.BaseViewTests): - def setUp(self): - super(ContainerViewTests, self).setUp() - self.container = api.Container(None) - self.container.name = 'containerName' - self.container.size_used = 128 - self.containers = (self.container,) - +class ContainerViewTests(test.TestCase): def test_index(self): + containers = self.containers.list() self.mox.StubOutWithMock(api, 'swift_get_containers') - api.swift_get_containers( - IsA(http.HttpRequest), marker=None).AndReturn( - ([self.container], False)) - + api.swift_get_containers(IsA(http.HttpRequest), marker=None) \ + .AndReturn((containers, False)) self.mox.ReplayAll() res = self.client.get(CONTAINER_INDEX_URL) self.assertTemplateUsed(res, 'nova/containers/index.html') self.assertIn('table', res.context) - containers = res.context['table'].data - - self.assertEqual(len(containers), 1) - self.assertEqual(containers[0].name, 'containerName') + resp_containers = res.context['table'].data + self.assertEqual(len(resp_containers), len(containers)) def test_delete_container(self): + container = self.containers.get(name="container_two") self.mox.StubOutWithMock(api, 'swift_delete_container') - api.swift_delete_container(IsA(http.HttpRequest), - 'containerName') - + api.swift_delete_container(IsA(http.HttpRequest), container.name) self.mox.ReplayAll() - action_string = "containers__delete__%s" % self.container.name + action_string = "containers__delete__%s" % container.name form_data = {"action": action_string} req = self.factory.post(CONTAINER_INDEX_URL, form_data) - table = ContainersTable(req, self.containers) + table = ContainersTable(req, self.containers.list()) handled = table.maybe_handle() - self.assertEqual(handled['location'], CONTAINER_INDEX_URL) def test_delete_container_nonempty(self): + container = self.containers.first() self.mox.StubOutWithMock(api, 'swift_delete_container') - - exception = ContainerNotEmpty('containerNotEmpty') - api.swift_delete_container( - IsA(http.HttpRequest), - 'containerName').AndRaise(exception) - + exc = ContainerNotEmpty('containerNotEmpty') + api.swift_delete_container(IsA(http.HttpRequest), + container.name).AndRaise(exc) self.mox.ReplayAll() - action_string = "containers__delete__%s" % self.container.name + action_string = "containers__delete__%s" % container.name form_data = {"action": action_string} req = self.factory.post(CONTAINER_INDEX_URL, form_data) - table = ContainersTable(req, self.containers) + table = ContainersTable(req, self.containers.list()) handled = table.maybe_handle() - self.assertEqual(handled['location'], CONTAINER_INDEX_URL) def test_create_container_get(self): res = self.client.get(reverse('horizon:nova:containers:create')) - self.assertTemplateUsed(res, 'nova/containers/create.html') def test_create_container_post(self): - formData = {'name': 'containerName', - 'method': 'CreateContainer'} - self.mox.StubOutWithMock(api, 'swift_create_container') - api.swift_create_container( - IsA(http.HttpRequest), u'containerName') - + api.swift_create_container(IsA(http.HttpRequest), + self.containers.first().name) self.mox.ReplayAll() + formData = {'name': self.containers.first().name, + 'method': forms.CreateContainer.__name__} res = self.client.post(reverse('horizon:nova:containers:create'), formData) - self.assertRedirectsNoFollow(res, CONTAINER_INDEX_URL) -class ObjectViewTests(test.BaseViewTests): - CONTAINER_NAME = 'containerName' - - def setUp(self): - class FakeCloudFile(object): - def __init__(self): - self.metadata = {} - - def sync_metadata(self): - pass - - super(ObjectViewTests, self).setUp() - swift_object = api.swift.SwiftObject(FakeCloudFile()) - swift_object.name = u"test_object" - swift_object.size = '128' - swift_object.container = api.swift.Container(None) - swift_object.container.name = self.CONTAINER_NAME - self.swift_objects = [swift_object] - +class ObjectViewTests(test.TestCase): def test_index(self): self.mox.StubOutWithMock(api, 'swift_get_objects') - api.swift_get_objects( - IsA(http.HttpRequest), - self.CONTAINER_NAME, - marker=None).AndReturn((self.swift_objects, False)) - + ret = (self.objects.list(), False) + api.swift_get_objects(IsA(http.HttpRequest), + self.containers.first().name, + marker=None).AndReturn(ret) self.mox.ReplayAll() res = self.client.get(reverse('horizon:nova:containers:object_index', - args=[self.CONTAINER_NAME])) + args=[self.containers.first().name])) self.assertTemplateUsed(res, 'nova/objects/index.html') - self.assertItemsEqual(res.context['table'].data, self.swift_objects) + expected = [obj.name for obj in self.objects.list()] + self.assertQuerysetEqual(res.context['table'].data, + expected, + lambda obj: obj.name) def test_upload_index(self): res = self.client.get(reverse('horizon:nova:containers:object_upload', - args=[self.CONTAINER_NAME])) - + args=[self.containers.first().name])) self.assertTemplateUsed(res, 'nova/objects/upload.html') def test_upload(self): + container = self.containers.first() + obj = self.objects.first() OBJECT_DATA = 'objectData' - OBJECT_FILE = tempfile.TemporaryFile() - OBJECT_FILE.write(OBJECT_DATA) - OBJECT_FILE.flush() - OBJECT_FILE.seek(0) - OBJECT_NAME = 'objectName' - formData = {'method': 'UploadObject', - 'container_name': self.CONTAINER_NAME, - 'name': OBJECT_NAME, - 'object_file': OBJECT_FILE} + temp_file = tempfile.TemporaryFile() + temp_file.write(OBJECT_DATA) + temp_file.flush() + temp_file.seek(0) self.mox.StubOutWithMock(api, 'swift_upload_object') api.swift_upload_object(IsA(http.HttpRequest), - unicode(self.CONTAINER_NAME), - unicode(OBJECT_NAME), - OBJECT_DATA).AndReturn(self.swift_objects[0]) - + container.name, + obj.name, + OBJECT_DATA).AndReturn(obj) + self.mox.StubOutWithMock(obj, 'sync_metadata') + obj.sync_metadata() self.mox.ReplayAll() - - res = self.client.get(reverse('horizon:nova:containers:object_upload', - args=[self.CONTAINER_NAME])) - + upload_url = reverse('horizon:nova:containers:object_upload', + args=[container.name]) + res = self.client.get(upload_url) self.assertContains(res, 'enctype="multipart/form-data"') - res = self.client.post(reverse('horizon:nova:containers:object_upload', - args=[self.CONTAINER_NAME]), - formData) + formData = {'method': forms.UploadObject.__name__, + 'container_name': container.name, + 'name': obj.name, + 'object_file': temp_file} + res = self.client.post(upload_url, formData) - self.assertRedirectsNoFollow(res, - reverse('horizon:nova:containers:object_index', - args=[self.CONTAINER_NAME])) + index_url = reverse('horizon:nova:containers:object_index', + args=[container.name]) + self.assertRedirectsNoFollow(res, index_url) def test_delete(self): + container = self.containers.first() + obj = self.objects.first() + index_url = reverse('horizon:nova:containers:object_index', + args=[container.name]) self.mox.StubOutWithMock(api, 'swift_delete_object') - api.swift_delete_object( - IsA(http.HttpRequest), - self.CONTAINER_NAME, self.swift_objects[0].name) - + api.swift_delete_object(IsA(http.HttpRequest), + container.name, + obj.name) self.mox.ReplayAll() - OBJECT_INDEX_URL = reverse('horizon:nova:containers:object_index', - args=[self.CONTAINER_NAME]) - action_string = "objects__delete__%s" % self.swift_objects[0].name + action_string = "objects__delete__%s" % obj.name form_data = {"action": action_string} - req = self.factory.post(OBJECT_INDEX_URL, form_data) - kwargs = {"container_name": self.CONTAINER_NAME} - table = ObjectsTable(req, self.swift_objects, **kwargs) + req = self.factory.post(index_url, form_data) + kwargs = {"container_name": container.name} + table = ObjectsTable(req, self.objects.list(), **kwargs) handled = table.maybe_handle() - - self.assertEqual(handled['location'], OBJECT_INDEX_URL) + self.assertEqual(handled['location'], index_url) def test_download(self): + container = self.containers.first() + obj = self.objects.first() OBJECT_DATA = 'objectData' - OBJECT_NAME = 'objectName' self.mox.StubOutWithMock(api, 'swift_get_object_data') self.mox.StubOutWithMock(api.swift, 'swift_get_object') - api.swift.swift_get_object(IsA(http.HttpRequest), - unicode(self.CONTAINER_NAME), - unicode(OBJECT_NAME)) \ - .AndReturn(self.swift_objects[0]) + container.name, + obj.name).AndReturn(obj) api.swift_get_object_data(IsA(http.HttpRequest), - unicode(self.CONTAINER_NAME), - unicode(OBJECT_NAME)).AndReturn(OBJECT_DATA) - + container.name, + obj.name).AndReturn(OBJECT_DATA) self.mox.ReplayAll() - res = self.client.get(reverse( - 'horizon:nova:containers:object_download', - args=[self.CONTAINER_NAME, OBJECT_NAME])) - + download_url = reverse('horizon:nova:containers:object_download', + args=[container.name, obj.name]) + res = self.client.get(download_url) self.assertEqual(res.content, OBJECT_DATA) self.assertTrue(res.has_header('Content-Disposition')) def test_copy_index(self): - OBJECT_NAME = 'objectName' - - container = self.mox.CreateMock(api.Container) - container.name = self.CONTAINER_NAME - self.mox.StubOutWithMock(api, 'swift_get_containers') - api.swift_get_containers( - IsA(http.HttpRequest)).AndReturn(([container], False)) - + ret = (self.containers.list(), False) + api.swift_get_containers(IsA(http.HttpRequest)).AndReturn(ret) self.mox.ReplayAll() res = self.client.get(reverse('horizon:nova:containers:object_copy', - args=[self.CONTAINER_NAME, - OBJECT_NAME])) - + args=[self.containers.first().name, + self.objects.first().name])) self.assertTemplateUsed(res, 'nova/objects/copy.html') def test_copy(self): - NEW_CONTAINER_NAME = self.CONTAINER_NAME - NEW_OBJECT_NAME = 'newObjectName' - ORIG_CONTAINER_NAME = 'origContainerName' - ORIG_OBJECT_NAME = 'origObjectName' - - formData = {'method': 'CopyObject', - 'new_container_name': NEW_CONTAINER_NAME, - 'new_object_name': NEW_OBJECT_NAME, - 'orig_container_name': ORIG_CONTAINER_NAME, - 'orig_object_name': ORIG_OBJECT_NAME} - - container = self.mox.CreateMock(api.Container) - container.name = self.CONTAINER_NAME + container_1 = self.containers.get(name="container_one") + container_2 = self.containers.get(name="container_two") + obj = self.objects.first() self.mox.StubOutWithMock(api, 'swift_get_containers') - api.swift_get_containers( - IsA(http.HttpRequest)).AndReturn(([container], False)) - self.mox.StubOutWithMock(api, 'swift_copy_object') + ret = (self.containers.list(), False) + api.swift_get_containers(IsA(http.HttpRequest)).AndReturn(ret) api.swift_copy_object(IsA(http.HttpRequest), - ORIG_CONTAINER_NAME, - ORIG_OBJECT_NAME, - NEW_CONTAINER_NAME, - NEW_OBJECT_NAME) - + container_1.name, + obj.name, + container_2.name, + obj.name) self.mox.ReplayAll() - res = self.client.post(reverse('horizon:nova:containers:object_copy', - args=[ORIG_CONTAINER_NAME, - ORIG_OBJECT_NAME]), - formData) - - self.assertRedirectsNoFollow(res, - reverse('horizon:nova:containers:object_index', - args=[NEW_CONTAINER_NAME])) + formData = {'method': forms.CopyObject.__name__, + 'new_container_name': container_2.name, + 'new_object_name': obj.name, + 'orig_container_name': container_1.name, + 'orig_object_name': obj.name} + copy_url = reverse('horizon:nova:containers:object_copy', + args=[container_1.name, obj.name]) + res = self.client.post(copy_url, formData) + index_url = reverse('horizon:nova:containers:object_index', + args=[container_2.name]) + self.assertRedirectsNoFollow(res, index_url) diff --git a/horizon/horizon/dashboards/nova/images_and_snapshots/images/tests.py b/horizon/horizon/dashboards/nova/images_and_snapshots/images/tests.py index 675a4b815..0b0f06c58 100644 --- a/horizon/horizon/dashboards/nova/images_and_snapshots/images/tests.py +++ b/horizon/horizon/dashboards/nova/images_and_snapshots/images/tests.py @@ -19,10 +19,8 @@ # under the License. from django import http -from django.contrib import messages from django.core.urlresolvers import reverse from keystoneclient import exceptions as keystone_exceptions -from novaclient.v1_1 import client as nova_client, volume_snapshots from mox import IgnoreArg, IsA from horizon import api @@ -32,116 +30,48 @@ from horizon import test IMAGES_INDEX_URL = reverse('horizon:nova:images_and_snapshots:index') -class FakeQuota: - ram = 100 - - -class ImageViewTests(test.BaseViewTests): - def setUp(self): - super(ImageViewTests, self).setUp() - image_dict = {'name': 'visibleImage', - 'container_format': 'novaImage'} - self.visibleImage = api.Image(image_dict) - self.visibleImage.id = 1 - - image_dict = {'name': 'invisibleImage', - 'container_format': 'aki'} - self.invisibleImage = api.Image(image_dict) - self.invisibleImage.id = 2 - - self.images = (self.visibleImage, self.invisibleImage) - - flavor = api.Flavor(None) - flavor.id = 1 - flavor.name = 'm1.massive' - flavor.vcpus = 1000 - flavor.disk = 1024 - flavor.ram = 10000 - self.flavors = (flavor,) - - keypair = api.KeyPair(None) - keypair.name = 'keyName' - self.keypairs = (keypair,) - - security_group = api.SecurityGroup(None) - security_group.name = 'default' - self.security_groups = (security_group,) - - volume = api.Volume(None) - volume.id = 1 - volume.name = 'vol' - volume.status = 'available' - volume.size = 40 - volume.displayName = '' - self.volumes = (volume,) - - self.volume_snapshot = volume_snapshots.Snapshot( - volume_snapshots.SnapshotManager, - {'id': 2, - 'displayName': 'test snapshot', - 'displayDescription': 'test snapshot description', - 'size': 40, - 'status': 'available', - 'volumeId': 1}) - self.volume_snapshots = [self.volume_snapshot] - +class ImageViewTests(test.TestCase): def test_launch_get(self): - IMAGE_ID = 1 + image = self.images.first() + tenant = self.tenants.first() + quota = self.quotas.first() self.mox.StubOutWithMock(api, 'image_get_meta') self.mox.StubOutWithMock(api, 'tenant_quota_get') self.mox.StubOutWithMock(api, 'flavor_list') self.mox.StubOutWithMock(api, 'keypair_list') self.mox.StubOutWithMock(api, 'security_group_list') - - api.image_get_meta(IsA(http.HttpRequest), str(IMAGE_ID)) \ - .AndReturn(self.visibleImage) - - api.tenant_quota_get(IsA(http.HttpRequest), - self.TEST_TENANT).AndReturn(FakeQuota) - - api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors) - - api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) - - api.security_group_list(IsA(http.HttpRequest)).AndReturn( - self.security_groups) - + api.image_get_meta(IsA(http.HttpRequest), image.id).AndReturn(image) + api.tenant_quota_get(IsA(http.HttpRequest), tenant.id).AndReturn(quota) + api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list()) + api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs.list()) + api.security_group_list(IsA(http.HttpRequest)) \ + .AndReturn(self.security_groups.list()) self.mox.ReplayAll() - res = self.client.get( - reverse('horizon:nova:images_and_snapshots:images:launch', - args=[IMAGE_ID])) + url = reverse('horizon:nova:images_and_snapshots:images:launch', + args=[image.id]) + res = self.client.get(url) form = res.context['form'] - self.assertTemplateUsed(res, 'nova/images_and_snapshots/images/launch.html') - self.assertEqual(res.context['image'].name, self.visibleImage.name) - self.assertIn('m1.massive', form.fields['flavor'].choices[0][1]) - self.assertEqual(form.fields['keypair'].choices[0][0], - self.keypairs[0].name) + self.assertEqual(res.context['image'].name, image.name) + self.assertIn(self.flavors.first().name, + form.fields['flavor'].choices[0][1]) + self.assertEqual(self.keypairs.first().name, + form.fields['keypair'].choices[0][0]) def test_launch_post(self): - FLAVOR_ID = unicode(self.flavors[0].id) - IMAGE_ID = u'1' - keypair = unicode(self.keypairs[0].name) - SERVER_NAME = u'serverName' - USER_DATA = u'userData' - volume = u'%s:vol' % self.volumes[0].id + flavor = self.flavors.first() + image = self.images.first() + keypair = self.keypairs.first() + server = self.servers.first() + volume = self.volumes.first() + sec_group = self.security_groups.first() + USER_DATA = 'user data' device_name = u'vda' - BLOCK_DEVICE_MAPPING = {device_name: u"1:vol::0"} - - form_data = {'method': 'LaunchForm', - 'flavor': FLAVOR_ID, - 'image_id': IMAGE_ID, - 'keypair': keypair, - 'name': SERVER_NAME, - 'user_data': USER_DATA, - 'count': 1, - 'tenant_id': self.TEST_TENANT, - 'security_groups': 'default', - 'volume': volume, - 'device_name': device_name} + volume_choice = "%s:vol" % volume.id + block_device_mapping = {device_name: u"%s::0" % volume_choice} self.mox.StubOutWithMock(api, 'image_get_meta') self.mox.StubOutWithMock(api, 'flavor_list') @@ -150,98 +80,97 @@ class ImageViewTests(test.BaseViewTests): self.mox.StubOutWithMock(api, 'server_create') self.mox.StubOutWithMock(api, 'volume_list') - api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors) - api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) - api.security_group_list(IsA(http.HttpRequest)).AndReturn( - self.security_groups) - api.image_get_meta(IsA(http.HttpRequest), IMAGE_ID).AndReturn( - self.visibleImage) - api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes) - api.server_create(IsA(http.HttpRequest), SERVER_NAME, - str(IMAGE_ID), str(FLAVOR_ID), - keypair, USER_DATA, [self.security_groups[0].name], - BLOCK_DEVICE_MAPPING, + api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list()) + api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs.list()) + api.security_group_list(IsA(http.HttpRequest)) \ + .AndReturn(self.security_groups.list()) + api.image_get_meta(IsA(http.HttpRequest), image.id).AndReturn(image) + api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list()) + api.server_create(IsA(http.HttpRequest), + server.name, + image.id, + flavor.id, + keypair.name, + USER_DATA, + [sec_group.name], + block_device_mapping, instance_count=IsA(int)) - self.mox.ReplayAll() - res = self.client.post( - reverse('horizon:nova:images_and_snapshots:images:launch', - args=[IMAGE_ID]), - form_data) - + form_data = {'method': 'LaunchForm', + 'flavor': flavor.id, + 'image_id': image.id, + 'keypair': keypair.name, + 'name': server.name, + 'user_data': USER_DATA, + 'tenant_id': self.tenants.first().id, + 'security_groups': sec_group.name, + 'volume': volume_choice, + 'device_name': device_name, + 'count': 1} + url = reverse('horizon:nova:images_and_snapshots:images:launch', + args=[image.id]) + res = self.client.post(url, form_data) + self.assertNoFormErrors(res) self.assertRedirectsNoFollow(res, reverse('horizon:nova:instances_and_volumes:index')) def test_launch_flavorlist_error(self): - IMAGE_ID = '1' + image = self.images.first() self.mox.StubOutWithMock(api, 'image_get_meta') self.mox.StubOutWithMock(api, 'tenant_quota_get') self.mox.StubOutWithMock(api, 'flavor_list') self.mox.StubOutWithMock(api, 'keypair_list') self.mox.StubOutWithMock(api, 'security_group_list') - api.image_get_meta(IsA(http.HttpRequest), - IMAGE_ID).AndReturn(self.visibleImage) + image.id).AndReturn(image) api.tenant_quota_get(IsA(http.HttpRequest), - self.TEST_TENANT).AndReturn(FakeQuota) - exception = keystone_exceptions.ClientException('Failed.') - api.flavor_list(IsA(http.HttpRequest)).AndRaise(exception) - api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs) - api.security_group_list(IsA(http.HttpRequest)).AndReturn( - self.security_groups) - + self.tenant.id).AndReturn(self.quotas.first()) + exc = keystone_exceptions.ClientException('Failed.') + api.flavor_list(IsA(http.HttpRequest)).AndRaise(exc) + api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs.list()) + api.security_group_list(IsA(http.HttpRequest)) \ + .AndReturn(self.security_groups.list()) self.mox.ReplayAll() - res = self.client.get( - reverse('horizon:nova:images_and_snapshots:images:launch', - args=[IMAGE_ID])) - + url = reverse('horizon:nova:images_and_snapshots:images:launch', + args=[image.id]) + res = self.client.get(url) self.assertTemplateUsed(res, 'nova/images_and_snapshots/images/launch.html') def test_launch_keypairlist_error(self): - IMAGE_ID = '2' + image = self.images.first() self.mox.StubOutWithMock(api, 'image_get_meta') - api.image_get_meta(IsA(http.HttpRequest), - IMAGE_ID).AndReturn(self.visibleImage) - self.mox.StubOutWithMock(api, 'tenant_quota_get') - api.tenant_quota_get(IsA(http.HttpRequest), - self.TEST_TENANT).AndReturn(FakeQuota) - self.mox.StubOutWithMock(api, 'flavor_list') - api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors) - - exception = keystone_exceptions.ClientException('Failed.') self.mox.StubOutWithMock(api, 'keypair_list') - api.keypair_list(IsA(http.HttpRequest)).AndRaise(exception) - self.mox.StubOutWithMock(api, 'security_group_list') - api.security_group_list(IsA(http.HttpRequest)).AndReturn( - self.security_groups) - + api.image_get_meta(IsA(http.HttpRequest), image.id).AndReturn(image) + api.tenant_quota_get(IsA(http.HttpRequest), + self.tenant.id).AndReturn(self.quotas.first()) + api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors.list()) + exception = keystone_exceptions.ClientException('Failed.') + api.keypair_list(IsA(http.HttpRequest)).AndRaise(exception) + api.security_group_list(IsA(http.HttpRequest)) \ + .AndReturn(self.security_groups.list()) self.mox.ReplayAll() - res = self.client.get( - reverse('horizon:nova:images_and_snapshots:images:launch', - args=[IMAGE_ID])) - + url = reverse('horizon:nova:images_and_snapshots:images:launch', + args=[image.id]) + res = self.client.get(url) self.assertTemplateUsed(res, 'nova/images_and_snapshots/images/launch.html') - - form = res.context['form'] - - form_keyfield = form.fields['keypair'] - self.assertEqual(len(form_keyfield.choices), 0) + self.assertEqual(len(res.context['form'].fields['keypair'].choices), 0) def test_launch_form_keystone_exception(self): - FLAVOR_ID = self.flavors[0].id - IMAGE_ID = '1' - keypair = self.keypairs[0].name - SERVER_NAME = 'serverName' + flavor = self.flavors.first() + image = self.images.first() + keypair = self.keypairs.first() + server = self.servers.first() + sec_group = self.security_groups.first() USER_DATA = 'userData' self.mox.StubOutWithMock(api, 'image_get_meta') @@ -251,37 +180,34 @@ class ImageViewTests(test.BaseViewTests): self.mox.StubOutWithMock(api, 'server_create') self.mox.StubOutWithMock(api, 'volume_list') - form_data = {'method': 'LaunchForm', - 'flavor': FLAVOR_ID, - 'image_id': IMAGE_ID, - 'keypair': keypair, - 'name': SERVER_NAME, - 'tenant_id': self.TEST_TENANT, - 'user_data': USER_DATA, - 'count': int(1), - 'security_groups': 'default'} - - api.flavor_list(IgnoreArg()).AndReturn(self.flavors) - api.keypair_list(IgnoreArg()).AndReturn(self.keypairs) - api.security_group_list(IsA(http.HttpRequest)).AndReturn( - self.security_groups) - api.image_get_meta(IgnoreArg(), IMAGE_ID).AndReturn(self.visibleImage) - api.volume_list(IgnoreArg()).AndReturn(self.volumes) - - exception = keystone_exceptions.ClientException('Failed') + api.flavor_list(IgnoreArg()).AndReturn(self.flavors.list()) + api.keypair_list(IgnoreArg()).AndReturn(self.keypairs.list()) + api.security_group_list(IsA(http.HttpRequest)) \ + .AndReturn(self.security_groups.list()) + api.image_get_meta(IgnoreArg(), image.id).AndReturn(image) + api.volume_list(IgnoreArg()).AndReturn(self.volumes.list()) + exc = keystone_exceptions.ClientException('Failed') api.server_create(IsA(http.HttpRequest), - SERVER_NAME, - IMAGE_ID, - str(FLAVOR_ID), - keypair, + server.name, + image.id, + flavor.id, + keypair.name, USER_DATA, - [group.name for group in self.security_groups], + [sec_group.name], None, - instance_count=IsA(int)).AndRaise(exception) - + instance_count=IsA(int)).AndRaise(exc) self.mox.ReplayAll() - url = reverse('horizon:nova:images_and_snapshots:images:launch', - args=[IMAGE_ID]) - res = self.client.post(url, form_data) + form_data = {'method': 'LaunchForm', + 'flavor': flavor.id, + 'image_id': image.id, + 'keypair': keypair.name, + 'name': server.name, + 'tenant_id': self.tenant.id, + 'user_data': USER_DATA, + 'count': 1, + 'security_groups': sec_group.name} + url = reverse('horizon:nova:images_and_snapshots:images:launch', + args=[image.id]) + res = self.client.post(url, form_data) self.assertRedirectsNoFollow(res, IMAGES_INDEX_URL) diff --git a/horizon/horizon/dashboards/nova/images_and_snapshots/snapshots/tests.py b/horizon/horizon/dashboards/nova/images_and_snapshots/snapshots/tests.py index d60b4341e..62c8c1ac0 100644 --- a/horizon/horizon/dashboards/nova/images_and_snapshots/snapshots/tests.py +++ b/horizon/horizon/dashboards/nova/images_and_snapshots/snapshots/tests.py @@ -30,144 +30,84 @@ from horizon import test INDEX_URL = reverse('horizon:nova:images_and_snapshots:index') -class SnapshotsViewTests(test.BaseViewTests): - def setUp(self): - super(SnapshotsViewTests, self).setUp() - image_dict = {'name': 'snapshot', - 'container_format': 'novaImage', - 'id': 3} - self.images = [image_dict] - - server = api.Server(None, self.request) - server.id = 1 - server.status = 'ACTIVE' - server.name = 'sgoody' - self.good_server = server - - server = api.Server(None, self.request) - server.id = 2 - server.status = 'BUILD' - server.name = 'baddy' - self.bad_server = server - - flavor = api.Flavor(None) - flavor.id = 1 - flavor.name = 'm1.massive' - flavor.vcpus = 1000 - flavor.disk = 1024 - flavor.ram = 10000 - self.flavors = (flavor,) - - keypair = api.KeyPair(None) - keypair.name = 'keyName' - self.keypairs = (keypair,) - - security_group = api.SecurityGroup(None) - security_group.name = 'default' - self.security_groups = (security_group,) - +class SnapshotsViewTests(test.TestCase): def test_create_snapshot_get(self): + server = self.servers.first() self.mox.StubOutWithMock(api, 'server_get') - api.server_get(IsA(http.HttpRequest), - str(self.good_server.id)).AndReturn(self.good_server) - + api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server) self.mox.ReplayAll() - res = self.client.get( - reverse('horizon:nova:images_and_snapshots:snapshots:create', - args=[self.good_server.id])) - + url = reverse('horizon:nova:images_and_snapshots:snapshots:create', + args=[server.id]) + res = self.client.get(url) self.assertTemplateUsed(res, 'nova/images_and_snapshots/snapshots/create.html') def test_create_snapshot_get_with_invalid_status(self): + server = self.servers.get(status='BUILD') self.mox.StubOutWithMock(api, 'server_get') - api.server_get(IsA(http.HttpRequest), - str(self.bad_server.id)).AndReturn(self.bad_server) - + api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server) self.mox.ReplayAll() - res = self.client.get( - reverse('horizon:nova:images_and_snapshots:snapshots:create', - args=[self.bad_server.id])) - - url = reverse("horizon:nova:instances_and_volumes:index") - self.assertRedirectsNoFollow(res, url) + url = reverse('horizon:nova:images_and_snapshots:snapshots:create', + args=[server.id]) + res = self.client.get(url) + redirect = reverse("horizon:nova:instances_and_volumes:index") + self.assertRedirectsNoFollow(res, redirect) def test_create_get_server_exception(self): + server = self.servers.first() self.mox.StubOutWithMock(api, 'server_get') - exception = novaclient_exceptions.ClientException('apiException') - api.server_get(IsA(http.HttpRequest), - str(self.good_server.id)).AndRaise(exception) - + exc = novaclient_exceptions.ClientException('apiException') + api.server_get(IsA(http.HttpRequest), server.id).AndRaise(exc) self.mox.ReplayAll() - res = self.client.get( - reverse('horizon:nova:images_and_snapshots:snapshots:create', - args=[self.good_server.id])) - - url = reverse("horizon:nova:instances_and_volumes:index") - self.assertRedirectsNoFollow(res, url) + url = reverse('horizon:nova:images_and_snapshots:snapshots:create', + args=[server.id]) + res = self.client.get(url) + redirect = reverse("horizon:nova:instances_and_volumes:index") + self.assertRedirectsNoFollow(res, redirect) def test_create_snapshot_post(self): - SNAPSHOT_NAME = 'snappy' - - new_snapshot = self.mox.CreateMock(api.Image) - new_snapshot.name = SNAPSHOT_NAME - - formData = {'method': 'CreateSnapshot', - 'tenant_id': self.TEST_TENANT, - 'instance_id': self.good_server.id, - 'name': SNAPSHOT_NAME} + server = self.servers.first() + snapshot = self.snapshots.first() self.mox.StubOutWithMock(api, 'server_get') self.mox.StubOutWithMock(api, 'snapshot_create') - - api.server_get(IsA(http.HttpRequest), - str(self.good_server.id)).AndReturn(self.good_server) - api.snapshot_create(IsA(http.HttpRequest), - str(self.good_server.id), SNAPSHOT_NAME).\ - AndReturn(new_snapshot) - api.server_get(IsA(http.HttpRequest), - str(self.good_server.id)).AndReturn(self.good_server) - + api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server) + api.snapshot_create(IsA(http.HttpRequest), server.id, snapshot.name) \ + .AndReturn(snapshot) + api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server) self.mox.ReplayAll() - res = self.client.post( - reverse('horizon:nova:images_and_snapshots:snapshots:create', - args=[self.good_server.id]), - formData) + formData = {'method': 'CreateSnapshot', + 'tenant_id': self.tenant.id, + 'instance_id': server.id, + 'name': snapshot.name} + url = reverse('horizon:nova:images_and_snapshots:snapshots:create', + args=[server.id]) + res = self.client.post(url, formData) self.assertRedirectsNoFollow(res, INDEX_URL) def test_create_snapshot_post_exception(self): - SNAPSHOT_NAME = 'snappy' - - new_snapshot = self.mox.CreateMock(api.Image) - new_snapshot.name = SNAPSHOT_NAME - - formData = {'method': 'CreateSnapshot', - 'tenant_id': self.TEST_TENANT, - 'instance_id': self.good_server.id, - 'name': SNAPSHOT_NAME} + server = self.servers.first() + snapshot = self.snapshots.first() self.mox.StubOutWithMock(api, 'server_get') self.mox.StubOutWithMock(api, 'snapshot_create') - - api.server_get(IsA(http.HttpRequest), - str(self.good_server.id)).AndReturn(self.good_server) - exception = novaclient_exceptions.ClientException('apiException', - message='apiException') - api.snapshot_create(IsA(http.HttpRequest), - str(self.good_server.id), SNAPSHOT_NAME).\ - AndRaise(exception) - + api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server) + exc = novaclient_exceptions.ClientException('apiException') + api.snapshot_create(IsA(http.HttpRequest), server.id, snapshot.name) \ + .AndRaise(exc) self.mox.ReplayAll() - res = self.client.post( - reverse('horizon:nova:images_and_snapshots:snapshots:create', - args=[self.good_server.id]), - formData) - - url = reverse("horizon:nova:instances_and_volumes:index") - self.assertRedirectsNoFollow(res, url) + formData = {'method': 'CreateSnapshot', + 'tenant_id': self.tenant.id, + 'instance_id': server.id, + 'name': snapshot.name} + url = reverse('horizon:nova:images_and_snapshots:snapshots:create', + args=[server.id]) + res = self.client.post(url, formData) + redirect = reverse("horizon:nova:instances_and_volumes:index") + self.assertRedirectsNoFollow(res, redirect) diff --git a/horizon/horizon/dashboards/nova/images_and_snapshots/tests.py b/horizon/horizon/dashboards/nova/images_and_snapshots/tests.py index df911c6ea..e4e5a73c8 100644 --- a/horizon/horizon/dashboards/nova/images_and_snapshots/tests.py +++ b/horizon/horizon/dashboards/nova/images_and_snapshots/tests.py @@ -31,97 +31,43 @@ from horizon import test INDEX_URL = reverse('horizon:nova:images_and_snapshots:index') -class ImagesAndSnapshotsTests(test.BaseViewTests): - def setUp(self): - super(ImagesAndSnapshotsTests, self).setUp() - snapshot_properties = api.glance.ImageProperties(None) - snapshot_properties.image_type = u'snapshot' - - snapshot_dict = {'name': u'snapshot', - 'container_format': u'ami', - 'id': 3} - snapshot = api.glance.Image(snapshot_dict) - snapshot.properties = snapshot_properties - self.snapshots = [snapshot] - - image_properties = api.glance.ImageProperties(None) - image_properties.image_type = u'image' - - image_dict = {'name': u'visibleImage', - 'container_format': u'novaImage'} - self.visibleImage = api.glance.Image(image_dict) - self.visibleImage.id = '1' - self.visibleImage.properties = image_properties - - image_dict = {'name': 'invisibleImage', - 'container_format': 'aki'} - self.invisibleImage = api.Image(image_dict) - self.invisibleImage.id = '2' - - flavor = api.Flavor(None) - flavor.id = 1 - flavor.name = 'm1.massive' - flavor.vcpus = 1000 - flavor.disk = 1024 - flavor.ram = 10000 - self.flavors = (flavor,) - - self.images = (self.visibleImage, self.invisibleImage) - - keypair = api.KeyPair(None) - keypair.name = 'keyName' - self.keypairs = (keypair,) - - security_group = api.SecurityGroup(None) - security_group.name = 'default' - self.security_groups = (security_group,) - +class ImagesAndSnapshotsTests(test.TestCase): def test_index(self): + images = self.images.list() + snapshots = self.snapshots.list() self.mox.StubOutWithMock(api, 'image_list_detailed') - api.image_list_detailed(IsA(http.HttpRequest)).AndReturn(self.images) - self.mox.StubOutWithMock(api, 'snapshot_list_detailed') - api.snapshot_list_detailed(IsA(http.HttpRequest)).AndReturn( - self.snapshots) - + api.image_list_detailed(IsA(http.HttpRequest)).AndReturn(images) + api.snapshot_list_detailed(IsA(http.HttpRequest)).AndReturn(snapshots) self.mox.ReplayAll() res = self.client.get(INDEX_URL) - - self.assertTemplateUsed(res, - 'nova/images_and_snapshots/index.html') - + self.assertTemplateUsed(res, 'nova/images_and_snapshots/index.html') self.assertIn('images_table', res.context) images = res.context['images_table'].data - self.assertEqual(len(images), 1) - self.assertEqual(images[0].name, 'visibleImage') + filter_func = lambda im: im.container_format not in ['aki', 'ari'] + filtered_images = filter(filter_func, images) + self.assertItemsEqual(images, filtered_images) def test_index_no_images(self): self.mox.StubOutWithMock(api, 'snapshot_list_detailed') self.mox.StubOutWithMock(api, 'image_list_detailed') - api.image_list_detailed(IsA(http.HttpRequest)).AndReturn([]) - api.snapshot_list_detailed(IsA(http.HttpRequest)).\ - AndReturn(self.snapshots) - + api.snapshot_list_detailed(IsA(http.HttpRequest)) \ + .AndReturn(self.snapshots.list()) self.mox.ReplayAll() res = self.client.get(INDEX_URL) - self.assertTemplateUsed(res, 'nova/images_and_snapshots/index.html') def test_index_client_conn_error(self): - self.mox.StubOutWithMock(api, 'image_list_detailed') self.mox.StubOutWithMock(api, 'snapshot_list_detailed') - - exception = glance_exception.ClientConnectionError('clientConnError') - api.image_list_detailed(IsA(http.HttpRequest)).AndRaise(exception) - api.snapshot_list_detailed(IsA(http.HttpRequest)).\ - AndReturn(self.snapshots) - + exc = glance_exception.ClientConnectionError('clientConnError') + api.image_list_detailed(IsA(http.HttpRequest)).AndRaise(exc) + api.snapshot_list_detailed(IsA(http.HttpRequest)) \ + .AndReturn(self.snapshots.list()) self.mox.ReplayAll() res = self.client.get(INDEX_URL) - self.assertTemplateUsed(res, 'nova/images_and_snapshots/index.html') diff --git a/horizon/horizon/dashboards/nova/images_and_snapshots/volume_snapshots/tests.py b/horizon/horizon/dashboards/nova/images_and_snapshots/volume_snapshots/tests.py index d98200718..a6315f80c 100644 --- a/horizon/horizon/dashboards/nova/images_and_snapshots/volume_snapshots/tests.py +++ b/horizon/horizon/dashboards/nova/images_and_snapshots/volume_snapshots/tests.py @@ -20,7 +20,6 @@ from django import http from django.core.urlresolvers import reverse -from novaclient.v1_1 import volume_snapshots from mox import IsA from horizon import api @@ -30,49 +29,34 @@ from horizon import test INDEX_URL = reverse('horizon:nova:images_and_snapshots:index') -class SnapshotsViewTests(test.BaseViewTests): +class VolumeSnapshotsViewTests(test.TestCase): def test_create_snapshot_get(self): - VOLUME_ID = u'1' - + volume = self.volumes.first() res = self.client.get(reverse('horizon:nova:instances_and_volumes:' 'volumes:create_snapshot', - args=[VOLUME_ID])) + args=[volume.id])) self.assertTemplateUsed(res, 'nova/instances_and_volumes/' 'volumes/create_snapshot.html') def test_create_snapshot_post(self): - VOLUME_ID = u'1' - SNAPSHOT_NAME = u'vol snap' - SNAPSHOT_DESCRIPTION = u'vol snap desc' - - volume_snapshot = volume_snapshots.Snapshot( - volume_snapshots.SnapshotManager, - {'id': 1, - 'displayName': 'test snapshot', - 'displayDescription': 'test snapshot description', - 'size': 40, - 'status': 'available', - 'volumeId': 1}) - - formData = {'method': 'CreateSnapshotForm', - 'tenant_id': self.TEST_TENANT, - 'volume_id': VOLUME_ID, - 'name': SNAPSHOT_NAME, - 'description': SNAPSHOT_DESCRIPTION} + volume = self.volumes.first() + snapshot = self.volume_snapshots.first() self.mox.StubOutWithMock(api, 'volume_snapshot_create') - - api.volume_snapshot_create( - IsA(http.HttpRequest), str(VOLUME_ID), SNAPSHOT_NAME, - SNAPSHOT_DESCRIPTION).AndReturn(volume_snapshot) - + api.volume_snapshot_create(IsA(http.HttpRequest), + volume.id, + snapshot.displayName, + snapshot.displayDescription) \ + .AndReturn(snapshot) self.mox.ReplayAll() - res = self.client.post( - reverse('horizon:nova:instances_and_volumes:volumes:' - 'create_snapshot', - args=[VOLUME_ID]), - formData) - + formData = {'method': 'CreateSnapshotForm', + 'tenant_id': self.tenant.id, + 'volume_id': volume.id, + 'name': snapshot.displayName, + 'description': snapshot.displayDescription} + url = reverse('horizon:nova:instances_and_volumes:volumes:' + 'create_snapshot', args=[volume.id]) + res = self.client.post(url, formData) self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/horizon/horizon/dashboards/nova/instances_and_volumes/instances/tests.py b/horizon/horizon/dashboards/nova/instances_and_volumes/instances/tests.py index 4526e038b..26db7a461 100644 --- a/horizon/horizon/dashboards/nova/instances_and_volumes/instances/tests.py +++ b/horizon/horizon/dashboards/nova/instances_and_volumes/instances/tests.py @@ -30,114 +30,87 @@ from horizon import test INDEX_URL = reverse('horizon:nova:instances_and_volumes:index') -class InstanceViewTests(test.BaseViewTests): +class InstanceViewTests(test.TestCase): def setUp(self): super(InstanceViewTests, self).setUp() self.now = self.override_times() - server = api.Server(None, self.request) - server.id = "1" - server.name = 'serverName' - server.status = "ACTIVE" - server.flavor = {'id': '1'} - - flavor = api.nova.Flavor(None) - flavor.id = '1' - - volume = api.Volume(self.request) - volume.id = "1" - - self.servers = (server,) - self.volumes = (volume,) - self.flavors = (flavor,) - def tearDown(self): super(InstanceViewTests, self).tearDown() self.reset_times() def test_terminate_instance(self): + server = self.servers.first() self.mox.StubOutWithMock(api, 'server_list') self.mox.StubOutWithMock(api, 'flavor_list') self.mox.StubOutWithMock(api, 'server_delete') - - api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers) - api.flavor_list(IgnoreArg()).AndReturn(self.flavors) - api.server_delete(IsA(http.HttpRequest), - self.servers[0].id) - + api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list()) + api.flavor_list(IgnoreArg()).AndReturn(self.flavors.list()) + api.server_delete(IsA(http.HttpRequest), server.id) self.mox.ReplayAll() - formData = {'action': 'instances__terminate__%s' % self.servers[0].id} + formData = {'action': 'instances__terminate__%s' % server.id} res = self.client.post(INDEX_URL, formData) - self.assertRedirectsNoFollow(res, INDEX_URL) def test_terminate_instance_exception(self): + server = self.servers.first() self.mox.StubOutWithMock(api, 'server_list') self.mox.StubOutWithMock(api, 'flavor_list') self.mox.StubOutWithMock(api, 'server_delete') - - api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers) - api.flavor_list(IgnoreArg()).AndReturn(self.flavors) - exception = nova_exceptions.ClientException(500) - api.server_delete(IsA(http.HttpRequest), - self.servers[0].id).AndRaise(exception) - + api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list()) + api.flavor_list(IgnoreArg()).AndReturn(self.flavors.list()) + exc = nova_exceptions.ClientException(500) + api.server_delete(IsA(http.HttpRequest), server.id).AndRaise(exc) self.mox.ReplayAll() - formData = {'action': 'instances__terminate__%s' % self.servers[0].id} + formData = {'action': 'instances__terminate__%s' % server.id} res = self.client.post(INDEX_URL, formData) - self.assertRedirectsNoFollow(res, INDEX_URL) def test_reboot_instance(self): + server = self.servers.first() self.mox.StubOutWithMock(api, 'server_reboot') self.mox.StubOutWithMock(api, 'server_list') - api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers) - api.server_reboot(IsA(http.HttpRequest), unicode(self.servers[0].id)) - + api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list()) + api.server_reboot(IsA(http.HttpRequest), server.id) self.mox.ReplayAll() - formData = {'action': 'instances__reboot__%s' % self.servers[0].id} + formData = {'action': 'instances__reboot__%s' % server.id} res = self.client.post(INDEX_URL, formData) - self.assertRedirectsNoFollow(res, INDEX_URL) def test_reboot_instance_exception(self): + server = self.servers.first() self.mox.StubOutWithMock(api, 'server_reboot') self.mox.StubOutWithMock(api, 'server_list') - api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers) - exception = nova_exceptions.ClientException(500) - api.server_reboot(IsA(http.HttpRequest), - unicode(self.servers[0].id)).AndRaise(exception) - + api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list()) + exc = nova_exceptions.ClientException(500) + api.server_reboot(IsA(http.HttpRequest), server.id).AndRaise(exc) self.mox.ReplayAll() - formData = {'action': 'instances__reboot__%s' % self.servers[0].id} + formData = {'action': 'instances__reboot__%s' % server.id} res = self.client.post(INDEX_URL, formData) - self.assertRedirectsNoFollow(res, INDEX_URL) def test_instance_console(self): + server = self.servers.first() CONSOLE_OUTPUT = 'output' - INSTANCE_ID = self.servers[0].id self.mox.StubOutWithMock(api, 'server_console_output') api.server_console_output(IsA(http.HttpRequest), - unicode(INSTANCE_ID), + server.id, tail_length=None).AndReturn(CONSOLE_OUTPUT) - self.mox.ReplayAll() - res = self.client.get( - reverse('horizon:nova:instances_and_volumes:instances:console', - args=[INSTANCE_ID])) - + url = reverse('horizon:nova:instances_and_volumes:instances:console', + args=[server.id]) + res = self.client.get(url) self.assertIsInstance(res, http.HttpResponse) self.assertContains(res, CONSOLE_OUTPUT) def test_instance_vnc(self): - INSTANCE_ID = self.servers[0].id + server = self.servers.first() CONSOLE_OUTPUT = '/vncserver' console_mock = self.mox.CreateMock(api.VNCConsole) @@ -145,113 +118,88 @@ class InstanceViewTests(test.BaseViewTests): self.mox.StubOutWithMock(api, 'server_vnc_console') self.mox.StubOutWithMock(api, 'server_get') - api.server_get(IsA(http.HttpRequest), - str(self.servers[0].id)).AndReturn(self.servers[0]) - api.server_vnc_console(IgnoreArg(), - unicode(INSTANCE_ID)).AndReturn(console_mock) - + api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server) + api.server_vnc_console(IgnoreArg(), server.id).AndReturn(console_mock) self.mox.ReplayAll() - res = self.client.get( - reverse('horizon:nova:instances_and_volumes:instances:vnc', - args=[INSTANCE_ID])) - - self.assertRedirectsNoFollow(res, - CONSOLE_OUTPUT + '&title=serverName(1)') + url = reverse('horizon:nova:instances_and_volumes:instances:vnc', + args=[server.id]) + res = self.client.get(url) + redirect = CONSOLE_OUTPUT + '&title=%s(1)' % server.name + self.assertRedirectsNoFollow(res, redirect) def test_instance_vnc_exception(self): - INSTANCE_ID = self.servers[0].id - - exception = nova_exceptions.ClientException(500) + server = self.servers.first() self.mox.StubOutWithMock(api, 'server_vnc_console') - api.server_vnc_console(IsA(http.HttpRequest), - unicode(INSTANCE_ID)).AndRaise(exception) - + exc = nova_exceptions.ClientException(500) + api.server_vnc_console(IsA(http.HttpRequest), server.id).AndRaise(exc) self.mox.ReplayAll() - res = self.client.get( - reverse('horizon:nova:instances_and_volumes:instances:vnc', - args=[INSTANCE_ID])) - + url = reverse('horizon:nova:instances_and_volumes:instances:vnc', + args=[server.id]) + res = self.client.get(url) self.assertRedirectsNoFollow(res, INDEX_URL) def test_instance_update_get(self): - INSTANCE_ID = self.servers[0].id + server = self.servers.first() self.mox.StubOutWithMock(api, 'server_get') - api.server_get(IsA(http.HttpRequest), - unicode(INSTANCE_ID)).AndReturn(self.servers[0]) - + api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server) self.mox.ReplayAll() - res = self.client.get( - reverse('horizon:nova:instances_and_volumes:instances:update', - args=[INSTANCE_ID])) - + url = reverse('horizon:nova:instances_and_volumes:instances:update', + args=[server.id]) + res = self.client.get(url) self.assertTemplateUsed(res, 'nova/instances_and_volumes/instances/update.html') def test_instance_update_get_server_get_exception(self): - INSTANCE_ID = self.servers[0].id - - exception = nova_exceptions.ClientException(500) + server = self.servers.first() self.mox.StubOutWithMock(api, 'server_get') - api.server_get(IsA(http.HttpRequest), - unicode(INSTANCE_ID)).AndRaise(exception) - + exc = nova_exceptions.ClientException(500) + api.server_get(IsA(http.HttpRequest), server.id).AndRaise(exc) self.mox.ReplayAll() - res = self.client.get( - reverse('horizon:nova:instances_and_volumes:instances:update', - args=[INSTANCE_ID])) - + url = reverse('horizon:nova:instances_and_volumes:instances:update', + args=[server.id]) + res = self.client.get(url) self.assertRedirectsNoFollow(res, INDEX_URL) def test_instance_update_post(self): - INSTANCE_ID = self.servers[0].id - NAME = 'myname' - formData = {'method': 'UpdateInstance', - 'instance': self.servers[0].id, - 'name': NAME, - 'tenant_id': self.TEST_TENANT} + server = self.servers.first() self.mox.StubOutWithMock(api, 'server_get') - api.server_get(IsA(http.HttpRequest), - unicode(INSTANCE_ID)).AndReturn(self.servers[0]) - self.mox.StubOutWithMock(api, 'server_update') - api.server_update(IsA(http.HttpRequest), - str(INSTANCE_ID), NAME) - + api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server) + api.server_update(IsA(http.HttpRequest), server.id, server.name) self.mox.ReplayAll() - res = self.client.post( - reverse('horizon:nova:instances_and_volumes:instances:update', - args=[INSTANCE_ID]), formData) - + formData = {'method': 'UpdateInstance', + 'instance': server.id, + 'name': server.name, + 'tenant_id': self.tenant.id} + url = reverse('horizon:nova:instances_and_volumes:instances:update', + args=[server.id]) + res = self.client.post(url, formData) self.assertRedirectsNoFollow(res, INDEX_URL) def test_instance_update_post_api_exception(self): - SERVER = self.servers[0] + server = self.servers.first() self.mox.StubOutWithMock(api, 'server_get') self.mox.StubOutWithMock(api, 'server_update') - - api.server_get(IsA(http.HttpRequest), unicode(SERVER.id)) \ - .AndReturn(self.servers[0]) - exception = nova_exceptions.ClientException(500) - api.server_update(IsA(http.HttpRequest), str(SERVER.id), SERVER.name) \ - .AndRaise(exception) - + api.server_get(IsA(http.HttpRequest), server.id).AndReturn(server) + exc = nova_exceptions.ClientException(500) + api.server_update(IsA(http.HttpRequest), server.id, server.name) \ + .AndRaise(exc) self.mox.ReplayAll() formData = {'method': 'UpdateInstance', - 'instance': SERVER.id, - 'name': SERVER.name, - 'tenant_id': self.TEST_TENANT} + 'instance': server.id, + 'name': server.name, + 'tenant_id': self.tenant.id} url = reverse('horizon:nova:instances_and_volumes:instances:update', - args=[SERVER.id]) + args=[server.id]) res = self.client.post(url, formData) - self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/horizon/horizon/dashboards/nova/instances_and_volumes/tests.py b/horizon/horizon/dashboards/nova/instances_and_volumes/tests.py index f0c7f0020..ae206b565 100644 --- a/horizon/horizon/dashboards/nova/instances_and_volumes/tests.py +++ b/horizon/horizon/dashboards/nova/instances_and_volumes/tests.py @@ -27,27 +27,12 @@ from horizon import api from horizon import test -class InstancesAndVolumesViewTest(test.BaseViewTests): - def setUp(self): - super(InstancesAndVolumesViewTest, self).setUp() - server = api.Server(None, self.request) - server.id = 1 - server.name = 'serverName' - server.status = "ACTIVE" - - volume = api.Volume(self.request) - volume.id = 1 - volume.size = 10 - volume.attachments = [{}] - - self.servers = (server,) - self.volumes = (volume,) - +class InstancesAndVolumesViewTest(test.TestCase): def test_index(self): self.mox.StubOutWithMock(api, 'server_list') self.mox.StubOutWithMock(api, 'volume_list') - api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers) - api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes) + api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers.list()) + api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list()) self.mox.ReplayAll() @@ -57,14 +42,14 @@ class InstancesAndVolumesViewTest(test.BaseViewTests): self.assertTemplateUsed(res, 'nova/instances_and_volumes/index.html') instances = res.context['instances_table'].data - self.assertItemsEqual(instances, self.servers) + self.assertItemsEqual(instances, self.servers.list()) def test_index_server_list_exception(self): self.mox.StubOutWithMock(api, 'server_list') self.mox.StubOutWithMock(api, 'volume_list') exception = novaclient_exceptions.ClientException('apiException') api.server_list(IsA(http.HttpRequest)).AndRaise(exception) - api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes) + api.volume_list(IsA(http.HttpRequest)).AndReturn(self.volumes.list()) self.mox.ReplayAll() diff --git a/horizon/horizon/dashboards/nova/networks/tests.py b/horizon/horizon/dashboards/nova/networks/tests.py index 0251035d3..c1283cab2 100644 --- a/horizon/horizon/dashboards/nova/networks/tests.py +++ b/horizon/horizon/dashboards/nova/networks/tests.py @@ -19,17 +19,18 @@ # under the License. from django import http -from django.contrib import messages from django.core.urlresolvers import reverse -from mox import IgnoreArg, IsA +from mox import IsA from horizon import api from horizon import test -class NetworkViewTests(test.BaseViewTests): +class NetworkViewTests(test.TestCase): def setUp(self): super(NetworkViewTests, self).setUp() + # TODO(gabriel): Move this to horizon.tests.test_data.quantum_data + # after the wrapper classes are added for Quantum. self.network = {} self.network['networks'] = [] self.network['networks'].append({'id': 'n1'}) @@ -186,9 +187,6 @@ class NetworkViewTests(test.BaseViewTests): 'network': 'n1', 'method': 'CreatePort'} - self.mox.StubOutWithMock(messages, 'success') - messages.success(IgnoreArg(), IsA(basestring)) - self.mox.ReplayAll() res = self.client.post(reverse('horizon:nova:networks:port_create', @@ -226,9 +224,6 @@ class NetworkViewTests(test.BaseViewTests): formData = {'action': 'network_details__delete__p1'} - self.mox.StubOutWithMock(messages, 'success') - messages.success(IgnoreArg(), IsA(basestring)) - self.mox.ReplayAll() detail_url = reverse('horizon:nova:networks:detail', args=["n1"]) @@ -292,9 +287,6 @@ class NetworkViewTests(test.BaseViewTests): formData = {'action': "network_details__detach_port__p1"} - self.mox.StubOutWithMock(messages, 'success') - messages.success(IgnoreArg(), IsA(basestring)) - self.mox.ReplayAll() detail_url = reverse('horizon:nova:networks:detail', args=["n1"]) diff --git a/horizon/horizon/dashboards/nova/overview/tests.py b/horizon/horizon/dashboards/nova/overview/tests.py index 354bccac5..908332b6c 100644 --- a/horizon/horizon/dashboards/nova/overview/tests.py +++ b/horizon/horizon/dashboards/nova/overview/tests.py @@ -24,7 +24,6 @@ from django import http from django.core.urlresolvers import reverse from mox import IsA from novaclient import exceptions as nova_exceptions -from novaclient.v1_1 import usage as nova_usage from horizon import api from horizon import test @@ -32,137 +31,83 @@ from horizon import usage INDEX_URL = reverse('horizon:nova:overview:index') -USAGE_DATA = { - 'total_memory_mb_usage': 64246.89777777778, - 'total_vcpus_usage': 125.48222222222223, - 'total_hours': 125.48222222222223, - 'total_local_gb_usage': 0.0, - 'tenant_id': u'99e7c0197c3643289d89e9854469a4ae', - 'stop': u'2012-01-3123: 30: 46', - 'start': u'2012-01-0100: 00: 00', - 'server_usages': [ - { - u'memory_mb': 512, - u'uptime': 442321, - u'started_at': u'2012-01-2620: 38: 21', - u'ended_at': None, - u'name': u'testing', - u'tenant_id': u'99e7c0197c3643289d89e9854469a4ae', - u'state': u'active', - u'hours': 122.87361111111112, - u'vcpus': 1, - u'flavor': u'm1.tiny', - u'local_gb': 0 - }, - { - u'memory_mb': 512, - u'uptime': 9367, - u'started_at': u'2012-01-3120: 54: 15', - u'ended_at': None, - u'name': u'instance2', - u'tenant_id': u'99e7c0197c3643289d89e9854469a4ae', - u'state': u'active', - u'hours': 2.608611111111111, - u'vcpus': 1, - u'flavor': u'm1.tiny', - u'local_gb': 0 - } - ] -} -class UsageViewTests(test.BaseViewTests): - def setUp(self): - super(UsageViewTests, self).setUp() - usage_resource = nova_usage.Usage(nova_usage.UsageManager, USAGE_DATA) - self.usage = api.nova.Usage(usage_resource) - self.usages = (self.usage,) - +class UsageViewTests(test.TestCase): def tearDown(self): super(UsageViewTests, self).tearDown() - self.reset_times() + self.reset_times() # override_times is called in the tests def test_usage(self): now = self.override_times() - + usage_obj = api.nova.Usage(self.usages.first()) self.mox.StubOutWithMock(api, 'usage_get') - api.usage_get(IsA(http.HttpRequest), self.TEST_TENANT, + api.usage_get(IsA(http.HttpRequest), self.tenant.id, datetime.datetime(now.year, now.month, 1, now.hour, now.minute, now.second), datetime.datetime(now.year, now.month, now.day, now.hour, now.minute, now.second)) \ - .AndReturn(self.usage) - + .AndReturn(usage_obj) self.mox.ReplayAll() res = self.client.get(reverse('horizon:nova:overview:index')) - self.assertTemplateUsed(res, 'nova/overview/usage.html') - self.assertTrue(isinstance(res.context['usage'], usage.TenantUsage)) self.assertContains(res, 'form-horizontal') def test_usage_csv(self): now = self.override_times() - + usage_obj = api.nova.Usage(self.usages.first()) self.mox.StubOutWithMock(api, 'usage_get') timestamp = datetime.datetime(now.year, now.month, 1, now.hour, now.minute, now.second) api.usage_get(IsA(http.HttpRequest), - self.TEST_TENANT, + self.tenant.id, timestamp, datetime.datetime(now.year, now.month, now.day, now.hour, now.minute, now.second)) \ - .AndReturn(self.usage) + .AndReturn(usage_obj) self.mox.ReplayAll() - res = self.client.get(reverse('horizon:nova:overview:index') + "?format=csv") - self.assertTemplateUsed(res, 'nova/overview/usage.csv') - self.assertTrue(isinstance(res.context['usage'], usage.TenantUsage)) def test_usage_exception(self): now = self.override_times() - self.mox.StubOutWithMock(api, 'usage_get') timestamp = datetime.datetime(now.year, now.month, 1, now.hour, now.minute, now.second) exception = nova_exceptions.ClientException(500) api.usage_get(IsA(http.HttpRequest), - self.TEST_TENANT, + self.tenant.id, timestamp, datetime.datetime(now.year, now.month, now.day, now.hour, now.minute, now.second)) \ .AndRaise(exception) - self.mox.ReplayAll() res = self.client.get(reverse('horizon:nova:overview:index')) - self.assertTemplateUsed(res, 'nova/overview/usage.html') self.assertEqual(res.context['usage'].usage_list, []) def test_usage_default_tenant(self): now = self.override_times() - + usage_obj = api.nova.Usage(self.usages.first()) self.mox.StubOutWithMock(api, 'usage_get') timestamp = datetime.datetime(now.year, now.month, 1, now.hour, now.minute, now.second) api.usage_get(IsA(http.HttpRequest), - self.TEST_TENANT, + self.tenant.id, timestamp, datetime.datetime(now.year, now.month, now.day, now.hour, now.minute, now.second)) \ - .AndReturn(self.usage) - + .AndReturn(usage_obj) self.mox.ReplayAll() res = self.client.get(reverse('horizon:nova:overview:index')) - self.assertTemplateUsed(res, 'nova/overview/usage.html') self.assertTrue(isinstance(res.context['usage'], usage.TenantUsage)) diff --git a/horizon/horizon/dashboards/syspanel/instances/tests.py b/horizon/horizon/dashboards/syspanel/instances/tests.py index 7b5696b1d..30d789255 100644 --- a/horizon/horizon/dashboards/syspanel/instances/tests.py +++ b/horizon/horizon/dashboards/syspanel/instances/tests.py @@ -24,37 +24,20 @@ from horizon import test class InstanceViewTest(test.BaseAdminViewTests): - def setUp(self): - super(InstanceViewTest, self).setUp() - self.server = api.Server(None, self.request) - self.server.id = 1 - self.server.name = 'serverName' - self.server.status = "ACTIVE" - self.server.flavor = {'id': '1'} - - self.flavor = api.nova.Flavor(None) - self.flavor.id = '1' - self.flavor.ram = 512 - self.flavor.vcpus = 512 - self.flavor.disk = 1 - - self.servers = (self.server,) - self.flavors = (self.flavor,) - def test_index(self): + servers = self.servers.list() + flavors = self.flavors.list() self.mox.StubOutWithMock(api.nova, 'server_list') self.mox.StubOutWithMock(api.nova, 'flavor_list') api.nova.server_list(IsA(http.HttpRequest), - all_tenants=True).AndReturn(self.servers) - api.nova.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors) - + all_tenants=True).AndReturn(servers) + api.nova.flavor_list(IsA(http.HttpRequest)).AndReturn(flavors) self.mox.ReplayAll() res = self.client.get(reverse('horizon:syspanel:instances:index')) - self.assertTemplateUsed(res, 'syspanel/instances/index.html') instances = res.context['table'].data - self.assertItemsEqual(instances, self.servers) + self.assertItemsEqual(instances, servers) def test_index_server_list_exception(self): self.mox.StubOutWithMock(api.nova, 'server_list') @@ -66,6 +49,5 @@ class InstanceViewTest(test.BaseAdminViewTests): self.mox.ReplayAll() res = self.client.get(reverse('horizon:syspanel:instances:index')) - self.assertTemplateUsed(res, 'syspanel/instances/index.html') self.assertEqual(len(res.context['instances_table'].data), 0) diff --git a/horizon/horizon/dashboards/syspanel/projects/tests.py b/horizon/horizon/dashboards/syspanel/projects/tests.py index a3d0c835c..24035c841 100644 --- a/horizon/horizon/dashboards/syspanel/projects/tests.py +++ b/horizon/horizon/dashboards/syspanel/projects/tests.py @@ -25,71 +25,39 @@ from horizon import test INDEX_URL = reverse('horizon:syspanel:projects:index') -class FakeResource(object): - def __init__(self, **kwargs): - for key, value in kwargs.items(): - setattr(self, key, value) - - class TenantsViewTests(test.BaseAdminViewTests): - def setUp(self): - super(TenantsViewTests, self).setUp() - - self.tenant = FakeResource(id=self.TEST_TENANT, - name=self.TEST_TENANT_NAME, - enabled=True) - - self.quota_data = dict(metadata_items='1', - injected_file_content_bytes='1', - volumes='1', - gigabytes='1', - ram=1, - floating_ips='1', - instances='1', - injected_files='1', - cores='1') - self.quota = FakeResource(id=self.TEST_TENANT, **self.quota_data) - - self.tenants = [self.tenant] - def test_index(self): self.mox.StubOutWithMock(api, 'tenant_list') - api.tenant_list(IsA(http.HttpRequest)).AndReturn(self.tenants) - + api.tenant_list(IsA(http.HttpRequest)).AndReturn(self.tenants.list()) self.mox.ReplayAll() res = self.client.get(INDEX_URL) - self.assertTemplateUsed(res, 'syspanel/projects/index.html') - self.assertItemsEqual(res.context['table'].data, self.tenants) + self.assertItemsEqual(res.context['table'].data, self.tenants.list()) def test_modify_quota(self): + tenant = self.tenants.first() + quota = self.quotas.first() + quota_data = {"metadata_items": '1', + "injected_files": '1', + "injected_file_content_bytes": '1', + "cores": '1', + "instances": '1', + "volumes": '1', + "gigabytes": '1', + "ram": 1, + "floating_ips": '1'} self.mox.StubOutWithMock(api.keystone, 'tenant_get') self.mox.StubOutWithMock(api.nova, 'tenant_quota_get') self.mox.StubOutWithMock(api.nova, 'tenant_quota_update') - - api.keystone.tenant_get(IgnoreArg(), self.TEST_TENANT) \ - .AndReturn(self.tenant) - api.nova.tenant_quota_get(IgnoreArg(), self.TEST_TENANT) \ - .AndReturn(self.quota) - api.nova.tenant_quota_update(IgnoreArg(), - self.TEST_TENANT, - **self.quota_data) - + api.keystone.tenant_get(IgnoreArg(), tenant.id).AndReturn(tenant) + api.nova.tenant_quota_get(IgnoreArg(), tenant.id).AndReturn(quota) + api.nova.tenant_quota_update(IgnoreArg(), tenant.id, **quota_data) self.mox.ReplayAll() url = reverse('horizon:syspanel:projects:quotas', - args=(self.TEST_TENANT,)) - data = {"method": "UpdateQuotas", - "tenant_id": self.TEST_TENANT, - "metadata_items": '1', - "injected_files": '1', - "injected_file_content_bytes": '1', - "cores": '1', - "instances": '1', - "volumes": '1', - "gigabytes": '1', - "ram": 1, - "floating_ips": '1'} - res = self.client.post(url, data) + args=[self.tenant.id]) + quota_data.update({"method": "UpdateQuotas", + "tenant_id": self.tenant.id}) + res = self.client.post(url, quota_data) self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/horizon/horizon/dashboards/syspanel/users/tests.py b/horizon/horizon/dashboards/syspanel/users/tests.py index 278c66493..3004592e5 100644 --- a/horizon/horizon/dashboards/syspanel/users/tests.py +++ b/horizon/horizon/dashboards/syspanel/users/tests.py @@ -19,8 +19,8 @@ # under the License. from django.core.urlresolvers import reverse -from mox import IgnoreArg from keystoneclient import exceptions as keystone_exceptions +from mox import IgnoreArg from horizon import api from horizon import test @@ -30,68 +30,50 @@ USERS_INDEX_URL = reverse('horizon:syspanel:users:index') class UsersViewTests(test.BaseAdminViewTests): - def setUp(self): - super(UsersViewTests, self).setUp() - - self.user = api.User(None) - self.user.enabled = True - self.user.id = self.TEST_USER_ID - self.user.name = self.TEST_USER - self.user.roles = self.TEST_ROLES - self.user.tenantId = self.TEST_TENANT - - self.users = [self.user] - def test_index(self): self.mox.StubOutWithMock(api, 'user_list') - api.user_list(IgnoreArg()).AndReturn(self.users) - + api.user_list(IgnoreArg()).AndReturn(self.users.list()) self.mox.ReplayAll() res = self.client.get(USERS_INDEX_URL) - self.assertTemplateUsed(res, 'syspanel/users/index.html') - self.assertItemsEqual(res.context['table'].data, self.users) + self.assertItemsEqual(res.context['table'].data, self.users.list()) def test_enable_user(self): - formData = {'action': 'users__enable__%s' % self.user.id} - + user = self.users.get(id="2") self.mox.StubOutWithMock(api.keystone, 'user_update_enabled') - api.keystone.user_update_enabled(IgnoreArg(), self.user.id, True) \ - .AndReturn(self.mox.CreateMock(api.User)) - + api.keystone.user_update_enabled(IgnoreArg(), + user.id, + True).AndReturn(user) self.mox.ReplayAll() + formData = {'action': 'users__enable__%s' % user.id} res = self.client.post(USERS_INDEX_URL, formData) - self.assertRedirects(res, USERS_INDEX_URL) def test_disable_user(self): - OTHER_USER_ID = '5' - formData = {'action': 'users__disable__%s' % OTHER_USER_ID} - + user = self.users.get(id="2") self.mox.StubOutWithMock(api.keystone, 'user_update_enabled') - api.keystone.user_update_enabled(IgnoreArg(), OTHER_USER_ID, False) \ - .AndReturn(self.mox.CreateMock(api.User)) - + api.keystone.user_update_enabled(IgnoreArg(), + user.id, + False).AndReturn(user) self.mox.ReplayAll() + formData = {'action': 'users__disable__%s' % user.id} res = self.client.post(USERS_INDEX_URL, formData) - self.assertRedirects(res, USERS_INDEX_URL) def test_enable_disable_user_exception(self): - OTHER_USER_ID = '5' - formData = {'action': 'users__enable__%s' % OTHER_USER_ID} - + user = self.users.get(id="2") self.mox.StubOutWithMock(api.keystone, 'user_update_enabled') api_exception = keystone_exceptions.ClientException('apiException', message='apiException') - api.keystone.user_update_enabled(IgnoreArg(), OTHER_USER_ID, True) \ - .AndRaise(api_exception) - + api.keystone.user_update_enabled(IgnoreArg(), + user.id, + True).AndRaise(api_exception) self.mox.ReplayAll() + formData = {'action': 'users__enable__%s' % user.id} res = self.client.post(USERS_INDEX_URL, formData) self.assertRedirects(res, USERS_INDEX_URL) @@ -99,10 +81,10 @@ class UsersViewTests(test.BaseAdminViewTests): def test_shoot_yourself_in_the_foot(self): self.mox.StubOutWithMock(api, 'user_list') # Four times... one for each post and one for each followed redirect - api.user_list(IgnoreArg()).AndReturn(self.users) - api.user_list(IgnoreArg()).AndReturn(self.users) - api.user_list(IgnoreArg()).AndReturn(self.users) - api.user_list(IgnoreArg()).AndReturn(self.users) + api.user_list(IgnoreArg()).AndReturn(self.users.list()) + api.user_list(IgnoreArg()).AndReturn(self.users.list()) + api.user_list(IgnoreArg()).AndReturn(self.users.list()) + api.user_list(IgnoreArg()).AndReturn(self.users.list()) self.mox.ReplayAll() @@ -115,4 +97,5 @@ class UsersViewTests(test.BaseAdminViewTests): formData = {'action': 'users__delete__%s' % self.request.user.id} res = self.client.post(USERS_INDEX_URL, formData, follow=True) self.assertEqual(list(res.context['messages'])[0].message, - u'You do not have permission to delete user: test') + u'You do not have permission to delete user: %s' + % self.request.user.username) diff --git a/horizon/horizon/exceptions.py b/horizon/horizon/exceptions.py index a0720c202..cc1c93d04 100644 --- a/horizon/horizon/exceptions.py +++ b/horizon/horizon/exceptions.py @@ -32,6 +32,81 @@ from novaclient import exceptions as novaclient LOG = logging.getLogger(__name__) +class HorizonException(Exception): + """ Base exception class for distinguishing our own exception classes. """ + pass + + +class Http302(HorizonException): + """ + Error class which can be raised from within a handler to cause an + early bailout and redirect at the middleware level. + """ + status_code = 302 + + def __init__(self, location, message=None): + self.location = location + self.message = message + + +class NotAuthorized(HorizonException): + """ + Raised whenever a user attempts to access a resource which they do not + have role-based access to (such as when failing the + :func:`~horizon.decorators.require_roles` decorator). + + The included :class:`~horizon.middleware.HorizonMiddleware` catches + ``NotAuthorized`` and handles it gracefully by displaying an error + message and redirecting the user to a login page. + """ + status_code = 401 + + +class NotFound(HorizonException): + """ Generic error to replace all "Not Found"-type API errors. """ + status_code = 404 + + +class RecoverableError(HorizonException): + """ Generic error to replace any "Recoverable"-type API errors. """ + status_code = 100 # HTTP status code "Continue" + + +class ServiceCatalogException(HorizonException): + """ + Raised when a requested service is not available in the ``ServiceCatalog`` + returned by Keystone. + """ + def __init__(self, service_name): + message = 'Invalid service catalog service: %s' % service_name + super(ServiceCatalogException, self).__init__(message) + + +class AlreadyExists(HorizonException): + """ + Exception to be raised when trying to create an API resource which + already exists. + """ + def __init__(self, name, resource_type): + self.attrs = {"name": name, "resource": resource_type} + self.msg = 'A %(resource)s with the name "%(name)s" already exists.' + + def __repr__(self): + return self.msg % self.attrs + + def __unicode__(self): + return _(self.msg) % self.attrs + + +class HandledException(HorizonException): + """ + Used internally to track exceptions that have gone through + :func:`horizon.exceptions.handle` more than once. + """ + def __init__(self, wrapped): + self.wrapped = wrapped + + UNAUTHORIZED = (keystoneclient.Unauthorized, keystoneclient.Forbidden, novaclient.Unauthorized, @@ -51,61 +126,8 @@ NOT_FOUND = (keystoneclient.NotFound, RECOVERABLE = (keystoneclient.ClientException, novaclient.ClientException, glanceclient.GlanceException, - swiftclient.Error) - - -class Http302(Exception): - """ - Error class which can be raised from within a handler to cause an - early bailout and redirect at the middleware level. - """ - status_code = 302 - - def __init__(self, location, message=None): - self.location = location - self.message = message - - -class NotAuthorized(Exception): - """ - Raised whenever a user attempts to access a resource which they do not - have role-based access to (such as when failing the - :func:`~horizon.decorators.require_roles` decorator). - - The included :class:`~horizon.middleware.HorizonMiddleware` catches - ``NotAuthorized`` and handles it gracefully by displaying an error - message and redirecting the user to a login page. - """ - status_code = 401 - - -class NotFound(Exception): - """ Generic error to replace all "Not Found"-type API errors. """ - status_code = 404 - - -class RecoverableError(Exception): - """ Generic error to replace any "Recoverable"-type API errors. """ - status_code = 100 # HTTP status code "Continue" - - -class ServiceCatalogException(Exception): - """ - Raised when a requested service is not available in the ``ServiceCatalog`` - returned by Keystone. - """ - def __init__(self, service_name): - message = 'Invalid service catalog service: %s' % service_name - super(ServiceCatalogException, self).__init__(message) - - -class HandledException(Exception): - """ - Used internally to track exceptions that have gone through - :func:`horizon.exceptions.handle` more than once. - """ - def __init__(self, wrapped): - self.wrapped = wrapped + swiftclient.Error, + AlreadyExists) def handle(request, message=None, redirect=None, ignore=False, escalate=False): @@ -149,8 +171,11 @@ def handle(request, message=None, redirect=None, ignore=False, escalate=False): exc_type, exc_value, exc_traceback = exc_value.wrapped wrap = True + # We trust messages from our own exceptions + if issubclass(exc_type, HorizonException): + message = exc_value # If the message has a placeholder for the exception, fill it in - if message and "%(exc)s" in message: + elif message and "%(exc)s" in message: message = message % {"exc": exc_value} if issubclass(exc_type, UNAUTHORIZED): diff --git a/horizon/horizon/test.py b/horizon/horizon/test.py index 661ba95f1..f3f59d822 100644 --- a/horizon/horizon/test.py +++ b/horizon/horizon/test.py @@ -20,18 +20,24 @@ import datetime +import cloudfiles as swift_client from django import http from django import test as django_test from django.conf import settings from django.contrib.messages.storage import default_storage from django.core.handlers import wsgi from django.test.client import RequestFactory +from glance import client as glance_client +from keystoneclient.v2_0 import client as keystone_client +from novaclient.v1_1 import client as nova_client import httplib2 import mox +from horizon import api from horizon import context_processors from horizon import middleware from horizon import users +from horizon.tests.test_data.utils import load_test_data from .time import time from .time import today @@ -51,85 +57,49 @@ class RequestFactoryWithMessages(RequestFactory): class TestCase(django_test.TestCase): - TEST_STAFF_USER = 'staffUser' - TEST_TENANT = '1' - TEST_TENANT_NAME = 'aTenant' - TEST_TOKEN = 'aToken' - TEST_USER = 'test' - TEST_USER_ID = '1' - TEST_ROLES = [{'name': 'admin', 'id': '1'}] - TEST_CONTEXT = {'authorized_tenants': [{'enabled': True, - 'name': 'aTenant', - 'id': '1', - 'description': "None"}]} - - TEST_SERVICE_CATALOG = [ - {"endpoints": [{ - "adminURL": "http://cdn.admin-nets.local:8774/v1.0", - "region": "RegionOne", - "internalURL": "http://127.0.0.1:8774/v1.0", - "publicURL": "http://cdn.admin-nets.local:8774/v1.0/"}], - "type": "nova_compat", - "name": "nova_compat"}, - {"endpoints": [{ - "adminURL": "http://nova/novapi/admin", - "region": "RegionOne", - "internalURL": "http://nova/novapi/internal", - "publicURL": "http://nova/novapi/public"}], - "type": "compute", - "name": "nova"}, - {"endpoints": [{ - "adminURL": "http://glance/glanceapi/admin", - "region": "RegionOne", - "internalURL": "http://glance/glanceapi/internal", - "publicURL": "http://glance/glanceapi/public"}], - "type": "image", - "name": "glance"}, - {"endpoints": [{ - "adminURL": "http://cdn.admin-nets.local:35357/v2.0", - "region": "RegionOne", - "internalURL": "http://127.0.0.1:5000/v2.0", - "publicURL": "http://cdn.admin-nets.local:5000/v2.0"}], - "type": "identity", - "name": "identity"}, - {"endpoints": [{ - "adminURL": "http://example.com:9696/quantum", - "region": "RegionOne", - "internalURL": "http://example.com:9696/quantum", - "publicURL": "http://example.com:9696/quantum"}], - "type": "network", - "name": "quantum"}, - {"endpoints": [{ - "adminURL": "http://swift/swiftapi/admin", - "region": "RegionOne", - "internalURL": "http://swift/swiftapi/internal", - "publicURL": "http://swift/swiftapi/public"}], - "type": "object-store", - "name": "swift"}] + """ + Specialized base test case class for Horizon which gives access to + numerous additional features: + * A full suite of test data through various attached objects and + managers (e.g. ``self.servers``, ``self.user``, etc.). See the + docs for :class:`~horizon.tests.test_data.utils.TestData` for more + information. + * The ``mox`` mocking framework via ``self.mox``. + * A set of request context data via ``self.context``. + * A ``RequestFactory`` class which supports Django's ``contrib.messages`` + framework via ``self.factory``. + * A ready-to-go request object via ``self.request``. + * The ability to override specific time data controls for easier testing. + * Several handy additional assertion methods. + """ def setUp(self): + load_test_data(self) self.mox = mox.Mox() self.factory = RequestFactoryWithMessages() + self.context = {'authorized_tenants': self.tenants.list()} def fake_conn_request(*args, **kwargs): raise Exception("An external URI request tried to escape through " "an httplib2 client. Args: %s, kwargs: %s" % (args, kwargs)) + self._real_conn_request = httplib2.Http._conn_request httplib2.Http._conn_request = fake_conn_request self._real_horizon_context_processor = context_processors.horizon - context_processors.horizon = lambda request: self.TEST_CONTEXT + context_processors.horizon = lambda request: self.context self._real_get_user_from_request = users.get_user_from_request - tenants = self.TEST_CONTEXT['authorized_tenants'] - self.setActiveUser(token=self.TEST_TOKEN, - username=self.TEST_USER, - tenant_id=self.TEST_TENANT, - service_catalog=self.TEST_SERVICE_CATALOG, + tenants = self.context['authorized_tenants'] + self.setActiveUser(token=self.token.id, + username=self.user.name, + tenant_id=self.tenant.id, + service_catalog=self.service_catalog, authorized_tenants=tenants) self.request = http.HttpRequest() self.request.session = self.client._session() + self.request.session['token'] = self.token.id middleware.HorizonMiddleware().process_request(self.request) def tearDown(self): @@ -151,41 +121,150 @@ class TestCase(django_test.TestCase): authorized_tenants=authorized_tenants) def override_times(self): + """ Overrides the "current" time with immutable values. """ now = datetime.datetime.utcnow() time.override_time = \ datetime.time(now.hour, now.minute, now.second) today.override_time = datetime.date(now.year, now.month, now.day) utcnow.override_time = now - return now def reset_times(self): + """ Undoes the changes made by ``override_times``. """ time.override_time = None today.override_time = None utcnow.override_time = None - -class BaseViewTests(TestCase): - """ - Base class for view based unit tests. - """ def assertRedirectsNoFollow(self, response, expected_url): + """ + Asserts that the given response issued a 302 redirect without + processing the view which is redirected to. + """ if response.status_code / 100 != 3: assert("The response did not return a redirect.") self.assertEqual(response._headers.get('location', None), ('Location', settings.TESTSERVER + expected_url)) self.assertEqual(response.status_code, 302) + def assertNoMessages(self): + """ + Asserts that no messages have been attached by the ``contrib.messages`` + framework. + """ + self.assertMessageCount(success=0, warn=0, info=0, error=0) -class BaseAdminViewTests(BaseViewTests): + def assertMessageCount(self, **kwargs): + """ + Asserts that the specified number of messages have been attached + for various message types. Usage would look like + ``self.assertMessageCount(success=1)``. + """ + temp_req = self.client.request(**{'wsgi.input': None}) + temp_req.COOKIES = self.client.cookies + storage = default_storage(temp_req) + # To gain early access to the messages we have to decode the + # cookie on the test client. + if 'messages' in self.client.cookies: + messages = storage._decode(self.client.cookies['messages'].value) + elif any(kwargs.values()): + error_msg = "Messages were expected, but none were set." + assert 0 == sum(kwargs.values()), error_msg + + for msg_type, count in kwargs.items(): + msgs = [m.message for m in messages if msg_type in m.tags] + assert len(msgs) == count, \ + "%s messages not as expected: %s" % (msg_type.title(), + ", ".join(msgs)) + + def assertNoFormErrors(self, response, context_name="form"): + """ + Asserts that the response either does not contain a form in it's + context, or that if it does, that form has no errors. + """ + context = getattr(response, "context", {}) + if not context or context_name not in context: + return True + errors = response.context[context_name]._errors + assert len(errors) == 0, \ + "Unexpected errors were found on the form: %s" % errors + + +class BaseAdminViewTests(TestCase): + """ + A ``TestCase`` subclass which sets an active user with the "admin" role + for testing admin-only views and functionality. + """ def setActiveUser(self, id=None, token=None, username=None, tenant_id=None, service_catalog=None, tenant_name=None, roles=None, authorized_tenants=None): users.get_user_from_request = lambda x: \ - users.User(id=self.TEST_USER_ID, - token=self.TEST_TOKEN, - user=self.TEST_USER, - tenant_id=self.TEST_TENANT, - service_catalog=self.TEST_SERVICE_CATALOG, - roles=self.TEST_ROLES, + users.User(id=self.user.id, + token=self.token.id, + user=self.user.name, + tenant_id=self.tenant.id, + service_catalog=self.service_catalog, + roles=[self.roles.admin._info], authorized_tenants=None) + + +class APITestCase(TestCase): + """ + The ``APITestCase`` class is for use with tests which deal with the + underlying clients rather than stubbing out the horizon.api.* methods. + """ + def setUp(self): + super(APITestCase, self).setUp() + + def fake_keystoneclient(request, username=None, password=None, + tenant_id=None, token_id=None, endpoint=None): + """ + Wrapper function which returns the stub keystoneclient. Only + necessary because the function takes too many arguments to + conveniently be a lambda. + """ + return self.stub_keystoneclient() + + # Store the original clients + self._original_glanceclient = api.glance.glanceclient + self._original_keystoneclient = api.keystone.keystoneclient + self._original_novaclient = api.nova.novaclient + + # Replace the clients with our stubs. + api.glance.glanceclient = lambda request: self.stub_glanceclient() + api.keystone.keystoneclient = fake_keystoneclient + api.nova.novaclient = lambda request: self.stub_novaclient() + + def tearDown(self): + super(APITestCase, self).tearDown() + api.glance.glanceclient = self._original_glanceclient + api.nova.novaclient = self._original_novaclient + api.keystone.keystoneclient = self._original_keystoneclient + + def stub_novaclient(self): + if not hasattr(self, "novaclient"): + self.mox.StubOutWithMock(nova_client, 'Client') + self.novaclient = self.mox.CreateMock(nova_client.Client) + return self.novaclient + + def stub_keystoneclient(self): + if not hasattr(self, "keystoneclient"): + self.mox.StubOutWithMock(keystone_client, 'Client') + self.keystoneclient = self.mox.CreateMock(keystone_client.Client) + return self.keystoneclient + + def stub_glanceclient(self): + if not hasattr(self, "glanceclient"): + self.mox.StubOutWithMock(glance_client, 'Client') + self.glanceclient = self.mox.CreateMock(glance_client.Client) + self.glanceclient.token = self.tokens.first().id + return self.glanceclient + + def stub_swiftclient(self, expected_calls=1): + if not hasattr(self, "swiftclient"): + self.mox.StubOutWithMock(swift_client, 'Connection') + self.swiftclient = self.mox.CreateMock(swift_client.Connection) + while expected_calls: + swift_client.Connection(auth=mox.IgnoreArg())\ + .AndReturn(self.swiftclient) + expected_calls -= 1 + return self.swiftclient diff --git a/horizon/horizon/tests/api_tests/__init__.py b/horizon/horizon/tests/api_tests/__init__.py index a52af6bfb..e69de29bb 100644 --- a/horizon/horizon/tests/api_tests/__init__.py +++ b/horizon/horizon/tests/api_tests/__init__.py @@ -1,28 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright 2012 Nebula, Inc. -# -# 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 horizon.tests.api_tests.base import (APIResourceWrapperTests, - APIDictWrapperTests, ApiHelperTests) -from horizon.tests.api_tests.glance import GlanceApiTests, ImageWrapperTests -from horizon.tests.api_tests.keystone import (TokenApiTests, RoleAPITests, - UserAPITests) -from horizon.tests.api_tests.nova import (ServerWrapperTests, - ComputeApiTests, VolumeTests) -from horizon.tests.api_tests.swift import SwiftApiTests diff --git a/horizon/horizon/tests/api_tests/base.py b/horizon/horizon/tests/api_tests/base_tests.py similarity index 64% rename from horizon/horizon/tests/api_tests/base.py rename to horizon/horizon/tests/api_tests/base_tests.py index 8ee35dec2..3e0704bb8 100644 --- a/horizon/horizon/tests/api_tests/base.py +++ b/horizon/horizon/tests/api_tests/base_tests.py @@ -20,12 +20,38 @@ from __future__ import absolute_import -from django import http -from django.conf import settings -from mox import IsA - from horizon import exceptions -from horizon.tests.api_tests.utils import * +from horizon import test +from horizon.api import base as api_base + + +class APIResource(api_base.APIResourceWrapper): + """ Simple APIResource for testing """ + _attrs = ['foo', 'bar', 'baz'] + + @staticmethod + def get_instance(innerObject=None): + if innerObject is None: + + class InnerAPIResource(object): + pass + + innerObject = InnerAPIResource() + innerObject.foo = 'foo' + innerObject.bar = 'bar' + return APIResource(innerObject) + + +class APIDict(api_base.APIDictWrapper): + """ Simple APIDict for testing """ + _attrs = ['foo', 'bar', 'baz'] + + @staticmethod + def get_instance(innerDict=None): + if innerDict is None: + innerDict = {'foo': 'foo', + 'bar': 'bar'} + return APIDict(innerDict) # Wrapper classes that only define _attrs don't need extra testing. @@ -85,28 +111,25 @@ class ApiHelperTests(test.TestCase): """ Tests for functions that don't use one of the api objects """ def test_url_for(self): - GLANCE_URL = 'http://glance/glanceapi/' - NOVA_URL = 'http://nova/novapi/' + url = api_base.url_for(self.request, 'image') + self.assertEqual(url, 'http://internal.glance.example.com:9292/v1') - url = api.url_for(self.request, 'image') - self.assertEqual(url, GLANCE_URL + 'internal') + url = api_base.url_for(self.request, 'image', admin=False) + self.assertEqual(url, 'http://internal.glance.example.com:9292/v1') - url = api.url_for(self.request, 'image', admin=False) - self.assertEqual(url, GLANCE_URL + 'internal') + url = api_base.url_for(self.request, 'image', admin=True) + self.assertEqual(url, 'http://admin.glance.example.com:9292/v1') - url = api.url_for(self.request, 'image', admin=True) - self.assertEqual(url, GLANCE_URL + 'admin') + url = api_base.url_for(self.request, 'compute') + self.assertEqual(url, 'http://internal.nova.example.com:8774/v1.0') - url = api.url_for(self.request, 'compute') - self.assertEqual(url, NOVA_URL + 'internal') + url = api_base.url_for(self.request, 'compute', admin=False) + self.assertEqual(url, 'http://internal.nova.example.com:8774/v1.0') - url = api.url_for(self.request, 'compute', admin=False) - self.assertEqual(url, NOVA_URL + 'internal') - - url = api.url_for(self.request, 'compute', admin=True) - self.assertEqual(url, NOVA_URL + 'admin') + url = api_base.url_for(self.request, 'compute', admin=True) + self.assertEqual(url, 'http://admin.nova.example.com:8774/v1.0') self.assertNotIn('notAnApi', self.request.user.service_catalog, 'Select a new nonexistent service catalog key') with self.assertRaises(exceptions.ServiceCatalogException): - url = api.url_for(self.request, 'notAnApi') + url = api_base.url_for(self.request, 'notAnApi') diff --git a/horizon/horizon/tests/api_tests/glance.py b/horizon/horizon/tests/api_tests/glance.py deleted file mode 100644 index 8b4471ee8..000000000 --- a/horizon/horizon/tests/api_tests/glance.py +++ /dev/null @@ -1,164 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright 2012 Nebula, Inc. -# -# 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 __future__ import absolute_import - -from django import http -from glance import client as glance_client -from mox import IsA - -from horizon.tests.api_tests.utils import * - - -class GlanceApiTests(APITestCase): - def stub_glance_api(self, count=1): - self.mox.StubOutWithMock(api.glance, 'glance_api') - glance_api = self.mox.CreateMock(glance_client.Client) - glance_api.token = TEST_TOKEN - for i in range(count): - api.glance.glance_api(IsA(http.HttpRequest)).AndReturn(glance_api) - return glance_api - - def test_get_glance_api(self): - self.mox.StubOutClassWithMocks(glance_client, 'Client') - client_instance = glance_client.Client(TEST_HOSTNAME, TEST_PORT, - auth_tok=TEST_TOKEN) - # Normally ``auth_tok`` is set in ``Client.__init__``, but mox doesn't - # duplicate that behavior so we set it manually. - client_instance.auth_tok = TEST_TOKEN - - self.mox.StubOutWithMock(api.glance, 'url_for') - api.glance.url_for(IsA(http.HttpRequest), 'image').AndReturn(TEST_URL) - - self.mox.ReplayAll() - - ret_val = api.glance.glance_api(self.request) - self.assertIsNotNone(ret_val) - self.assertEqual(ret_val.auth_tok, TEST_TOKEN) - - def test_image_create(self): - IMAGE_FILE = 'someData' - IMAGE_META = {'metadata': 'foo'} - - glance_api = self.stub_glance_api() - glance_api.add_image(IMAGE_META, IMAGE_FILE).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.image_create(self.request, IMAGE_META, IMAGE_FILE) - - self.assertIsInstance(ret_val, api.Image) - self.assertEqual(ret_val._apidict, TEST_RETURN) - - def test_image_delete(self): - IMAGE_ID = '1' - - glance_api = self.stub_glance_api() - glance_api.delete_image(IMAGE_ID).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.image_delete(self.request, IMAGE_ID) - - self.assertEqual(ret_val, TEST_RETURN) - - def test_image_get_meta(self): - IMAGE_ID = '1' - - glance_api = self.stub_glance_api() - glance_api.get_image_meta(IMAGE_ID).AndReturn([TEST_RETURN]) - - self.mox.ReplayAll() - - ret_val = api.image_get_meta(self.request, IMAGE_ID) - - self.assertIsInstance(ret_val, api.Image) - self.assertEqual(ret_val._apidict, [TEST_RETURN]) - - def test_image_list_detailed(self): - images = (TEST_RETURN, TEST_RETURN + '2') - glance_api = self.stub_glance_api() - glance_api.get_images_detailed().AndReturn(images) - - self.mox.ReplayAll() - - ret_val = api.image_list_detailed(self.request) - - self.assertEqual(len(ret_val), len(images)) - for image in ret_val: - self.assertIsInstance(image, api.Image) - self.assertIn(image._apidict, images) - - def test_image_update(self): - IMAGE_ID = '1' - IMAGE_META = {'metadata': 'foobar'} - - glance_api = self.stub_glance_api(count=2) - glance_api.update_image(IMAGE_ID, image_meta={}).AndReturn(TEST_RETURN) - glance_api.update_image(IMAGE_ID, - image_meta=IMAGE_META).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.image_update(self.request, IMAGE_ID) - - self.assertIsInstance(ret_val, api.Image) - self.assertEqual(ret_val._apidict, TEST_RETURN) - - ret_val = api.image_update(self.request, - IMAGE_ID, - image_meta=IMAGE_META) - - self.assertIsInstance(ret_val, api.Image) - self.assertEqual(ret_val._apidict, TEST_RETURN) - - -# Wrapper classes that have other attributes or methods need testing -class ImageWrapperTests(test.TestCase): - dict_with_properties = { - 'properties': - {'image_state': 'running'}, - 'size': 100, - } - dict_without_properties = { - 'size': 100, - } - - def test_get_properties(self): - image = api.Image(self.dict_with_properties) - image_props = image.properties - self.assertIsInstance(image_props, api.ImageProperties) - self.assertEqual(image_props.image_state, 'running') - - def test_get_other(self): - image = api.Image(self.dict_with_properties) - self.assertEqual(image.size, 100) - - def test_get_properties_missing(self): - image = api.Image(self.dict_without_properties) - with self.assertRaises(AttributeError): - image.properties - - def test_get_other_missing(self): - image = api.Image(self.dict_without_properties) - with self.assertRaises(AttributeError): - self.assertNotIn('missing', image._attrs, - msg="Test assumption broken. Find new missing attribute") - image.missing diff --git a/horizon/horizon/tests/api_tests/glance_tests.py b/horizon/horizon/tests/api_tests/glance_tests.py new file mode 100644 index 000000000..7a1b0423a --- /dev/null +++ b/horizon/horizon/tests/api_tests/glance_tests.py @@ -0,0 +1,86 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# +# 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 horizon import api +from horizon import test + + +class GlanceApiTests(test.APITestCase): + def test_get_glanceclient(self): + """ Verify the client connection method does what we expect. """ + # Replace the original client which is stubbed out in setUp() + api.glance.glanceclient = self._original_glanceclient + + client = api.glance.glanceclient(self.request) + self.assertEqual(client.auth_tok, self.tokens.first().id) + + def test_image_get_meta(self): + """ Verify "get" returns our custom Image class. """ + image = self.images.get(id='1') + + glanceclient = self.stub_glanceclient() + glanceclient.get_image_meta(image.id).AndReturn(image) + self.mox.ReplayAll() + + ret_val = api.image_get_meta(self.request, image.id) + self.assertIsInstance(ret_val, api.glance.Image) + self.assertEqual(ret_val._apidict, image) + + def test_image_list_detailed(self): + """ Verify "list" returns our custom Image class. """ + images = self.images.list() + + glanceclient = self.stub_glanceclient() + glanceclient.get_images_detailed().AndReturn(images) + self.mox.ReplayAll() + + ret_val = api.image_list_detailed(self.request) + for image in ret_val: + self.assertIsInstance(image, api.glance.Image) + self.assertIn(image._apidict, images) + + +class ImageWrapperTests(test.TestCase): + """ Tests for wrapper classes since they have extra logic attached. """ + WITHOUT_PROPERTIES = {'size': 100} + WITH_PROPERTIES = {'properties': {'image_state': 'running'}, + 'size': 100} + + def test_get_properties(self): + image = api.Image(self.WITH_PROPERTIES) + image_props = image.properties + self.assertIsInstance(image_props, api.ImageProperties) + self.assertEqual(image_props.image_state, 'running') + + def test_get_other(self): + image = api.Image(self.WITH_PROPERTIES) + self.assertEqual(image.size, 100) + + def test_get_properties_missing(self): + image = api.Image(self.WITHOUT_PROPERTIES) + with self.assertRaises(AttributeError): + image.properties + + def test_get_other_missing(self): + image = api.Image(self.WITHOUT_PROPERTIES) + with self.assertRaises(AttributeError): + self.assertNotIn('missing', image._attrs, + msg="Test assumption broken. Find new missing attribute") + image.missing diff --git a/horizon/horizon/tests/api_tests/keystone.py b/horizon/horizon/tests/api_tests/keystone.py deleted file mode 100644 index 4c08d3b40..000000000 --- a/horizon/horizon/tests/api_tests/keystone.py +++ /dev/null @@ -1,224 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright 2012 Nebula, Inc. -# -# 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 __future__ import absolute_import - -from django.conf import settings -from keystoneclient.v2_0.roles import Role, RoleManager - -from horizon import api -from horizon.tests.api_tests.utils import (APITestCase, - TEST_RETURN, TEST_URL, TEST_USERNAME, TEST_TENANT_ID, TEST_TOKEN_ID, - TEST_TENANT_NAME, TEST_PASSWORD, TEST_EMAIL) - - -class Token(object): - """ More or less fakes what the api is looking for """ - def __init__(self, id, username, tenant_id, - tenant_name, serviceCatalog=None): - self.id = id - self.user = {'name': username} - self.tenant = {'id': tenant_id, 'name': tenant_name} - self.serviceCatalog = serviceCatalog - - def __eq__(self, other): - return self.id == other.id and \ - self.user['name'] == other.user['name'] and \ - self.tenant_id == other.tenant_id and \ - self.serviceCatalog == other.serviceCatalog - - def __ne__(self, other): - return not self == other - - -class TokenApiTests(APITestCase): - def setUp(self): - super(TokenApiTests, self).setUp() - self._prev_OPENSTACK_KEYSTONE_URL = getattr(settings, - 'OPENSTACK_KEYSTONE_URL', - None) - settings.OPENSTACK_KEYSTONE_URL = TEST_URL - - def tearDown(self): - super(TokenApiTests, self).tearDown() - settings.OPENSTACK_KEYSTONE_URL = self._prev_OPENSTACK_KEYSTONE_URL - - def test_token_create(self): - test_token = Token(TEST_TOKEN_ID, TEST_USERNAME, - TEST_TENANT_ID, TEST_TENANT_NAME) - - keystoneclient = self.stub_keystoneclient() - - keystoneclient.tokens = self.mox.CreateMockAnything() - keystoneclient.tokens.authenticate(username=TEST_USERNAME, - password=TEST_PASSWORD, - tenant_id=TEST_TENANT_ID)\ - .AndReturn(test_token) - - self.mox.ReplayAll() - - ret_val = api.token_create(self.request, TEST_TENANT_ID, - TEST_USERNAME, TEST_PASSWORD) - - self.assertEqual(test_token.tenant['id'], ret_val.tenant['id']) - - -class RoleAPITests(APITestCase): - def setUp(self): - super(RoleAPITests, self).setUp() - self.role = Role(RoleManager, - {'id': '2', - 'name': settings.OPENSTACK_KEYSTONE_DEFAULT_ROLE}) - self.roles = (self.role,) - - def test_remove_tenant_user(self): - """ - Tests api.keystone.remove_tenant_user. - - Verifies that remove_tenant_user is called with the right arguments - after iterating the user's roles. - - There are no assertions in this test because the checking is handled - by mox in the VerifyAll() call in tearDown(). - """ - keystoneclient = self.stub_keystoneclient() - - keystoneclient.roles = self.mox.CreateMockAnything() - keystoneclient.roles.roles_for_user(TEST_USERNAME, TEST_TENANT_ID) \ - .AndReturn(self.roles) - keystoneclient.roles.remove_user_role(TEST_USERNAME, - self.role.id, - TEST_TENANT_ID) \ - .AndReturn(self.role) - self.mox.ReplayAll() - api.keystone.remove_tenant_user(self.request, - TEST_TENANT_ID, - TEST_USERNAME) - - def test_get_default_role(self): - keystoneclient = self.stub_keystoneclient() - keystoneclient.roles = self.mox.CreateMockAnything() - keystoneclient.roles.list().AndReturn(self.roles) - self.mox.ReplayAll() - role = api.keystone.get_default_role(self.request) - self.assertEqual(role, self.role) - # Verify that a second call doesn't hit the API again, - # (it would show up in mox as an unexpected method call) - role = api.keystone.get_default_role(self.request) - - -class UserAPITests(APITestCase): - def test_user_create(self): - keystoneclient = self.stub_keystoneclient() - - keystoneclient.users = self.mox.CreateMockAnything() - keystoneclient.users.create(TEST_USERNAME, TEST_PASSWORD, TEST_EMAIL, - TEST_TENANT_ID, True).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.user_create(self.request, TEST_USERNAME, TEST_EMAIL, - TEST_PASSWORD, TEST_TENANT_ID, True) - - self.assertIsInstance(ret_val, api.User) - self.assertEqual(ret_val._apiresource, TEST_RETURN) - - def test_user_delete(self): - keystoneclient = self.stub_keystoneclient() - - keystoneclient.users = self.mox.CreateMockAnything() - keystoneclient.users.delete(TEST_USERNAME).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.user_delete(self.request, TEST_USERNAME) - - self.assertIsNone(ret_val) - - def test_user_get(self): - keystoneclient = self.stub_keystoneclient() - - keystoneclient.users = self.mox.CreateMockAnything() - keystoneclient.users.get(TEST_USERNAME).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.user_get(self.request, TEST_USERNAME) - - self.assertIsInstance(ret_val, api.User) - self.assertEqual(ret_val._apiresource, TEST_RETURN) - - def test_user_list(self): - users = (TEST_USERNAME, TEST_USERNAME + '2') - - keystoneclient = self.stub_keystoneclient() - keystoneclient.users = self.mox.CreateMockAnything() - keystoneclient.users.list(tenant_id=None).AndReturn(users) - - self.mox.ReplayAll() - - ret_val = api.user_list(self.request) - - self.assertEqual(len(ret_val), len(users)) - for user in ret_val: - self.assertIsInstance(user, api.User) - self.assertIn(user._apiresource, users) - - def test_user_update_email(self): - keystoneclient = self.stub_keystoneclient() - keystoneclient.users = self.mox.CreateMockAnything() - keystoneclient.users.update_email(TEST_USERNAME, - TEST_EMAIL).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.user_update_email(self.request, TEST_USERNAME, - TEST_EMAIL) - - self.assertIsInstance(ret_val, api.User) - self.assertEqual(ret_val._apiresource, TEST_RETURN) - - def test_user_update_password(self): - keystoneclient = self.stub_keystoneclient() - keystoneclient.users = self.mox.CreateMockAnything() - keystoneclient.users.update_password(TEST_USERNAME, - TEST_PASSWORD).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.user_update_password(self.request, TEST_USERNAME, - TEST_PASSWORD) - - self.assertIsInstance(ret_val, api.User) - self.assertEqual(ret_val._apiresource, TEST_RETURN) - - def test_user_update_tenant(self): - keystoneclient = self.stub_keystoneclient() - keystoneclient.users = self.mox.CreateMockAnything() - keystoneclient.users.update_tenant(TEST_USERNAME, - TEST_TENANT_ID).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.user_update_tenant(self.request, TEST_USERNAME, - TEST_TENANT_ID) - - self.assertIsInstance(ret_val, api.User) - self.assertEqual(ret_val._apiresource, TEST_RETURN) diff --git a/horizon/horizon/tests/api_tests/keystone_tests.py b/horizon/horizon/tests/api_tests/keystone_tests.py new file mode 100644 index 000000000..b3a00b173 --- /dev/null +++ b/horizon/horizon/tests/api_tests/keystone_tests.py @@ -0,0 +1,84 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# +# 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 __future__ import absolute_import + +from horizon import api +from horizon import test + + +class TokenApiTests(test.APITestCase): + def test_token_create(self): + token = self.tokens.scoped_token + keystoneclient = self.stub_keystoneclient() + + keystoneclient.tokens = self.mox.CreateMockAnything() + keystoneclient.tokens.authenticate(username=self.user.name, + password=self.user.password, + tenant_id=token.tenant['id'])\ + .AndReturn(token) + + self.mox.ReplayAll() + + ret_val = api.token_create(self.request, token.tenant['id'], + self.user.name, self.user.password) + + self.assertEqual(token.tenant['id'], ret_val.tenant['id']) + + +class RoleAPITests(test.APITestCase): + def setUp(self): + super(RoleAPITests, self).setUp() + self.role = self.roles.member + self.roles = self.roles.list() + + def test_remove_tenant_user(self): + """ + Tests api.keystone.remove_tenant_user + + Verifies that remove_tenant_user is called with the right arguments + after iterating the user's roles. + + There are no assertions in this test because the checking is handled + by mox in the VerifyAll() call in tearDown(). + """ + keystoneclient = self.stub_keystoneclient() + tenant = self.tenants.first() + + keystoneclient.roles = self.mox.CreateMockAnything() + keystoneclient.roles.roles_for_user(self.user.id, + tenant.id).AndReturn(self.roles) + for role in self.roles: + keystoneclient.roles.remove_user_role(self.user.id, + role.id, + tenant.id) + self.mox.ReplayAll() + api.keystone.remove_tenant_user(self.request, tenant.id, self.user.id) + + def test_get_default_role(self): + keystoneclient = self.stub_keystoneclient() + keystoneclient.roles = self.mox.CreateMockAnything() + keystoneclient.roles.list().AndReturn(self.roles) + self.mox.ReplayAll() + role = api.keystone.get_default_role(self.request) + self.assertEqual(role, self.role) + # Verify that a second call doesn't hit the API again, + # (it would show up in mox as an unexpected method call) + role = api.keystone.get_default_role(self.request) diff --git a/horizon/horizon/tests/api_tests/nova.py b/horizon/horizon/tests/api_tests/nova.py deleted file mode 100644 index 6ae41bdd8..000000000 --- a/horizon/horizon/tests/api_tests/nova.py +++ /dev/null @@ -1,617 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright 2012 Nebula, Inc. -# Copyright (c) 2012 X.commerce, a business unit of eBay Inc. -# -# 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 __future__ import absolute_import - -from django import http -from mox import IsA, IgnoreArg -from novaclient.v1_1 import servers - - -from horizon.tests.api_tests.utils import * - - -class Server(object): - """ More or less fakes what the api is looking for """ - def __init__(self, id, image, attrs=None): - self.id = id - - self.image = image - if attrs is not None: - self.attrs = attrs - - def __eq__(self, other): - if self.id != other.id or \ - self.image['id'] != other.image['id']: - return False - - for k in self.attrs: - if other.attrs.__getattr__(k) != v: - return False - - return True - - def __ne__(self, other): - return not self == other - - -class ServerWrapperTests(test.TestCase): - HOST = 'hostname' - ID = '1' - IMAGE_NAME = 'imageName' - IMAGE_OBJ = {'id': '3', 'links': [{'href': '3', u'rel': u'bookmark'}]} - - def setUp(self): - super(ServerWrapperTests, self).setUp() - - # these are all objects "fetched" from the api - self.inner_attrs = {'host': self.HOST} - - self.inner_server = Server(self.ID, self.IMAGE_OBJ, self.inner_attrs) - self.inner_server_no_attrs = Server(self.ID, self.IMAGE_OBJ) - - #self.request = self.mox.CreateMock(http.HttpRequest) - - def test_get_other(self): - server = api.Server(self.inner_server, self.request) - self.assertEqual(server.id, self.ID) - - def test_get_attrs_missing(self): - server = api.Server(self.inner_server_no_attrs, self.request) - with self.assertRaises(AttributeError): - server.attrs - - def test_get_other_missing(self): - server = api.Server(self.inner_server, self.request) - with self.assertRaises(AttributeError): - self.assertNotIn('missing', server._attrs, - msg="Test assumption broken. Find new missing attribute") - server.missing - - def test_image_name(self): - image = api.Image({'name': self.IMAGE_NAME}) - self.mox.StubOutWithMock(api.glance, 'image_get_meta') - api.glance.image_get_meta(IsA(http.HttpRequest), - self.IMAGE_OBJ['id']).AndReturn(image) - - server = api.Server(self.inner_server, self.request) - - self.mox.ReplayAll() - - image_name = server.image_name - - self.assertEqual(image_name, self.IMAGE_NAME) - - -class ComputeApiTests(APITestCase): - - def setUp(self): - super(ComputeApiTests, self).setUp() - keypair = api.KeyPair(APIResource.get_instance()) - keypair.id = 1 - keypair.name = TEST_RETURN - - self.keypair = keypair - self.keypairs = [keypair, ] - - floating_ip = api.FloatingIp(APIResource.get_instance()) - floating_ip.id = 1 - floating_ip.fixed_ip = '10.0.0.4' - floating_ip.instance_id = 1 - floating_ip.ip = '58.58.58.58' - - self.floating_ip = floating_ip - self.floating_ips = [floating_ip, ] - - server = api.Server(APIResource.get_instance(), self.request) - server.id = 1 - - self.server = server - self.servers = [server, ] - - def test_flavor_create(self): - FLAVOR_DISK = 1000 - FLAVOR_ID = 6 - FLAVOR_MEMORY = 1024 - FLAVOR_NAME = 'newFlavor' - FLAVOR_VCPU = 2 - - novaclient = self.stub_novaclient() - - novaclient.flavors = self.mox.CreateMockAnything() - novaclient.flavors.create(FLAVOR_NAME, FLAVOR_MEMORY, FLAVOR_VCPU, - FLAVOR_DISK, FLAVOR_ID).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.flavor_create(self.request, FLAVOR_NAME, - str(FLAVOR_MEMORY), str(FLAVOR_VCPU), - str(FLAVOR_DISK), FLAVOR_ID) - - self.assertIsInstance(ret_val, api.Flavor) - self.assertEqual(ret_val._apiresource, TEST_RETURN) - - def test_flavor_delete(self): - FLAVOR_ID = 6 - - novaclient = self.stub_novaclient() - - novaclient.flavors = self.mox.CreateMockAnything() - novaclient.flavors.delete(FLAVOR_ID).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.flavor_delete(self.request, FLAVOR_ID) - self.assertIsNone(ret_val) - - def test_flavor_get(self): - FLAVOR_ID = 6 - - novaclient = self.stub_novaclient() - - novaclient.flavors = self.mox.CreateMockAnything() - novaclient.flavors.get(FLAVOR_ID).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.flavor_get(self.request, FLAVOR_ID) - self.assertIsInstance(ret_val, api.Flavor) - self.assertEqual(ret_val._apiresource, TEST_RETURN) - - def test_server_delete(self): - INSTANCE = 'anInstance' - - novaclient = self.stub_novaclient() - novaclient.servers = self.mox.CreateMockAnything() - novaclient.servers.delete(INSTANCE).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.server_delete(self.request, INSTANCE) - - self.assertIsNone(ret_val) - - def test_server_pause(self): - INSTANCE = 'anInstance' - - novaclient = self.stub_novaclient() - novaclient.servers = self.mox.CreateMockAnything() - novaclient.servers.pause(INSTANCE).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - server = self.mox.CreateMock(servers.Server) - - ret_val = api.server_pause(self.request, INSTANCE) - - self.assertIsNone(ret_val) - - def test_server_unpause(self): - INSTANCE = 'anInstance' - - novaclient = self.stub_novaclient() - novaclient.servers = self.mox.CreateMockAnything() - novaclient.servers.unpause(INSTANCE).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.server_unpause(self.request, INSTANCE) - - self.assertIsNone(ret_val) - - def test_server_suspend(self): - INSTANCE = 'anInstance' - - novaclient = self.stub_novaclient() - novaclient.servers = self.mox.CreateMockAnything() - novaclient.servers.suspend(INSTANCE).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.server_suspend(self.request, INSTANCE) - - self.assertIsNone(ret_val) - - def test_server_resume(self): - INSTANCE = 'anInstance' - - novaclient = self.stub_novaclient() - novaclient.servers = self.mox.CreateMockAnything() - novaclient.servers.resume(INSTANCE).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.server_resume(self.request, INSTANCE) - - self.assertIsNone(ret_val) - - def test_server_reboot(self): - INSTANCE_ID = '2' - HARDNESS = servers.REBOOT_HARD - - server = self.mox.CreateMock(servers.Server) - server.reboot(HARDNESS) - - self.mox.StubOutWithMock(api.nova, 'server_get') - - api.nova.server_get(IsA(http.HttpRequest), - INSTANCE_ID).AndReturn(server) - - self.mox.ReplayAll() - - ret_val = api.server_reboot(self.request, INSTANCE_ID) - self.assertIsNone(ret_val) - - def test_server_create(self): - NAME = 'server' - IMAGE = 'anImage' - FLAVOR = 'cherry' - USER_DATA = {'nuts': 'berries'} - KEY = 'user' - SECGROUP = self.mox.CreateMock(api.SecurityGroup) - BLOCK_DEVICE_MAPPING = {'vda': '1:::0'} - - novaclient = self.stub_novaclient() - novaclient.servers = self.mox.CreateMockAnything() - novaclient.servers.create(NAME, IMAGE, FLAVOR, userdata=USER_DATA, - security_groups=[SECGROUP], key_name=KEY, - block_device_mapping=BLOCK_DEVICE_MAPPING, - min_count=IsA(int)).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.server_create(self.request, NAME, IMAGE, FLAVOR, - KEY, USER_DATA, [SECGROUP], - BLOCK_DEVICE_MAPPING, - instance_count=1) - - self.assertIsInstance(ret_val, api.Server) - self.assertEqual(ret_val._apiresource, TEST_RETURN) - - def test_server_vnc_console(self): - fake_console = {'console': {'url': 'http://fake', 'type': ''}} - novaclient = self.stub_novaclient() - novaclient.servers = self.mox.CreateMockAnything() - novaclient.servers.get_vnc_console( - TEST_INSTANCE_ID, TEST_CONSOLE_TYPE).AndReturn(fake_console) - novaclient.servers.get_vnc_console( - TEST_INSTANCE_ID, 'novnc').AndReturn(fake_console) - - self.mox.ReplayAll() - - ret_val = api.server_vnc_console(self.request, - TEST_INSTANCE_ID, - TEST_CONSOLE_TYPE) - self.assertIsInstance(ret_val, api.VNCConsole) - self.assertEqual(ret_val._apidict, fake_console['console']) - - ret_val = api.server_vnc_console(self.request, TEST_INSTANCE_ID) - self.assertIsInstance(ret_val, api.VNCConsole) - self.assertEqual(ret_val._apidict, fake_console['console']) - - def test_flavor_list(self): - flavors = (TEST_RETURN, TEST_RETURN + '2') - novaclient = self.stub_novaclient() - novaclient.flavors = self.mox.CreateMockAnything() - novaclient.flavors.list().AndReturn(flavors) - - self.mox.ReplayAll() - - ret_val = api.flavor_list(self.request) - - self.assertEqual(len(ret_val), len(flavors)) - for flavor in ret_val: - self.assertIsInstance(flavor, api.Flavor) - self.assertIn(flavor._apiresource, flavors) - - def test_server_list(self): - servers = (TEST_RETURN, TEST_RETURN + '2') - - novaclient = self.stub_novaclient() - - novaclient.servers = self.mox.CreateMockAnything() - novaclient.servers.list(True, {'project_id': '1'}).AndReturn(servers) - - self.mox.ReplayAll() - - ret_val = api.server_list(self.request) - - self.assertEqual(len(ret_val), len(servers)) - for server in ret_val: - self.assertIsInstance(server, api.Server) - self.assertIn(server._apiresource, servers) - - def test_usage_get(self): - novaclient = self.stub_novaclient() - - novaclient.usage = self.mox.CreateMockAnything() - novaclient.usage.get(TEST_TENANT_ID, 'start', - 'end').AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.usage_get(self.request, TEST_TENANT_ID, 'start', 'end') - - self.assertIsInstance(ret_val, api.Usage) - self.assertEqual(ret_val._apiresource, TEST_RETURN) - - def test_usage_list(self): - usages = (TEST_RETURN, TEST_RETURN + '2') - - novaclient = self.stub_novaclient() - - novaclient.usage = self.mox.CreateMockAnything() - novaclient.usage.list('start', 'end', True).AndReturn(usages) - - self.mox.ReplayAll() - - ret_val = api.usage_list(self.request, 'start', 'end') - - self.assertEqual(len(ret_val), len(usages)) - for usage in ret_val: - self.assertIsInstance(usage, api.Usage) - self.assertIn(usage._apiresource, usages) - - def test_server_get(self): - INSTANCE_ID = '2' - - novaclient = self.stub_novaclient() - novaclient.servers = self.mox.CreateMockAnything() - novaclient.servers.get(INSTANCE_ID).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.server_get(self.request, INSTANCE_ID) - - self.assertIsInstance(ret_val, api.Server) - self.assertEqual(ret_val._apiresource, TEST_RETURN) - - def test_server_snapshot_create(self): - novaclient = self.stub_novaclient() - - novaclient.servers = self.mox.CreateMockAnything() - novaclient.servers.create_image(IsA(int), IsA(str)).\ - AndReturn(self.server) - self.mox.ReplayAll() - - server = api.snapshot_create(self.request, 1, 'test-snapshot') - - self.assertIsInstance(server, api.Server) - - def test_tenant_floating_ip_list(self): - novaclient = self.stub_novaclient() - - novaclient.floating_ips = self.mox.CreateMockAnything() - novaclient.floating_ips.list().AndReturn(self.floating_ips) - self.mox.ReplayAll() - - floating_ips = api.tenant_floating_ip_list(self.request) - - self.assertEqual(len(floating_ips), len(self.floating_ips)) - self.assertIsInstance(floating_ips[0], api.FloatingIp) - - def test_tenant_floating_ip_get(self): - novaclient = self.stub_novaclient() - - novaclient.floating_ips = self.mox.CreateMockAnything() - novaclient.floating_ips.get(IsA(int)).AndReturn(self.floating_ip) - self.mox.ReplayAll() - - floating_ip = api.tenant_floating_ip_get(self.request, 1) - - self.assertIsInstance(floating_ip, api.FloatingIp) - - def test_tenant_floating_ip_allocate_without_pool(self): - novaclient = self.stub_novaclient() - - novaclient.floating_ips = self.mox.CreateMockAnything() - novaclient.floating_ips.create(pool=IgnoreArg()).\ - AndReturn(self.floating_ip) - self.mox.ReplayAll() - - floating_ip = api.tenant_floating_ip_allocate(self.request) - - self.assertIsInstance(floating_ip, api.FloatingIp) - - def test_tenant_floating_ip_allocate_with_pool(self): - novaclient = self.stub_novaclient() - - novaclient.floating_ips = self.mox.CreateMockAnything() - novaclient.floating_ips.create(pool="nova").AndReturn(self.floating_ip) - self.mox.ReplayAll() - - floating_ip = api.tenant_floating_ip_allocate(self.request, - pool='nova') - - self.assertIsInstance(floating_ip, api.FloatingIp) - - def test_tenant_floating_ip_release(self): - novaclient = self.stub_novaclient() - - novaclient.floating_ips = self.mox.CreateMockAnything() - novaclient.floating_ips.delete(1).AndReturn(self.floating_ip) - self.mox.ReplayAll() - - floating_ip = api.tenant_floating_ip_release(self.request, 1) - - self.assertIsInstance(floating_ip, api.FloatingIp) - - def test_server_remove_floating_ip(self): - novaclient = self.stub_novaclient() - - novaclient.servers = self.mox.CreateMockAnything() - novaclient.floating_ips = self.mox.CreateMockAnything() - - novaclient.servers.get(IsA(int)).AndReturn(self.server) - novaclient.floating_ips.get(IsA(int)).AndReturn(self.floating_ip) - novaclient.servers.remove_floating_ip(IsA(self.server.__class__), - IsA(self.floating_ip.__class__)) \ - .AndReturn(self.server) - self.mox.ReplayAll() - - server = api.server_remove_floating_ip(self.request, 1, 1) - - self.assertIsInstance(server, api.Server) - - def test_server_add_floating_ip(self): - novaclient = self.stub_novaclient() - - novaclient.floating_ips = self.mox.CreateMockAnything() - novaclient.servers = self.mox.CreateMockAnything() - - novaclient.servers.get(IsA(int)).AndReturn(self.server) - novaclient.floating_ips.get(IsA(int)).AndReturn(self.floating_ip) - novaclient.servers.add_floating_ip(IsA(self.server.__class__), - IsA(self.floating_ip.__class__)) \ - .AndReturn(self.server) - self.mox.ReplayAll() - - server = api.server_add_floating_ip(self.request, 1, 1) - - self.assertIsInstance(server, api.Server) - - def test_keypair_create(self): - novaclient = self.stub_novaclient() - - novaclient.keypairs = self.mox.CreateMockAnything() - novaclient.keypairs.create(IsA(str)).AndReturn(self.keypair) - self.mox.ReplayAll() - - ret_val = api.keypair_create(self.request, TEST_RETURN) - self.assertIsInstance(ret_val, api.KeyPair) - self.assertEqual(ret_val.name, self.keypair.name) - - def test_keypair_import(self): - novaclient = self.stub_novaclient() - - novaclient.keypairs = self.mox.CreateMockAnything() - novaclient.keypairs.create(IsA(str), IsA(str)).AndReturn(self.keypair) - self.mox.ReplayAll() - - ret_val = api.keypair_import(self.request, TEST_RETURN, TEST_RETURN) - self.assertIsInstance(ret_val, api.KeyPair) - self.assertEqual(ret_val.name, self.keypair.name) - - def test_keypair_delete(self): - novaclient = self.stub_novaclient() - - novaclient.keypairs = self.mox.CreateMockAnything() - novaclient.keypairs.delete(IsA(int)) - - self.mox.ReplayAll() - - ret_val = api.keypair_delete(self.request, self.keypair.id) - self.assertIsNone(ret_val) - - def test_keypair_list(self): - novaclient = self.stub_novaclient() - - novaclient.keypairs = self.mox.CreateMockAnything() - novaclient.keypairs.list().AndReturn(self.keypairs) - - self.mox.ReplayAll() - - ret_val = api.keypair_list(self.request) - - self.assertEqual(len(ret_val), len(self.keypairs)) - for keypair in ret_val: - self.assertIsInstance(keypair, api.KeyPair) - - -class VolumeTests(APITestCase): - def setUp(self): - super(VolumeTests, self).setUp() - volume = api.Volume(APIResource.get_instance()) - volume.id = 1 - volume.displayName = "displayName" - volume.attachments = [{"device": "/dev/vdb", - "serverId": 1, - "id": 1, - "volumeId": 1}] - self.volume = volume - self.volumes = [volume, ] - - self.novaclient = self.stub_novaclient() - self.novaclient.volumes = self.mox.CreateMockAnything() - - def test_volume_list(self): - self.novaclient.volumes.list().AndReturn(self.volumes) - self.mox.ReplayAll() - - volumes = api.volume_list(self.request) - - self.assertIsInstance(volumes[0], api.Volume) - - def test_volume_get(self): - self.novaclient.volumes.get(IsA(int)).AndReturn(self.volume) - self.mox.ReplayAll() - - volume = api.volume_get(self.request, 1) - - self.assertIsInstance(volume, api.Volume) - - def test_volume_instance_list(self): - self.novaclient.volumes.get_server_volumes(IsA(int)).AndReturn( - self.volume.attachments) - self.mox.ReplayAll() - - attachments = api.volume_instance_list(self.request, 1) - - self.assertEqual(attachments, self.volume.attachments) - - def test_volume_create(self): - self.novaclient.volumes.create(IsA(int), - display_name=IsA(str), - display_description=IsA(str)) \ - .AndReturn(self.volume) - self.mox.ReplayAll() - - new_volume = api.volume_create(self.request, - 10, - "new volume", - "new description") - - self.assertIsInstance(new_volume, api.Volume) - - def test_volume_delete(self): - self.novaclient.volumes.delete(IsA(int)) - self.mox.ReplayAll() - - ret_val = api.volume_delete(self.request, 1) - - self.assertIsNone(ret_val) - - def test_volume_attach(self): - self.novaclient.volumes.create_server_volume( - IsA(int), IsA(int), IsA(str)) - self.mox.ReplayAll() - - ret_val = api.volume_attach(self.request, 1, 1, "/dev/vdb") - - self.assertIsNone(ret_val) - - def test_volume_detach(self): - self.novaclient.volumes.delete_server_volume(IsA(int), IsA(int)) - self.mox.ReplayAll() - - ret_val = api.volume_detach(self.request, 1, 1) - - self.assertIsNone(ret_val) diff --git a/horizon/horizon/tests/api_tests/nova_tests.py b/horizon/horizon/tests/api_tests/nova_tests.py new file mode 100644 index 000000000..41160f3f0 --- /dev/null +++ b/horizon/horizon/tests/api_tests/nova_tests.py @@ -0,0 +1,158 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# Copyright (c) 2012 X.commerce, a business unit of eBay Inc. +# +# 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 __future__ import absolute_import + +from django import http +from mox import IsA +from novaclient.v1_1 import servers + +from horizon import api +from horizon import test + + +class ServerWrapperTests(test.TestCase): + def test_get_base_attribute(self): + server = api.nova.Server(self.servers.first(), self.request) + self.assertEqual(server.id, self.servers.first().id) + + def test_image_name(self): + image = api.Image(self.images.first()) + self.mox.StubOutWithMock(api.glance, 'image_get_meta') + api.glance.image_get_meta(IsA(http.HttpRequest), + image.id).AndReturn(image) + self.mox.ReplayAll() + + server = api.Server(self.servers.first(), self.request) + self.assertEqual(server.image_name, image.name) + + +class ComputeApiTests(test.APITestCase): + def test_server_reboot(self): + server = self.servers.first() + HARDNESS = servers.REBOOT_HARD + + novaclient = self.stub_novaclient() + novaclient.servers = self.mox.CreateMockAnything() + novaclient.servers.get(server.id).AndReturn(server) + novaclient.servers.reboot(server.id, HARDNESS) + self.mox.ReplayAll() + + ret_val = api.nova.server_reboot(self.request, server.id) + self.assertIsNone(ret_val) + + def test_server_vnc_console(self): + server = self.servers.first() + console = self.servers.console_data + console_type = console["console"]["type"] + + novaclient = self.stub_novaclient() + novaclient.servers = self.mox.CreateMockAnything() + novaclient.servers.get_vnc_console(server.id, + console_type).AndReturn(console) + self.mox.ReplayAll() + + ret_val = api.server_vnc_console(self.request, + server.id, + console_type) + self.assertIsInstance(ret_val, api.nova.VNCConsole) + + def test_server_list(self): + servers = self.servers.list() + + novaclient = self.stub_novaclient() + novaclient.servers = self.mox.CreateMockAnything() + novaclient.servers.list(True, {'all_tenants': True}).AndReturn(servers) + self.mox.ReplayAll() + + ret_val = api.nova.server_list(self.request, all_tenants=True) + for server in ret_val: + self.assertIsInstance(server, api.Server) + + def test_usage_get(self): + novaclient = self.stub_novaclient() + novaclient.usage = self.mox.CreateMockAnything() + novaclient.usage.get(self.tenant.id, + 'start', + 'end').AndReturn(self.usages.first()) + self.mox.ReplayAll() + + ret_val = api.usage_get(self.request, self.tenant.id, 'start', 'end') + self.assertIsInstance(ret_val, api.nova.Usage) + + def test_usage_list(self): + usages = self.usages.list() + + novaclient = self.stub_novaclient() + novaclient.usage = self.mox.CreateMockAnything() + novaclient.usage.list('start', 'end', True).AndReturn(usages) + self.mox.ReplayAll() + + ret_val = api.usage_list(self.request, 'start', 'end') + for usage in ret_val: + self.assertIsInstance(usage, api.Usage) + + def test_server_get(self): + server = self.servers.first() + + novaclient = self.stub_novaclient() + novaclient.servers = self.mox.CreateMockAnything() + novaclient.servers.get(server.id).AndReturn(server) + self.mox.ReplayAll() + + ret_val = api.server_get(self.request, server.id) + self.assertIsInstance(ret_val, api.nova.Server) + + def test_server_remove_floating_ip(self): + server = api.nova.Server(self.servers.first(), self.request) + floating_ip = self.floating_ips.first() + + novaclient = self.stub_novaclient() + novaclient.servers = self.mox.CreateMockAnything() + novaclient.floating_ips = self.mox.CreateMockAnything() + novaclient.servers.get(server.id).AndReturn(server) + novaclient.floating_ips.get(floating_ip.id).AndReturn(floating_ip) + novaclient.servers.remove_floating_ip(server.id, floating_ip.id) \ + .AndReturn(server) + self.mox.ReplayAll() + + server = api.server_remove_floating_ip(self.request, + server.id, + floating_ip.id) + self.assertIsInstance(server, api.nova.Server) + + def test_server_add_floating_ip(self): + server = api.nova.Server(self.servers.first(), self.request) + floating_ip = self.floating_ips.first() + novaclient = self.stub_novaclient() + + novaclient.floating_ips = self.mox.CreateMockAnything() + novaclient.servers = self.mox.CreateMockAnything() + novaclient.servers.get(server.id).AndReturn(server) + novaclient.floating_ips.get(floating_ip.id).AndReturn(floating_ip) + novaclient.servers.add_floating_ip(server.id, floating_ip.id) \ + .AndReturn(server) + self.mox.ReplayAll() + + server = api.server_add_floating_ip(self.request, + server.id, + floating_ip.id) + self.assertIsInstance(server, api.nova.Server) diff --git a/horizon/horizon/tests/api_tests/swift.py b/horizon/horizon/tests/api_tests/swift.py deleted file mode 100644 index 000e32329..000000000 --- a/horizon/horizon/tests/api_tests/swift.py +++ /dev/null @@ -1,247 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright 2012 Nebula, Inc. -# -# 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 __future__ import absolute_import - -import cloudfiles -from django import http -from mox import IsA - -from horizon.tests.api_tests.utils import * - - -class SwiftApiTests(APITestCase): - def setUp(self): - super(SwiftApiTests, self).setUp() - self.request = http.HttpRequest() - self.request.session = dict() - self.request.session['token'] = TEST_TOKEN - - def stub_swift_api(self, count=1): - self.mox.StubOutWithMock(api.swift, 'swift_api') - swift_api = self.mox.CreateMock(cloudfiles.connection.Connection) - for i in range(count): - api.swift.swift_api(IsA(http.HttpRequest)).AndReturn(swift_api) - return swift_api - - def test_swift_get_containers(self): - containers = (TEST_RETURN, TEST_RETURN + '2') - - swift_api = self.stub_swift_api() - - swift_api.get_all_containers(limit=1001, - marker=None).AndReturn(containers) - - self.mox.ReplayAll() - - (conts, more) = api.swift_get_containers(self.request) - - self.assertEqual(len(conts), len(containers)) - self.assertFalse(more) - - for container in conts: - self.assertIsInstance(container, api.Container) - self.assertIn(container._apiresource, containers) - - def test_swift_create_container(self): - NAME = 'containerName' - - swift_api = self.stub_swift_api() - self.mox.StubOutWithMock(api.swift, 'swift_container_exists') - - api.swift.swift_container_exists(self.request, - NAME).AndReturn(False) - swift_api.create_container(NAME).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.swift_create_container(self.request, NAME) - - self.assertIsInstance(ret_val, api.Container) - self.assertEqual(ret_val._apiresource, TEST_RETURN) - - def test_swift_delete_container(self): - NAME = 'containerName' - - swift_api = self.stub_swift_api() - - swift_api.delete_container(NAME).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.swift_delete_container(self.request, NAME) - - self.assertIsNone(ret_val) - - def test_swift_get_objects(self): - NAME = 'containerName' - - swift_objects = (TEST_RETURN, TEST_RETURN + '2') - container = self.mox.CreateMock(cloudfiles.container.Container) - container.get_objects(limit=1001, - marker=None, - prefix=None).AndReturn(swift_objects) - - swift_api = self.stub_swift_api() - - swift_api.get_container(NAME).AndReturn(container) - - self.mox.ReplayAll() - - (objects, more) = api.swift_get_objects(self.request, NAME) - - self.assertEqual(len(objects), len(swift_objects)) - self.assertFalse(more) - - for swift_object in objects: - self.assertIsInstance(swift_object, api.SwiftObject) - self.assertIn(swift_object._apiresource, swift_objects) - - def test_swift_get_objects_with_prefix(self): - NAME = 'containerName' - PREFIX = 'prefacedWith' - - swift_objects = (TEST_RETURN, TEST_RETURN + '2') - container = self.mox.CreateMock(cloudfiles.container.Container) - container.get_objects(limit=1001, - marker=None, - prefix=PREFIX).AndReturn(swift_objects) - - swift_api = self.stub_swift_api() - - swift_api.get_container(NAME).AndReturn(container) - - self.mox.ReplayAll() - - (objects, more) = api.swift_get_objects(self.request, - NAME, - prefix=PREFIX) - - self.assertEqual(len(objects), len(swift_objects)) - self.assertFalse(more) - - for swift_object in objects: - self.assertIsInstance(swift_object, api.SwiftObject) - self.assertIn(swift_object._apiresource, swift_objects) - - def test_swift_upload_object(self): - CONTAINER_NAME = 'containerName' - OBJECT_NAME = 'objectName' - OBJECT_DATA = 'someData' - - swift_api = self.stub_swift_api() - container = self.mox.CreateMock(cloudfiles.container.Container) - swift_object = self.mox.CreateMock(cloudfiles.storage_object.Object) - - swift_api.get_container(CONTAINER_NAME).AndReturn(container) - container.create_object(OBJECT_NAME).AndReturn(swift_object) - swift_object.write(OBJECT_DATA).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.swift_upload_object(self.request, - CONTAINER_NAME, - OBJECT_NAME, - OBJECT_DATA) - - self.assertEqual(ret_val, swift_object) - - def test_swift_delete_object(self): - CONTAINER_NAME = 'containerName' - OBJECT_NAME = 'objectName' - - swift_api = self.stub_swift_api() - container = self.mox.CreateMock(cloudfiles.container.Container) - - swift_api.get_container(CONTAINER_NAME).AndReturn(container) - container.delete_object(OBJECT_NAME).AndReturn(TEST_RETURN) - - self.mox.ReplayAll() - - ret_val = api.swift_delete_object(self.request, - CONTAINER_NAME, - OBJECT_NAME) - - self.assertIsNone(ret_val) - - def test_swift_get_object_data(self): - CONTAINER_NAME = 'containerName' - OBJECT_NAME = 'objectName' - OBJECT_DATA = 'objectData' - - swift_api = self.stub_swift_api() - container = self.mox.CreateMock(cloudfiles.container.Container) - swift_object = self.mox.CreateMock(cloudfiles.storage_object.Object) - - swift_api.get_container(CONTAINER_NAME).AndReturn(container) - container.get_object(OBJECT_NAME).AndReturn(swift_object) - swift_object.stream().AndReturn(OBJECT_DATA) - - self.mox.ReplayAll() - - ret_val = api.swift_get_object_data(self.request, - CONTAINER_NAME, - OBJECT_NAME) - - self.assertEqual(ret_val, OBJECT_DATA) - - def test_swift_object_exists(self): - CONTAINER_NAME = 'containerName' - OBJECT_NAME = 'objectName' - - swift_api = self.stub_swift_api() - container = self.mox.CreateMock(cloudfiles.container.Container) - swift_object = self.mox.CreateMock(cloudfiles.Object) - - swift_api.get_container(CONTAINER_NAME).AndReturn(container) - container.get_object(OBJECT_NAME).AndReturn(swift_object) - - self.mox.ReplayAll() - - ret_val = api.swift_object_exists(self.request, - CONTAINER_NAME, - OBJECT_NAME) - self.assertTrue(ret_val) - - def test_swift_copy_object(self): - CONTAINER_NAME = 'containerName' - OBJECT_NAME = 'objectName' - - swift_api = self.stub_swift_api() - container = self.mox.CreateMock(cloudfiles.container.Container) - self.mox.StubOutWithMock(api.swift, 'swift_object_exists') - - swift_object = self.mox.CreateMock(cloudfiles.Object) - - swift_api.get_container(CONTAINER_NAME).AndReturn(container) - api.swift.swift_object_exists(self.request, - CONTAINER_NAME, - OBJECT_NAME).AndReturn(False) - - container.get_object(OBJECT_NAME).AndReturn(swift_object) - swift_object.copy_to(CONTAINER_NAME, OBJECT_NAME) - - self.mox.ReplayAll() - - ret_val = api.swift_copy_object(self.request, CONTAINER_NAME, - OBJECT_NAME, CONTAINER_NAME, - OBJECT_NAME) - - self.assertIsNone(ret_val) diff --git a/horizon/horizon/tests/api_tests/swift_tests.py b/horizon/horizon/tests/api_tests/swift_tests.py new file mode 100644 index 000000000..9b405844e --- /dev/null +++ b/horizon/horizon/tests/api_tests/swift_tests.py @@ -0,0 +1,170 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# +# 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 __future__ import absolute_import + +import cloudfiles + +from horizon import api +from horizon import exceptions +from horizon import test + + +class SwiftApiTests(test.APITestCase): + def test_swift_get_containers(self): + containers = self.containers.list() + swift_api = self.stub_swiftclient() + swift_api.get_all_containers(limit=1001, + marker=None).AndReturn(containers) + self.mox.ReplayAll() + + (conts, more) = api.swift_get_containers(self.request) + self.assertEqual(len(conts), len(containers)) + self.assertFalse(more) + + def test_swift_create_container(self): + container = self.containers.first() + swift_api = self.stub_swiftclient(expected_calls=2) + # Check for existence, then create + exc = cloudfiles.errors.NoSuchContainer() + swift_api.get_container(container.name).AndRaise(exc) + swift_api.create_container(container.name).AndReturn(container) + self.mox.ReplayAll() + # Verification handled by mox, no assertions needed. + api.swift_create_container(self.request, container.name) + + def test_swift_create_duplicate_container(self): + container = self.containers.first() + swift_api = self.stub_swiftclient() + swift_api.get_container(container.name).AndReturn(container) + self.mox.ReplayAll() + # Verification handled by mox, no assertions needed. + with self.assertRaises(exceptions.AlreadyExists): + api.swift_create_container(self.request, container.name) + + def test_swift_get_objects(self): + container = self.containers.first() + objects = self.objects.list() + + swift_api = self.stub_swiftclient() + swift_api.get_container(container.name).AndReturn(container) + self.mox.StubOutWithMock(container, 'get_objects') + container.get_objects(limit=1001, + marker=None, + prefix=None).AndReturn(objects) + self.mox.ReplayAll() + + (objs, more) = api.swift_get_objects(self.request, container.name) + self.assertEqual(len(objs), len(objects)) + self.assertFalse(more) + + def test_swift_upload_object(self): + container = self.containers.first() + obj = self.objects.first() + OBJECT_DATA = 'someData' + + swift_api = self.stub_swiftclient() + swift_api.get_container(container.name).AndReturn(container) + self.mox.StubOutWithMock(container, 'create_object') + container.create_object(obj.name).AndReturn(obj) + self.mox.StubOutWithMock(obj, 'write') + obj.write(OBJECT_DATA).AndReturn(obj) + self.mox.ReplayAll() + + ret_val = api.swift_upload_object(self.request, + container.name, + obj.name, + OBJECT_DATA) + self.assertEqual(ret_val, obj) + + def test_swift_delete_object(self): + container = self.containers.first() + obj = self.objects.first() + + swift_api = self.stub_swiftclient() + swift_api.get_container(container.name).AndReturn(container) + self.mox.StubOutWithMock(container, 'delete_object') + container.delete_object(obj.name).AndReturn(obj) + self.mox.ReplayAll() + + ret_val = api.swift_delete_object(self.request, + container.name, + obj.name) + + self.assertIsNone(ret_val) + + def test_swift_get_object_data(self): + container = self.containers.first() + obj = self.objects.first() + OBJECT_DATA = 'objectData' + + swift_api = self.stub_swiftclient() + swift_api.get_container(container.name).AndReturn(container) + self.mox.StubOutWithMock(container, 'get_object') + container.get_object(obj.name).AndReturn(obj) + self.mox.StubOutWithMock(obj, 'stream') + obj.stream().AndReturn(OBJECT_DATA) + self.mox.ReplayAll() + + ret_val = api.swift_get_object_data(self.request, + container.name, + obj.name) + self.assertEqual(ret_val, OBJECT_DATA) + + def test_swift_object_exists(self): + container = self.containers.first() + obj = self.objects.first() + + swift_api = self.stub_swiftclient(expected_calls=2) + self.mox.StubOutWithMock(container, 'get_object') + swift_api.get_container(container.name).AndReturn(container) + container.get_object(obj.name).AndReturn(obj) + swift_api.get_container(container.name).AndReturn(container) + exc = cloudfiles.errors.NoSuchObject() + container.get_object(obj.name).AndRaise(exc) + self.mox.ReplayAll() + + args = self.request, container.name, obj.name + self.assertTrue(api.swift_object_exists(*args)) + # Again, for a "non-existent" object + self.assertFalse(api.swift_object_exists(*args)) + + def test_swift_copy_object(self): + container = self.containers.get(name="container_one") + container_2 = self.containers.get(name="container_two") + obj = self.objects.first() + + swift_api = self.stub_swiftclient() + self.mox.StubOutWithMock(api.swift, 'swift_object_exists') + swift_api.get_container(container.name).AndReturn(container) + api.swift.swift_object_exists(self.request, + container_2.name, + obj.name).AndReturn(False) + self.mox.StubOutWithMock(container, 'get_object') + container.get_object(obj.name).AndReturn(obj) + self.mox.StubOutWithMock(obj, 'copy_to') + obj.copy_to(container_2.name, obj.name) + self.mox.ReplayAll() + # Verification handled by mox. No assertions needed. + api.swift_copy_object(self.request, + container.name, + obj.name, + container_2.name, + obj.name) diff --git a/horizon/horizon/tests/api_tests/utils.py b/horizon/horizon/tests/api_tests/utils.py deleted file mode 100644 index c037141c5..000000000 --- a/horizon/horizon/tests/api_tests/utils.py +++ /dev/null @@ -1,99 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright 2012 Nebula, Inc. -# -# 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 keystoneclient.v2_0 import client as keystone_client -from novaclient.v1_1 import client as nova_client - -from horizon import api -from horizon import test - - -TEST_CONSOLE_TYPE = 'novnc' -TEST_EMAIL = 'test@test.com' -TEST_HOSTNAME = 'hostname' -TEST_INSTANCE_ID = '2' -TEST_PASSWORD = '12345' -TEST_PORT = 8000 -TEST_RETURN = 'retValue' -TEST_TENANT_DESCRIPTION = 'tenantDescription' -TEST_TENANT_ID = '1234' -TEST_TENANT_NAME = 'foo' -TEST_TOKEN = 'aToken' -TEST_TOKEN_ID = 'userId' -TEST_URL = 'http://%s:%s/something/v1.0' % (TEST_HOSTNAME, TEST_PORT) -TEST_USERNAME = 'testUser' - - -class APIResource(api.APIResourceWrapper): - """ Simple APIResource for testing """ - _attrs = ['foo', 'bar', 'baz'] - - @staticmethod - def get_instance(innerObject=None): - if innerObject is None: - - class InnerAPIResource(object): - pass - - innerObject = InnerAPIResource() - innerObject.foo = 'foo' - innerObject.bar = 'bar' - return APIResource(innerObject) - - -class APIDict(api.APIDictWrapper): - """ Simple APIDict for testing """ - _attrs = ['foo', 'bar', 'baz'] - - @staticmethod - def get_instance(innerDict=None): - if innerDict is None: - innerDict = {'foo': 'foo', - 'bar': 'bar'} - return APIDict(innerDict) - - -class APITestCase(test.TestCase): - def setUp(self): - def fake_keystoneclient(request, username=None, password=None, - tenant_id=None, token_id=None, endpoint=None): - return self.stub_keystoneclient() - super(APITestCase, self).setUp() - self._original_keystoneclient = api.keystone.keystoneclient - self._original_novaclient = api.nova.novaclient - api.keystone.keystoneclient = fake_keystoneclient - api.nova.novaclient = lambda request: self.stub_novaclient() - - def stub_novaclient(self): - if not hasattr(self, "novaclient"): - self.mox.StubOutWithMock(nova_client, 'Client') - self.novaclient = self.mox.CreateMock(nova_client.Client) - return self.novaclient - - def stub_keystoneclient(self): - if not hasattr(self, "keystoneclient"): - self.mox.StubOutWithMock(keystone_client, 'Client') - self.keystoneclient = self.mox.CreateMock(keystone_client.Client) - return self.keystoneclient - - def tearDown(self): - super(APITestCase, self).tearDown() - api.nova.novaclient = self._original_novaclient - api.keystone.keystoneclient = self._original_keystoneclient diff --git a/horizon/horizon/tests/auth_tests.py b/horizon/horizon/tests/auth_tests.py index 8dd2bdfa5..5cf46c663 100644 --- a/horizon/horizon/tests/auth_tests.py +++ b/horizon/horizon/tests/auth_tests.py @@ -19,9 +19,7 @@ # under the License. from django import http -from django.contrib import messages from django.core.urlresolvers import reverse -from keystoneclient.v2_0 import tenants as keystone_tenants from keystoneclient import exceptions as keystone_exceptions from mox import IsA @@ -33,59 +31,41 @@ SYSPANEL_INDEX_URL = reverse('horizon:syspanel:overview:index') DASH_INDEX_URL = reverse('horizon:nova:overview:index') -class AuthViewTests(test.BaseViewTests): +class AuthViewTests(test.TestCase): def setUp(self): super(AuthViewTests, self).setUp() self.setActiveUser() - self.PASSWORD = 'secret' - self.tenant = keystone_tenants.Tenant(keystone_tenants.TenantManager, - {'id': '6', - 'name': 'FAKENAME'}) - self.tenants = [self.tenant] def test_login_index(self): res = self.client.get(reverse('horizon:auth_login')) self.assertTemplateUsed(res, 'horizon/auth/login.html') def test_login_user_logged_in(self): - self.setActiveUser(self.TEST_TOKEN, self.TEST_USER, self.TEST_TENANT, - False, self.TEST_SERVICE_CATALOG) - + self.setActiveUser(self.tokens.first().id, + self.user.name, + self.tenant.id, + False, + self.service_catalog) # Hitting the login URL directly should always give you a login page. res = self.client.get(reverse('horizon:auth_login')) self.assertTemplateUsed(res, 'horizon/auth/login.html') def test_login_no_tenants(self): - - TOKEN_ID = 1 - - form_data = {'method': 'Login', - 'region': 'http://localhost:5000/v2.0', - 'password': self.PASSWORD, - 'username': self.TEST_USER} + aToken = self.tokens.first() self.mox.StubOutWithMock(api, 'token_create') - - class FakeToken(object): - id = TOKEN_ID, - user = {'roles': [{'name': 'fake'}]}, - serviceCatalog = {} - aToken = api.Token(FakeToken()) - - api.token_create(IsA(http.HttpRequest), "", self.TEST_USER, - self.PASSWORD).AndReturn(aToken) - self.mox.StubOutWithMock(api, 'tenant_list_for_token') + api.token_create(IsA(http.HttpRequest), "", self.user.name, + self.user.password).AndReturn(aToken) api.tenant_list_for_token(IsA(http.HttpRequest), aToken.id).\ AndReturn([]) - self.mox.StubOutWithMock(messages, 'error') - messages.error(IsA(http.HttpRequest), - IsA(unicode), - extra_tags=IsA(str)) - self.mox.ReplayAll() + form_data = {'method': 'Login', + 'region': 'http://localhost:5000/v2.0', + 'password': self.user.password, + 'username': self.user.name} res = self.client.post(reverse('horizon:auth_login'), form_data) self.assertTemplateUsed(res, 'horizon/auth/login.html') @@ -93,28 +73,20 @@ class AuthViewTests(test.BaseViewTests): def test_login(self): form_data = {'method': 'Login', 'region': 'http://localhost:5000/v2.0', - 'password': self.PASSWORD, - 'username': self.TEST_USER} + 'password': self.user.password, + 'username': self.user.name} self.mox.StubOutWithMock(api, 'token_create') self.mox.StubOutWithMock(api, 'tenant_list_for_token') self.mox.StubOutWithMock(api, 'token_create_scoped') - class FakeToken(object): - id = 1, - user = {"id": "1", - "roles": [{"id": "1", "name": "fake"}], "name": "user"} - serviceCatalog = {} - tenant = None + aToken = self.tokens.unscoped_token + bToken = self.tokens.scoped_token - aToken = api.Token(FakeToken()) - bToken = aToken - bToken.tenant = {'id': self.tenant.id, 'name': self.tenant.name} - - api.token_create(IsA(http.HttpRequest), "", self.TEST_USER, - self.PASSWORD).AndReturn(aToken) + api.token_create(IsA(http.HttpRequest), "", self.user.name, + self.user.password).AndReturn(aToken) api.tenant_list_for_token(IsA(http.HttpRequest), - aToken.id).AndReturn(self.tenants) + aToken.id).AndReturn(self.tenants.list()) api.token_create_scoped(IsA(http.HttpRequest), self.tenant.id, aToken.id).AndReturn(bToken) @@ -127,15 +99,15 @@ class AuthViewTests(test.BaseViewTests): def test_login_invalid_credentials(self): self.mox.StubOutWithMock(api, 'token_create') unauthorized = keystone_exceptions.Unauthorized("Invalid") - api.token_create(IsA(http.HttpRequest), "", self.TEST_USER, - self.PASSWORD).AndRaise(unauthorized) + api.token_create(IsA(http.HttpRequest), "", self.user.name, + self.user.password).AndRaise(unauthorized) self.mox.ReplayAll() form_data = {'method': 'Login', 'region': 'http://localhost:5000/v2.0', - 'password': self.PASSWORD, - 'username': self.TEST_USER} + 'password': self.user.password, + 'username': self.user.name} res = self.client.post(reverse('horizon:auth_login'), form_data, follow=True) @@ -147,69 +119,62 @@ class AuthViewTests(test.BaseViewTests): ex = keystone_exceptions.BadRequest('Cannot talk to keystone') api.token_create(IsA(http.HttpRequest), "", - self.TEST_USER, - self.PASSWORD).AndRaise(ex) + self.user.name, + self.user.password).AndRaise(ex) self.mox.ReplayAll() form_data = {'method': 'Login', 'region': 'http://localhost:5000/v2.0', - 'password': self.PASSWORD, - 'username': self.TEST_USER} + 'password': self.user.password, + 'username': self.user.name} res = self.client.post(reverse('horizon:auth_login'), form_data) self.assertTemplateUsed(res, 'horizon/auth/login.html') def test_switch_tenants_index(self): res = self.client.get(reverse('horizon:auth_switch', - args=[self.TEST_TENANT])) + args=[self.tenant.id])) self.assertRedirects(res, reverse("horizon:auth_login")) def test_switch_tenants(self): - NEW_TENANT_ID = '6' - NEW_TENANT_NAME = 'FAKENAME' - TOKEN_ID = 1 - tenants = self.TEST_CONTEXT['authorized_tenants'] + tenants = self.tenants.list() - aTenant = self.mox.CreateMock(api.Token) - aTenant.id = NEW_TENANT_ID - aTenant.name = NEW_TENANT_NAME + tenant = self.tenants.first() + token = self.tokens.unscoped_token + scoped_token = self.tokens.scoped_token + switch_to = scoped_token.tenant['id'] + user = self.users.first() - aToken = self.mox.CreateMock(api.Token) - aToken.id = TOKEN_ID - aToken.user = {'id': self.TEST_USER_ID, - 'name': self.TEST_USER, 'roles': [{'name': 'fake'}]} - aToken.serviceCatalog = {} - aToken.tenant = {'id': aTenant.id, 'name': aTenant.name} - - self.setActiveUser(id=self.TEST_USER_ID, - token=self.TEST_TOKEN, - username=self.TEST_USER, - tenant_id=self.TEST_TENANT, - service_catalog=self.TEST_SERVICE_CATALOG, + self.setActiveUser(id=user.id, + token=token.id, + username=user.name, + tenant_id=tenant.id, + service_catalog=self.service_catalog, authorized_tenants=tenants) self.mox.StubOutWithMock(api, 'token_create') self.mox.StubOutWithMock(api, 'tenant_list_for_token') - api.token_create(IsA(http.HttpRequest), NEW_TENANT_ID, self.TEST_USER, - self.PASSWORD).AndReturn(aToken) - api.tenant_list_for_token(IsA(http.HttpRequest), aToken.id) \ - .AndReturn([aTenant]) - + api.token_create(IsA(http.HttpRequest), + switch_to, + user.name, + user.password).AndReturn(scoped_token) + api.tenant_list_for_token(IsA(http.HttpRequest), + token.id).AndReturn(tenants) self.mox.ReplayAll() form_data = {'method': 'LoginWithTenant', 'region': 'http://localhost:5000/v2.0', - 'password': self.PASSWORD, - 'tenant': NEW_TENANT_ID, - 'username': self.TEST_USER} - res = self.client.post(reverse('horizon:auth_switch', - args=[NEW_TENANT_ID]), form_data) - + 'username': user.name, + 'password': user.password, + 'tenant': switch_to} + switch_url = reverse('horizon:auth_switch', args=[switch_to]) + res = self.client.post(switch_url, form_data) self.assertRedirectsNoFollow(res, DASH_INDEX_URL) - self.assertEqual(self.client.session['tenant'], NEW_TENANT_NAME) + self.assertEqual(self.client.session['tenant'], + scoped_token.tenant['name']) def test_logout(self): KEY = 'arbitraryKeyString' diff --git a/horizon/horizon/tests/base_tests.py b/horizon/horizon/tests/base_tests.py index 852b3d339..b35b02b41 100644 --- a/horizon/horizon/tests/base_tests.py +++ b/horizon/horizon/tests/base_tests.py @@ -191,8 +191,6 @@ class HorizonTests(BaseHorizonTests): syspanel = horizon.get_dashboard("syspanel") self.assertFalse(hasattr(syspanel, "evil")) - -class HorizonBaseViewTests(BaseHorizonTests, test.BaseViewTests): def test_public(self): users.get_user_from_request = self._real_get_user_from_request settings = horizon.get_dashboard("settings") @@ -220,10 +218,10 @@ class HorizonBaseViewTests(BaseHorizonTests, test.BaseViewTests): # should get a 404. new_catalog = [service for service in self.request.user.service_catalog if service['type'] != MyPanel.services[0]] - tenants = self.TEST_CONTEXT['authorized_tenants'] - self.setActiveUser(token=self.TEST_TOKEN, - username=self.TEST_USER, - tenant_id=self.TEST_TENANT, + tenants = self.context['authorized_tenants'] + self.setActiveUser(token=self.token.id, + username=self.user.name, + tenant_id=self.tenant.id, service_catalog=new_catalog, authorized_tenants=tenants) resp = self.client.get(panel.get_absolute_url()) diff --git a/horizon/horizon/tests/context_processor_tests.py b/horizon/horizon/tests/context_processor_tests.py index 817ae20ed..30102282b 100644 --- a/horizon/horizon/tests/context_processor_tests.py +++ b/horizon/horizon/tests/context_processor_tests.py @@ -38,11 +38,11 @@ class ContextProcessorTests(test.TestCase): self.request.user.service_catalog = self._prev_catalog def test_authorized_tenants(self): - tenant_list = self.TEST_CONTEXT['authorized_tenants'] + tenant_list = self.context['authorized_tenants'] self.request.user.authorized_tenants = None # Reset from setUp self.mox.StubOutWithMock(api, 'tenant_list_for_token') api.tenant_list_for_token(IsA(http.HttpRequest), - self.TEST_TOKEN, + self.token.id, endpoint_type='internalURL') \ .AndReturn(tenant_list) self.mox.ReplayAll() @@ -51,4 +51,4 @@ class ContextProcessorTests(test.TestCase): context = context_processors.horizon(self.request) self.assertEqual(len(context['authorized_tenants']), 1) tenant = context['authorized_tenants'].pop() - self.assertEqual(tenant['id'], self.TEST_TENANT) + self.assertEqual(tenant.id, self.tenant.id) diff --git a/horizon/horizon/tests/templatetag_tests.py b/horizon/horizon/tests/templatetag_tests.py index 0156e5c15..10548916c 100644 --- a/horizon/horizon/tests/templatetag_tests.py +++ b/horizon/horizon/tests/templatetag_tests.py @@ -20,7 +20,6 @@ import re -from django import dispatch, http, template from django.utils.text import normalize_newlines from horizon import test @@ -34,5 +33,4 @@ def single_line(text): class TemplateTagTests(test.TestCase): - def setUp(self): - super(TemplateTagTests, self).setUp() + pass diff --git a/horizon/horizon/tests/test_data/__init__.py b/horizon/horizon/tests/test_data/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/horizon/horizon/tests/test_data/glance_data.py b/horizon/horizon/tests/test_data/glance_data.py new file mode 100644 index 000000000..82554ec7e --- /dev/null +++ b/horizon/horizon/tests/test_data/glance_data.py @@ -0,0 +1,46 @@ +# Copyright 2012 Nebula, Inc. +# +# 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 horizon.api import glance +from .utils import TestDataContainer + + +def data(TEST): + TEST.images = TestDataContainer() + TEST.snapshots = TestDataContainer() + + # Snapshots + snapshot_dict = {'name': u'snapshot', + 'container_format': u'ami', + 'id': 3} + snapshot = glance.Image(snapshot_dict) + snapshot_properties_dict = {'image_type': u'snapshot'} + snapshot.properties = glance.ImageProperties(snapshot_properties_dict) + TEST.snapshots.add(snapshot) + + # Images + image_properties_dict = {'image_type': u'image'} + image_dict = {'id': '1', + 'name': 'public_image', + 'container_format': 'novaImage'} + public_image = glance.Image(image_dict) + public_image.properties = glance.ImageProperties(image_properties_dict) + + image_dict = {'id': '2', + 'name': 'private_image', + 'container_format': 'aki'} + private_image = glance.Image(image_dict) + private_image.properties = glance.ImageProperties(image_properties_dict) + + TEST.images.add(public_image, private_image) diff --git a/horizon/horizon/tests/test_data/keystone_data.py b/horizon/horizon/tests/test_data/keystone_data.py new file mode 100644 index 000000000..958ab45a3 --- /dev/null +++ b/horizon/horizon/tests/test_data/keystone_data.py @@ -0,0 +1,118 @@ +# Copyright 2012 Nebula, Inc. +# +# 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 django.conf import settings +from keystoneclient.v2_0 import users, tenants, tokens, roles + +from .utils import TestDataContainer + + +# Dummy service catalog with all service. +# All endpoint URLs should point to example.com. +# Try to keep them as accurate to real data as possible (ports, URIs, etc.) +SERVICE_CATALOG = [ + {"type": "compute", + "name": "nova", + "endpoints": [ + {"region": "RegionOne", + "adminURL": "http://admin.nova.example.com:8774/v1.0", + "internalURL": "http://internal.nova.example.com:8774/v1.0", + "publicURL": "http://public.nova.example.com:8774/v1.0/"}]}, + {"type": "image", + "name": "glance", + "endpoints": [ + {"region": "RegionOne", + "adminURL": "http://admin.glance.example.com:9292/v1", + "internalURL": "http://internal.glance.example.com:9292/v1", + "publicURL": "http://public.glance.example.com:9292/v1"}]}, + {"type": "identity", + "name": "keystone", + "endpoints": [ + {"region": "RegionOne", + "adminURL": "http://admin.keystone.example.com:35357/v2.0", + "internalURL": "http://internal.keystone.example.com:5000/v2.0", + "publicURL": "http://public.keystone.example.com:5000/v2.0"}]}, + {"type": "object-store", + "name": "swift", + "endpoints": [ + {"region": "RegionOne", + "adminURL": "http://admin.swift.example.com:8080/", + "internalURL": "http://internal.swift.example.com:8080/", + "publicURL": "http://public.swift.example.com:8080/"}]}, + {"type": "network", + "name": "quantum", + "endpoints": [ + {"region": "RegionOne", + "adminURL": "http://admin.quantum.example.com:9696/", + "internalURL": "http://internal.quantum.example.com:9696/", + "publicURL": "http://public.quantum.example.com:9696/"}]}, +] + + +def data(TEST): + TEST.service_catalog = SERVICE_CATALOG + TEST.tokens = TestDataContainer() + TEST.users = TestDataContainer() + TEST.tenants = TestDataContainer() + TEST.roles = TestDataContainer() + + admin_role_dict = {'id': '1', + 'name': 'admin'} + admin_role = roles.Role(roles.RoleManager, admin_role_dict) + member_role_dict = {'id': "2", + 'name': settings.OPENSTACK_KEYSTONE_DEFAULT_ROLE} + member_role = roles.Role(roles.RoleManager, member_role_dict) + TEST.roles.add(member_role, admin_role) + TEST.roles.admin = admin_role + TEST.roles.member = member_role + + user_dict = {'id': "1", + 'name': 'test_user', + 'email': 'test@example.com', + 'password': 'password'} + user = users.User(users.UserManager, user_dict) + user_dict.update({'id': "2", + 'name': 'user_two', + 'email': 'two@example.com'}) + user2 = users.User(users.UserManager, user_dict) + TEST.users.add(user, user2) + TEST.user = user # Your "current" user + + tenant_dict = {'id': "1", + 'name': 'test_tenant', + 'description': "a test tenant."} + tenant = tenants.Tenant(tenants.TenantManager, tenant_dict) + TEST.tenants.add(tenant) + TEST.tenant = tenant # Your "current" tenant + + scoped_token = tokens.Token(tokens.TokenManager, + dict(token={"id": "test_token_id", + "expires": "#FIXME", + "tenant": tenant_dict, + "tenants": [tenant_dict]}, + user={"id": "test_user_id", + "name": "test_user", + "roles": [member_role_dict]}, + serviceCatalog=TEST.service_catalog)) + unscoped_token = tokens.Token(tokens.TokenManager, + dict(token={"id": "test_token_id", + "expires": "#FIXME"}, + user={"id": "test_user_id", + "name": "test_user", + "roles": [member_role_dict]}, + serviceCatalog=TEST.service_catalog)) + TEST.tokens.add(scoped_token, unscoped_token) + TEST.token = scoped_token # your "current" token. + TEST.tokens.scoped_token = scoped_token + TEST.tokens.unscoped_token = unscoped_token diff --git a/horizon/horizon/tests/test_data/nova_data.py b/horizon/horizon/tests/test_data/nova_data.py new file mode 100644 index 000000000..f055351e1 --- /dev/null +++ b/horizon/horizon/tests/test_data/nova_data.py @@ -0,0 +1,262 @@ +# Copyright 2012 Nebula, Inc. +# +# 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 json + +from novaclient.v1_1 import (flavors, keypairs, servers, volumes, quotas, + floating_ips, usage, + volume_snapshots as vol_snaps, + security_group_rules as rules, + security_groups as sec_groups) + +from .utils import TestDataContainer + + +SERVER_DATA = """ +{ + "server": { + "OS-EXT-STS:task_state": null, + "addresses": { + "private": [ + { + "version": 4, + "addr": "10.0.0.1" + } + ] + }, + "links": [ + { + "href": "%(host)s/v1.1/%(tenant_id)s/servers/%(server_id)s", + "rel": "self" + }, + { + "href": "%(host)s/%(tenant_id)s/servers/%(server_id)s", + "rel": "bookmark" + } + ], + "image": { + "id": "%(image_id)s", + "links": [ + { + "href": "%(host)s/%(tenant_id)s/images/%(image_id)s", + "rel": "bookmark" + } + ] + }, + "OS-EXT-STS:vm_state": "active", + "flavor": { + "id": "%(flavor_id)s", + "links": [ + { + "href": "%(host)s/%(tenant_id)s/flavors/%(flavor_id)s", + "rel": "bookmark" + } + ] + }, + "id": "%(server_id)s", + "user_id": "%(user_id)s", + "OS-DCF:diskConfig": "MANUAL", + "accessIPv4": "", + "accessIPv6": "", + "progress": null, + "OS-EXT-STS:power_state": 1, + "config_drive": "", + "status": "%(status)s", + "updated": "2012-02-28T19:51:27Z", + "hostId": "c461ea283faa0ab5d777073c93b126c68139e4e45934d4fc37e403c2", + "key_name": "%(key_name)s", + "name": "%(name)s", + "created": "2012-02-28T19:51:17Z", + "tenant_id": "%(tenant_id)s", + "metadata": {} + } +} +""" + + +USAGE_DATA = """ +{ + "total_memory_mb_usage": 64246.89777777778, + "total_vcpus_usage": 125.48222222222223, + "total_hours": 125.48222222222223, + "total_local_gb_usage": 0, + "tenant_id": "%(tenant_id)s", + "stop": "2012-01-31 23:59:59", + "start": "2012-01-01 00:00:00", + "server_usages": [ + { + "memory_mb": %(flavor_ram)s, + "uptime": 442321, + "started_at": "2012-01-26 20:38:21", + "ended_at": null, + "name": "%(instance_name)s", + "tenant_id": "%(tenant_id)s", + "state": "active", + "hours": 122.87361111111112, + "vcpus": %(flavor_vcpus)s, + "flavor": "%(flavor_name)s", + "local_gb": %(flavor_disk)s + }, + { + "memory_mb": %(flavor_ram)s, + "uptime": 9367, + "started_at": "2012-01-31 20:54:15", + "ended_at": null, + "name": "%(instance_name)s", + "tenant_id": "%(tenant_id)s", + "state": "active", + "hours": 2.608611111111111, + "vcpus": %(flavor_vcpus)s, + "flavor": "%(flavor_name)s", + "local_gb": %(flavor_disk)s + } + ] +} +""" + + +def data(TEST): + TEST.servers = TestDataContainer() + TEST.flavors = TestDataContainer() + TEST.keypairs = TestDataContainer() + TEST.security_groups = TestDataContainer() + TEST.security_group_rules = TestDataContainer() + TEST.volumes = TestDataContainer() + TEST.quotas = TestDataContainer() + TEST.floating_ips = TestDataContainer() + TEST.usages = TestDataContainer() + TEST.volume_snapshots = TestDataContainer() + + # Volumes + volume = volumes.Volume(volumes.VolumeManager, + dict(id="1", + name='test_volume', + status='available', + size=40, + displayName='', + attachments={})) + TEST.volumes.add(volume) + + # Flavors + flavor_1 = flavors.Flavor(flavors.FlavorManager, + dict(id="1", + name='m1.tiny', + vcpus=1, + disk=0, + ram=512)) + flavor_2 = flavors.Flavor(flavors.FlavorManager, + dict(id="2", + name='m1.massive', + vcpus=1000, + disk=1024, + ram=10000)) + TEST.flavors.add(flavor_1, flavor_2) + + # Keypairs + keypair = keypairs.Keypair(keypairs.KeypairManager, + dict(name='keyName')) + TEST.keypairs.add(keypair) + + # Security Groups + sec_group_1 = sec_groups.SecurityGroup(sec_groups.SecurityGroupManager, + {"rules": [], + "tenant_id": TEST.tenant.id, + "id": 1, + "name": u"default", + "description": u"default"}) + sec_group_2 = sec_groups.SecurityGroup(sec_groups.SecurityGroupManager, + {"rules": [], + "tenant_id": TEST.tenant.id, + "id": 2, + "name": u"other_group", + "description": u"Not default."}) + + rule = {'id': 1, + 'ip_protocol': u"tcp", + 'from_port': u"80", + 'to_port': u"80", + 'parent_group_id': 1, + 'ip_range': {'cidr': u"0.0.0.0/32"}} + rule_obj = rules.SecurityGroupRule(rules.SecurityGroupRuleManager, rule) + TEST.security_group_rules.add(rule_obj) + + sec_group_1.rules = [rule_obj] + sec_group_2.rules = [rule_obj] + TEST.security_groups.add(sec_group_1, sec_group_2) + + # Security Group Rules + + # Quota Sets + quota_data = dict(metadata_items='1', + injected_file_content_bytes='1', + volumes='1', + gigabytes='1', + ram=1, + floating_ips='1', + instances='1', + injected_files='1', + cores='1') + quota = quotas.QuotaSet(quotas.QuotaSetManager, quota_data) + TEST.quotas.add(quota) + + # Servers + vals = {"host": "http://nova.example.com:8774", + "name": "server_1", + "status": "ACTIVE", + "tenant_id": TEST.tenants.first().id, + "user_id": TEST.user.id, + "server_id": "1", + "flavor_id": flavor_1.id, + "image_id": TEST.images.first().id, + "key_name": keypair.name} + server_1 = servers.Server(servers.ServerManager, + json.loads(SERVER_DATA % vals)['server']) + vals.update({"name": "server_2", + "status": "BUILD", + "server_id": "2"}) + server_2 = servers.Server(servers.ServerManager, + json.loads(SERVER_DATA % vals)['server']) + TEST.servers.add(server_1, server_2) + + # VNC Console Data + console = {u'console': {u'url': u'http://example.com:6080/vnc_auto.html', + u'type': u'novnc'}} + TEST.servers.console_data = console + # Floating IPs + fip_1 = floating_ips.FloatingIP(floating_ips.FloatingIPManager, + {'id': 1, + 'fixed_ip': '10.0.0.4', + 'instance_id': server_1.id, + 'ip': '58.58.58.58'}) + TEST.floating_ips.add(fip_1) + + # Usage + usage_vals = {"tenant_id": TEST.tenant.id, + "instance_name": server_1.name, + "flavor_name": flavor_1.name, + "flavor_vcpus": flavor_1.vcpus, + "flavor_disk": flavor_1.disk, + "flavor_ram": flavor_1.ram} + usage_obj = usage.Usage(usage.UsageManager, + json.loads(USAGE_DATA % usage_vals)) + TEST.usages.add(usage_obj) + + volume_snapshot = vol_snaps.Snapshot(vol_snaps.SnapshotManager, + {'id': 2, + 'displayName': 'test snapshot', + 'displayDescription': 'vol snap!', + 'size': 40, + 'status': 'available', + 'volumeId': 1}) + TEST.volume_snapshots.add(volume_snapshot) diff --git a/horizon/horizon/tests/test_data/quantum_data.py b/horizon/horizon/tests/test_data/quantum_data.py new file mode 100644 index 000000000..0471c08d4 --- /dev/null +++ b/horizon/horizon/tests/test_data/quantum_data.py @@ -0,0 +1,22 @@ +# Copyright 2012 Nebula, Inc. +# +# 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 .utils import TestDataContainer + + +def data(TEST): + TEST.networks = TestDataContainer() + TEST.ports = TestDataContainer() + # TODO(gabriel): Move quantum test data into this module after it + # has been refactored with object wrappers (a la Glance). diff --git a/horizon/horizon/tests/test_data/swift_data.py b/horizon/horizon/tests/test_data/swift_data.py new file mode 100644 index 000000000..aa077d592 --- /dev/null +++ b/horizon/horizon/tests/test_data/swift_data.py @@ -0,0 +1,53 @@ +# Copyright 2012 Nebula, Inc. +# +# 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 new + +from cloudfiles import container, storage_object + +from .utils import TestDataContainer + + +def data(TEST): + TEST.containers = TestDataContainer() + TEST.objects = TestDataContainer() + + class FakeConnection(object): + def __init__(self): + self.cdn_enabled = False + + conn = FakeConnection() + + container_1 = container.Container(conn, name="container_one") + container_2 = container.Container(conn, name="container_two") + TEST.containers.add(container_1, container_2) + + object_dict = {"name": "test_object", + "content_type": "text/plain", + "bytes": 128, + "last_modified": None, + "hash": "object_hash"} + obj_dicts = [object_dict] + for obj_dict in obj_dicts: + swift_object = storage_object.Object(container_1, + object_record=obj_dict) + TEST.objects.add(swift_object) + + # Override the list method to return the type of list cloudfiles does. + def get_object_result_list(self): + return storage_object.ObjectResults(container_1, + objects=obj_dicts) + + list_method = new.instancemethod(get_object_result_list, TEST.objects) + TEST.objects.list = list_method diff --git a/horizon/horizon/tests/test_data/utils.py b/horizon/horizon/tests/test_data/utils.py new file mode 100644 index 000000000..f7720a7bd --- /dev/null +++ b/horizon/horizon/tests/test_data/utils.py @@ -0,0 +1,122 @@ +# Copyright 2012 Nebula, Inc. +# +# 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. + + +def load_test_data(load_onto=None): + from . import glance_data + from . import keystone_data + from . import nova_data + from . import swift_data + + # The order of these loaders matters, some depend on others. + loaders = (keystone_data.data, + glance_data.data, + nova_data.data, + swift_data.data) + if load_onto: + for data_func in loaders: + data_func(load_onto) + return load_onto + else: + return TestData(*loaders) + + +class TestData(object): + """ + Holder object for test data. Any functions passed to the init method + will be called with the ``TestData`` object as their only argument. They + can then load data onto the object as desired. + + The idea is to use the instantiated object like this:: + + >>> import glance_data + >>> TEST = TestData(glance_data.data) + >>> TEST.images.list() + ... [, ] + >>> TEST.images.first() + ... + + You can load as little or as much data as you like as long as the loaders + don't conflict with each other. + + See the :class:`~horizon.tests.test_data.utils.TestDataContainer` class + for a list of available methods. + """ + def __init__(self, *args): + for data_func in args: + data_func(self) + + +class TestDataContainer(object): + """ A container for test data objects. + + The behavior of this class is meant to mimic a "manager" class, which + has convenient shortcuts for common actions like "list", "filter", "get", + and "add". + """ + def __init__(self): + self._objects = [] + + def add(self, *args): + """ Add a new object to this container. + + Generally this method should only be used during data loading, since + adding data during a test can affect the results of other tests. + """ + for obj in args: + if obj not in self._objects: + self._objects.append(obj) + + def list(self): + """ Returns a list of all objects in this container. """ + return self._objects + + def filter(self, filtered=None, **kwargs): + """ + Returns objects in this container whose attributes match the given + keyword arguments. + """ + if filtered is None: + filtered = self._objects + try: + key, value = kwargs.popitem() + except KeyError: + # We're out of filters, return + return filtered + + def get_match(obj): + return hasattr(obj, key) and getattr(obj, key) == value + + return self.filter(filtered=filter(get_match, filtered), **kwargs) + + def get(self, **kwargs): + """ + Returns the single object in this container whose attributes match + the given keyword arguments. An error will be raised if the arguments + provided don't return exactly one match. + """ + matches = self.filter(**kwargs) + if not matches: + raise Exception("No matches found.") + elif len(matches) > 1: + raise Exception("Multiple matches found.") + else: + return matches.pop() + + def first(self): + """ Returns the first object from this container. """ + return self._objects[0] + + def count(self): + return len(self._objects) diff --git a/horizon/horizon/tests/testsettings.py b/horizon/horizon/tests/testsettings.py index 96496920a..01761209d 100644 --- a/horizon/horizon/tests/testsettings.py +++ b/horizon/horizon/tests/testsettings.py @@ -60,6 +60,8 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.messages.context_processors.messages', 'horizon.context_processors.horizon') +MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' + ROOT_URLCONF = 'horizon.tests.testurls' TEMPLATE_DIRS = (os.path.join(ROOT_PATH, 'tests', 'templates')) SITE_ID = 1 diff --git a/horizon/horizon/tests/views.py b/horizon/horizon/tests/views.py index 91e32204d..0191a4717 100644 --- a/horizon/horizon/tests/views.py +++ b/horizon/horizon/tests/views.py @@ -27,5 +27,4 @@ def fakeView(request): 'This is a fake httpresponse from a fake view for testing ' ' purposes only' '

') - return resp diff --git a/horizon/horizon/usage/base.py b/horizon/horizon/usage/base.py index c569e3686..5299a0e2d 100644 --- a/horizon/horizon/usage/base.py +++ b/horizon/horizon/usage/base.py @@ -76,9 +76,6 @@ class BaseUsage(object): def get_usage_list(self, start, end): raise NotImplementedError("You must define a get_usage method.") - def get_summary(self): - raise NotImplementedError("You must define a get_summary method.") - def summarize(self, start, end): if start <= end <= time.today(): # Convert to datetime.datetime just for API call. diff --git a/horizon/horizon/users.py b/horizon/horizon/users.py index 30a14c30c..c39f63cb0 100644 --- a/horizon/horizon/users.py +++ b/horizon/horizon/users.py @@ -23,6 +23,8 @@ Classes and methods related to user handling in Horizon. import logging +from django.utils.translation import ugettext as _ + from horizon import exceptions @@ -135,7 +137,8 @@ class User(object): return False def get_and_delete_messages(self): - """ Placeholder function for parity with + """ + Placeholder function for parity with ``django.contrib.auth.models.User``. """ return []