Merge lp:~termie/nova/eventlet_objectstore into lp:~hudson-openstack/nova/trunk

Proposed by termie
Status: Superseded
Proposed branch: lp:~termie/nova/eventlet_objectstore
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 912 lines (+495/-328)
6 files modified
bin/nova-objectstore (+9/-6)
nova/objectstore/bucket.py (+1/-2)
nova/objectstore/s3server.py (+303/-0)
nova/test.py (+32/-5)
nova/tests/objectstore_unittest.py (+0/-315)
nova/tests/test_objectstore.py (+150/-0)
To merge this branch: bzr merge lp:~termie/nova/eventlet_objectstore
Reviewer Review Type Date Requested Status
Soren Hansen (community) Needs Information
Vish Ishaya (community) Needs Fixing
Review via email: mp+52152@code.launchpad.net

This proposal has been superseded by a proposal from 2011-03-07.

Description of the change

Ports the Tornado version of an S3 server to eventlet and wsgi, first step in deprecating the twistd-based objectstore.

This is a trivial implementation, never meant for production, it exists to provide an s3-look-alike objectstore for use when developing/testing things related to the amazon APIs (eucatools, etc), any production deployment would be expected to use Swift + an S3 interface.

In later patches I expect to be able to remove the old objectstore code entirely.

To post a comment you must log in.
lp:~termie/nova/eventlet_objectstore updated
760. By Rick Harris

Use %s for instance-delete logging in case instance_id comes through as a string.

Revision history for this message
Vish Ishaya (vishvananda) wrote :

Code looks awesome. Unfortunately this breaks register_image until my patch here is merged:
lp:~vishvananda/nova/kill-objectstore is merged. To avoid having trunk temporarily broken, I suggest that you merge my patch and repropose the merge with that one as a prereq.

review: Needs Fixing
lp:~termie/nova/eventlet_objectstore updated
761. By Todd Willey

Fix renaming of instance fields using update_instance api method.

Revision history for this message
Soren Hansen (soren) wrote :

In nova/test.py, you import eventlet.greenpool, yet you don't use it. Is that a mistake or does importing the greenpool module make eventlet do something differently behind the scenes? If so, it would probably be good to add a comment to that effect.

I wonder what the rationale behind not removing the twisted based handler is?

You're removing a bunch of tests for the objectstore (the ones that do not directly pertain to the S3 API). Why is that?

review: Needs Information
lp:~termie/nova/eventlet_objectstore updated
762. By Matt Dietz

Implementation for XenServer migrations. There are several places for optimization but I based the current implementation on the chance scheduler just to be safe. Beyond that, a few features are missing, such as ensuring the IP address is transferred along with the migrated instance. This will be added in a subsequent patch. Finally, everything is implemented through the Openstack API resize hooks, but actual resizing of the instance RAM and hard drive space is not yet implemented.

763. By Kevin L. Mitchell

Addresses bugs 704985 and 705453 by:

1) Adding new nova-api flag --paste_config, the value of which is used in place of the hard-coded "nova-api.conf"

2) Defaulting the new --paste_config to "api-paste.ini"

764. By Kevin L. Mitchell

Inhibit inclusion of stack traces in the logs UNLESS --verbose has been specified. This should help keep the logs compact, helping admins find the messages they're interested in (e.g., "Can't connect to MySQL server on '127.0.0.1' (111)") without having to sort through the stack traces, while still allowing developers to see those traces at will.

765. By Masanori Itoh

This fix is an updated version of Todd's lp720157. Adds SignatureVersion checking for Amazon EC2 API requests, and resolves bug #720157.

766. By Trey Morris

refactored up nova/virt/xenapi/vmops _get_vm_opaque_ref()
no longer inspects the param to check to see if it is an opaque ref
works better for unittests

767. By justinsb

Fix the bug where fakerabbit is doing a sort of prefix matching on the AMQP routing key

768. By Rick Harris

Use disk_format and container_format in place of image type.

769. By Matt Dietz

Fixes lp730960 - mangled instance creation in virt drivers due to improper merge conflict resolution

770. By justinsb

Refactoring nova-api to be a service, so that we can reuse it in unit tests

771. By Christian Berendt

replaced ConnectionFailed with Exception in tools/euca-get-ajax-console
was not working for me with euca2tools 1.2 (version 2007-10-10, release 31337)

772. By Naveed Massjouni

Fixes bug #729400. Invalid values for offset and limit params in http requests now return a 400 response with a useful message in the body. Also added and updated tests.

773. By Soren Hansen

Add a decorator that lets you synchronise actions across multiple binaries. Like, say, ensuring that only one worker manipulates iptables at a time.

774. By Dan Prince

Update the create server call in the Openstack API so that it generates an 'adminPass' and calls set_admin_password in the compute API. This gets us closer to parity with the Cloud Servers v1.0 spec.

775. By Ricardo Carrillo Cruz

Hi guys

This branch fixes lp722982 (ability to delete networks with nova-manage) and lp715427 (nova-manage does not check if a network exists before creating it) .

Regards

776. By Erica Windisch

Fixes bug 726359. Passes unit tests.

Changes parameters of utils.execute to utils.execute(*cmd, **kwargs). The kwargs themselves have not changed, other than changing check_exit_code to default to 0. The exit code of the process is now checked against this variable, unless None.

777. By Brian Waldon

- Content-Type and Accept headers handled properly
- Content-Type added to responses
- Query extensions no long cause computeFaults
- adding wsgi.Request object
- removing request-specific code from wsgi.Serializer

778. By Erica Windisch

Fixes doc build after execvp patch.

779. By Vish Ishaya

Fixes a race condition where multiple greenthreads were attempting to resize a file at the same time. Adds tests to verify that the image caching call will run concurrently for different files, but will block other greenthreads trying to cache the same file.

780. By Vish Ishaya

Fixes nova.sh to run properly the first time. We have to get the zip file after nova-api is running.

781. By Vish Ishaya

Modifies S3ImageService to wrap LocalImageService or GlanceImageService. It now pulls the parts out of s3, decrypts them locally, and sends them to the underlying service. It includes various fixes for image/glance.py, image/local.py and the tests.

I also uncovered a bug in glance so for the glance backend to work properly, it requires the patch to glance here lp:~vishvananda/glance/fix-update or Glance's Cactus trunk r80.

782. By Sandy Walsh

Introduces the ZoneManager to the Scheduler which polls the child zones and caches their availability and capabilities.

783. By Soren Hansen

Fix a few things that were either missed in the execvp conversion or stuff that was merged after it, but wasn't updated accordingly.

784. By Soren Hansen

Another little bit of fallout from the execvp branch.

785. By Ken Pepple

initializing instance power state on launch to 0 (fixes EC2 API bug)

786. By Soren Hansen

Add a new IptablesManager that takes care of all uses of iptables.

Port all uses of iptables (in linux_net and libvirt_conn) over to use this new manager.

It wraps all uses of iptables so that each component can maintain its own set of rules without interfering with other components and/or existing system rules.

iptables-restore is an atomic interface to netfilter in the kernel. This means we can make a bunch of changes at a time, minimising the number of calls to iptables.

787. By Dan Prince

Make linux_net ensure_bridge commands that add and remove ip addr's from
devices/bridges work with with the latest utils.execute method (execvp).

788. By Monsyne Dragon

Adds in multi-tenant support to openstack api.
Allows for multiple accounts (projects) with admin api for creating accounts & users.

789. By Ryan Lane

Changes the output of status in describe_volumes from showing the user as the owner of the volume to showing the project as the owner.

790. By Erica Windisch

Passes net variable as value of keyword argument process_input. Prior to the execvp patch, this was passed positionally.

791. By Cory Wright

This change adds the ability to boot Windows and Linux instances in XenServer using different sets of vm-params.

Images in glance should have a property set with a key of `os_type` and a value of either `windows` or `linux`. Images without `os_type` set default to `linux`.

Linux images boot para-virtualized, while Windows images boot via HVM.

792. By Soren Hansen

Remove race condition when refreshing security groups and destroying instances at the same time.

793. By Kevin L. Mitchell

Replace raw SQL calls through session.execute() with SQLAlchemy code.

794. By Josh Kearney

Use a consistent naming scheme for XenAPI variables.

795. By Soren Hansen

Fix instructions for setting up the initial database.

796. By Erica Windisch

fixes: 733137

797. By Soren Hansen

Make nova-dhcpbridge output lease information in dnsmasq's leasesfile format.

798. By Soren Hansen

Only include kernel and ramdisk ID in meta-data output if they are actually set.

799. By Kei Masumoto

NTT's live-migration branch, merged with trunk, conflicts resolved, and migrate file renamed.

800. By Soren Hansen

Include cpuinfo.xml.template in tarball.

801. By justinsb

Fix capitalization of ApiError (it was mistakenly called APIError)

802. By justinsb

Implements basic OpenStack API client, ready to support API tests

803. By Josh Kearney

PEP8 0.5.0 cleanup.

804. By justinsb

Removed duplicated tests.

One was a straight copy-and-paste, one was different, but the masked test didn't pass.

805. By Soren Hansen

Make Authors check account for tests being run with different os.getcwd() depending on how they're run. Add missing people to Authors.

806. By Ken Pepple

just fixing a small typo in nova-manage vm live-migration

807. By Brian Lamar

Fixed lp732866 by catching relevant `exception.NotFound` exception. Tests did not uncover this vulnerability due to "incorrect" FakeAuthManager. I say "incorrect" because potentially different implementations (LDAP or Database driven) of AuthManager might return different errors from `get_user_from_access_key`.

Also, removed all references to 'bacon', 'ham', 'herp', and 'derp' and replaced them with hopefully more helpful terms.

Long story short it addresses the immediate issue while throughly ignoring the larger issue, which is correctly testing all implementations of Auth. I find this acceptable as currently the future of auth is in flux.

808. By Christian Berendt

added new class Instances for managaging instances
added new method list in class Instances:

# nova-manage instance list
instance node type state launched image kernel ramdisk project user zone index
i-00000547 XXXXXXX m1.small running 2011-02-18 08:36:37 ami-a03ndz0q ami-0isqekvw testing berendt None 0
i-00000548 XXXXXXX m1.small running 2011-02-18 08:37:17 ami-a03ndz0q ami-0isqekvw testing berendt None 1
i-00000549 XXXXXXX m1.small running 2011-02-18 08:37:52 ami-a03ndz0q ami-0isqekvw testing berendt None 2

# nova-manage instance list ares
instance node type state launched image kernel ramdisk project user zone index
i-00000c1c ares m1.tiny running 2011-02-26 22:51:40 ami-pus9dj84 ami-zhcv0yyx ami-av96fu30 testing berendt None 1

extended the method list in class FixedIps to lookup ip addresses assigned to a specified node:

# nova-manage fixed list ares
network IP address MAC address hostname host
192.168.3.0/24 192.168.3.6 02:16:3e:75:d7:9a i-00000c1c ares

809. By Thierry Carrez

Fixes euca-get-ajax-console returning Unknown Error, by using the correct
exception in get_open_port() logic. Patch from Tushar Patil.

810. By justinsb

Don't generate insecure passwords where it's easy to use urandom instead

811. By Soren Hansen

Add missing fallback chain for ipv6.

812. By Jay Pipes

Keypairs are not required in the OpenStack API; don't require them!

This cleans up some weirdness from the original branch (https://code.launchpad.net/~justin-fathomdb/nova/bug732204/+merge/53359) with justin's LP username in r804.

813. By Christian Berendt

fixes bug 735298: start of nova-compute not possible because of wrong xml paths to the //host/cpu section in "virsh capabilities", used in nova/virt/libvirt_conn.py

814. By Mark Washenberger

Fix lp727225 by adding support for personality files to the openstack api.

Description:

This merge adds support for personality files to the openstack api. It leverages previous work which added this functionality to the compute api, compute manager, and xen api.

815. By Josh Kearney

Log the use of utils.synchronized.

816. By Soren Hansen

Remove unconditional raise, probably left over from debugging.

817. By Soren Hansen

Comparisons to None should not use == or !=.

Stop converting sets to lists before comparing them. They might be in different order after being list()ified.

818. By Matt Dietz

Fixes lp736343 - Incorrect mapping of instance type id to flavor id in Openstack API

819. By Monsyne Dragon

Add support for network QoS (ratelimiting) for XenServer.
Rate is pulled from the flavor (instance_type) when constructing a vm.

820. By justinsb

Backfix of bugfix of issue blocking creating servers with metadata

821. By Naveed Massjouni

- general approach for openstack api versioning
- openstack api version now preserved in request context
- added view builder classes to handle os api responses
- added imageRef and flavorRef to os api v1.1 servers
- modified addresses container structure in os api v1.1 servers

822. By Soren Hansen

Make utils.execute not overwrite std{in,out,err} args to Popen on retries.
Make utils.execute reject unknown kwargs.

Add a couple of unit tests for utils.execute.

823. By Soren Hansen

Fix a number of place in the volume driver where the argv hadn't been fully split

824. By Soren Hansen

Fix a couple of things that assume that libvirt == kvm/qemu.

825. By Dan Prince

Update the Openstack API to handle case where personality is set but null in the request to create a server.

826. By Todd Willey

Make the rpc cast/call debug calls show what topic they are sending to. This aides in debuugging.

827. By Todd Willey

When changing the project manager, if the new manager is not yet a project member, be sure to make them be a project member.

828. By Anthony Young

Make "ApiError" the default error code for ApiError instances, rather than "Unknown."

Note Dashoard's error handling code:

        except boto.exception.BotoServerError, e:
            if e.status == 400 and e.error_code == 'ApiError':
                raise NovaApiError(e)
            elif e.status == 401:
                raise NovaUnauthorizedError()
            elif e.status == 503:
                raise NovaUnavailableError(e)
            raise NovaServerError(e)

So an error_code of 'Unknown' will raise an ugly exception.

This change also makes the euca errors a bit prettier:

> euca-attach-volume -i i-1 vol-2 -d /dev/vdb
> ApiError: ApiError: Volume status must be available

Rather than:

> euca-attach-volume -i i-1 vol-2 -d /dev/vdb
> Unknown: Unknown: Volume status must be available

Though the reiteration of the error code is still a bit ugly.

829. By justinsb

Mark instance metadata as deleted when we delete the instance

830. By Brian Lamar

Re-implementation (or just implementation in many cases) of Limits in the OpenStack API. Limits is now available through /limits and the concept of a limit has been extended to include arbitrary regex / http verb combinations along with correct XML/JSON serialization. Tests included.

831. By Dan Prince

Change cloud.id_to_ec2_id to ec2utils.id_to_ec2_id. Fixes EC2 API error handling when invalid instances and volume names are specified.

832. By Brian Lamar

Replaced all pylint "disable-msg=" with "disable=" and "enable-msg=" with "enable=".

833. By justinsb

Cleanup of FakeAuthManager

834. By Tushar Patil

Added run_instances method to the connection.py of the contrib/boto_v6/ec2 which would return ReservationV6 object instead of Reservation in order to access attribute dns_name_v6 of an instance.

835. By Soren Hansen

Make smoketests' exit code reveal whether they were succesful.

Adjust volume tests to check the exact size of the block device, instead of a rounded-off size of the resulting filesystem.

Make proxy.sh work with both variants of netcat.

836. By justinsb

Tell PyLint not to complain about the "_" function

837. By Ken Pepple

fixes nova-manage instance_type compatibility with postgres db

838. By Josh Kleinpeter

Changed fixed_range (CIDR) to be required in the nova-manage command; changed default num_networks to 1.

839. By Brian Waldon

Openstack api 1.0 flavors resource now implemented to match the spec

840. By Josh Kearney

Provide more useful exception messages when unable to load the virtual driver.

841. By Ken Pepple

wrap and log errors getting image ids from local image store

842. By Soren Hansen

Wrap update_dhcp in utils.synchronized.

843. By Trey Morris

xenapi support for multi_nic. This is a phase of multi_nic which allows xenapi to work as is and with multi_nic. The other virt driver(s) need to be updated with the same support.

844. By Todd Willey

Fix a typo in the ec2 admin api.

845. By Tushar Patil

Fix for LP Bug #704300

846. By Todd Willey

Fix the describe_vpns admin api call.

Firstly, use the correct mechanism for mapping ec2 ids from instance ids.

Secondly, if a vpn doesn't have an ip/port assignment from the project, don't try utils.vpn_ping, as it will raise an exception.

847. By justinsb

Test the login behavior of the OpenStack API. Uncovered bug732866

848. By Vish Ishaya

Correctly imports greenthread in libvirt_conn.py. It is used by live_migrate().

849. By Anne Gentle

Adding a talk bubble to the nova.openstack.org site that points readers to the 2011.1 site and the docs.openstack.org site - similar to the swift.openstack.org site. I believe it helps people see more sites are available, plus they can get to the Bexar site if they want to. Going forward it'll be nice to use this talk bubble to point people to the trunk site from released sites.

850. By Tushar Patil

Enable flat manager support for ipv6.

851. By Anthony Young

Fix for #740742 - format describe_instance_output correctly to prevent errors in dashboard.

Without this change, it is not possible to properly select instance types when launching instances with the OS dashboard.

852. By Hisaharu Ishii

We update update_ra method to synchronize, in order to prevent crash when we request multiple instance at once.

853. By Salvatore Orlando

This branch contains the fix for bug #740929
It makes sure cidr_v6 is not null before building the 'ip6s' key in the network info dictionary.
This way utils.to_global_ipv6 does not fail because of cidr==None.

854. By Mark Washenberger

Fix lp735636 by standardizing the format of image timestamp properties as datetime objects.

So far, only glance supports returning image create, update, and delete timestamps. And only the openstack api reports those dates. Thus, the fixes were confined to those two modules.

855. By Matt Dietz

Updates the previously merged xs_migration functionality to allow upsizing of the RAM and disk quotas for a XenServer instance.

856. By Soren Hansen

Move all types of locking into utils.synchronize decorator.

Convert all uses of semaphores to use this decorator.

857. By Tushar Patil

boto_v6 module is imported if the flag "use_ipv6" is set to True

858. By Josh Kearney

Offers the ability to run a periodic_task that sweeps through rescued instances older than 24 hours and forcibly unrescues them.

Flag added: rescue_timeout (default is 0 - disabled)

859. By Todd Willey

Fix issues with certificate updating & whitespace removal

860. By justinsb

better logging of exceptions

861. By Naveed Massjouni

Added a mechanism for versioned controllers for openstack api versions 1.0/1.1.
Create servers in the 1.1 api now supports imageRef/flavorRef instead of imageId/flavorId.

862. By justinsb

Poll instance states periodically, so that we can detect when something changes 'behind the scenes'.

Beginnings of work on Bug #661214 and Bug #661260.

863. By Soren Hansen

Pass a fake timing source to live_migration_pre in every test that expectes it to fail, shaving off a whole minute of test run time.

864. By Matt Dietz

Fixes lp740322: cannot run test_localization in isolation

865. By Sandy Walsh

Aggregates capabilities from Compute, Network, Volume to the ZoneManager in Scheduler.

866. By Muneyuki Noguchi

Fix lp741514 by declaring libvirt_type in nova-manage.

867. By justinsb

Detect if user is running the default Lucid version of libvirt, and give a nicer error message

868. By justinsb

Don't try to parse the empty string as a datetime

869. By Josh Kleinpeter

Made service_get_all()'s disabled parameter default to None. Pass False for enabled services; True for disabled services. Calls to this method have been updated to remain consistent.

870. By Brian Lamar

Pylint 'Undefined variable' E0602 error fixes.

871. By Todd Willey

Fix api logging to show proper path and controller:action.

872. By justinsb

Fix some errors that pylint found in nova/api/openstack/servers.py

This was meant more as a test that pylint is actually being helpful now (it is), but these are real errors.

873. By termie

Fixes a bug that was causing tests to fail on OS X by ensuring that greenthread sleep is called during retry loops.

874. By Sandy Walsh

In this branch we are forwarding incoming requests to child zones when the requested resource is not found in the current zone.

For example: If 'nova pause 123' is issued against Zone 1, but instance 123 does not live in Zone 1, the call will be forwarded to all child zones hoping someone can deal with it.

NOTE: This currently only works with OpenStack API requests and routing checks are only being done against Compute/instance_id checks.
Specifically:
* servers.get/pause/unpause/diagnostics/suspend/resume/rescue/unrescue/delete
* servers.create is pending for distributed scheduler
* servers.get_all will get added early in Diablo.

What I've been doing for testing:
1. Set up a Nova deployment in a VM (Zone0)
2. Clone the VM and set --zone_name=zone1 (and change all the IP addresses to the new address in nova.conf, glance.conf and novarc)
3. Set --enable_zone_routing=true on all zones
4. use the --connection_type=fake driver for compute to keep things easy
5. Add Zone1 as a child of Zone0 (nova zone-add)

(make sure the instance id's are different in each zone)

Example of calls being sent to child zones:
http://paste.openstack.org/show/964/

875. By termie

Additions to the Direct API:

 * Add an example of a versioned api
 * Add some more docs to direct.py
 * Add Limited, an API limiting/versioning wrapper
 * Improve the formatting of the stack tool
 * Add support for volume and network services to the direct api

876. By Ilya Alekseyev

libvirt driver multi_nic support. In this phase libvirt can work with and without multi_nic support, as in multi_nic support for xenapi: https://code.launchpad.net/~tr3buchet/nova/xs_multi_nic/+merge/53458

877. By Josh Kearney

Adds unit test coverage for XenAPI Rescue & Unrescue.

878. By Dan Prince

Implement API extensions for the Openstack API. Based on the Openstack 1.1 API the following types of extensions are supported:

-Top level resources (extension)
-Action extensions (add an extra action to a core nova controller)
-Response extensions (inject data into response from core nova controllers)

To add an extension simply drop an extension file into the configured osapi_extensions_path (which defaults to /var/lib/nova/extensions).

See nova/tests/api/openstack/extensions/foxinsocks.py for an example Extension.

879. By Salvatore Orlando

Fix for bug #740947
Executing parted with sudo in _write_partition (vm_utils.py)

880. By Rick Harris

Adds serverId to OpenStack API image detail per related_image blueprint

881. By Sateesh

Implementation of blueprint hypervisor-vmware-vsphere-support. (Link to blueprint: https://blueprints.launchpad.net/nova/+spec/hypervisor-vmware-vsphere-support)

Adds support for hypervisor vmware ESX/ESXi server in OpenStack (Nova).

Key features included are,
1) Support for FLAT and VLAN networking model
2) Support for Guest console access through VMware vmrc
3) Integrated with Glance service for image storage and retrival

Documentation: A readme file at "doc/source/vmwareapi_readme.rst" encapsulates configuration/installation instructions required to use this module/feature.

882. By termie

add s3server, pre-modifications

883. By termie

port s3server to eventlet/wsgi

884. By termie

rename objectstore tests

885. By termie

update test base class to monkey patch wsgi

886. By termie

port the objectstore tests to the new tests

887. By termie

remove twisted objectstore

888. By termie

don't require integrated tests to recycle connections

889. By termie

add descriptive docstring

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/nova-objectstore'
2--- bin/nova-objectstore 2010-12-14 23:22:03 +0000
3+++ bin/nova-objectstore 2011-03-04 05:34:14 +0000
4@@ -36,9 +36,10 @@
5 gettext.install('nova', unicode=1)
6
7 from nova import flags
8+from nova import log as logging
9 from nova import utils
10-from nova import twistd
11-from nova.objectstore import handler
12+from nova import wsgi
13+from nova.objectstore import s3server
14
15
16 FLAGS = flags.FLAGS
17@@ -46,7 +47,9 @@
18
19 if __name__ == '__main__':
20 utils.default_flagfile()
21- twistd.serve(__file__)
22-
23-if __name__ == '__builtin__':
24- application = handler.get_application() # pylint: disable-msg=C0103
25+ FLAGS(sys.argv)
26+ logging.setup()
27+ router = s3server.S3Application(FLAGS.buckets_path)
28+ server = wsgi.Server()
29+ server.start(router, FLAGS.s3_port, host=FLAGS.s3_host)
30+ server.wait()
31
32=== modified file 'nova/objectstore/bucket.py'
33--- nova/objectstore/bucket.py 2011-02-19 06:49:13 +0000
34+++ nova/objectstore/bucket.py 2011-03-04 05:34:14 +0000
35@@ -33,8 +33,7 @@
36
37
38 FLAGS = flags.FLAGS
39-flags.DEFINE_string('buckets_path', '$state_path/buckets',
40- 'path to s3 buckets')
41+flags.DECLARE('buckets_path', 'nova.objectstore.s3server')
42
43
44 class Bucket(object):
45
46=== added file 'nova/objectstore/s3server.py'
47--- nova/objectstore/s3server.py 1970-01-01 00:00:00 +0000
48+++ nova/objectstore/s3server.py 2011-03-04 05:34:14 +0000
49@@ -0,0 +1,303 @@
50+# vim: tabstop=4 shiftwidth=4 softtabstop=4
51+#
52+# Copyright 2010 United States Government as represented by the
53+# Administrator of the National Aeronautics and Space Administration.
54+# Copyright 2010 OpenStack LLC.
55+# Copyright 2009 Facebook
56+#
57+# Licensed under the Apache License, Version 2.0 (the "License"); you may
58+# not use this file except in compliance with the License. You may obtain
59+# a copy of the License at
60+#
61+# http://www.apache.org/licenses/LICENSE-2.0
62+#
63+# Unless required by applicable law or agreed to in writing, software
64+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
65+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
66+# License for the specific language governing permissions and limitations
67+# under the License.
68+
69+"""Implementation of an S3-like storage server based on local files.
70+
71+Useful to test features that will eventually run on S3, or if you want to
72+run something locally that was once running on S3.
73+
74+We don't support all the features of S3, but it does work with the
75+standard S3 client for the most basic semantics. To use the standard
76+S3 client with this module:
77+
78+ c = S3.AWSAuthConnection("", "", server="localhost", port=8888,
79+ is_secure=False)
80+ c.create_bucket("mybucket")
81+ c.put("mybucket", "mykey", "a value")
82+ print c.get("mybucket", "mykey").body
83+
84+"""
85+
86+import bisect
87+import datetime
88+import hashlib
89+import os
90+import os.path
91+import urllib
92+
93+import routes
94+import webob
95+
96+from nova import flags
97+from nova import log as logging
98+from nova import utils
99+from nova import wsgi
100+
101+
102+FLAGS = flags.FLAGS
103+flags.DEFINE_string('buckets_path', '$state_path/buckets',
104+ 'path to s3 buckets')
105+
106+
107+class S3Application(wsgi.Router):
108+ """Implementation of an S3-like storage server based on local files.
109+
110+ If bucket depth is given, we break files up into multiple directories
111+ to prevent hitting file system limits for number of files in each
112+ directories. 1 means one level of directories, 2 means 2, etc.
113+
114+ """
115+
116+ def __init__(self, root_directory, bucket_depth=0, mapper=None):
117+ if mapper is None:
118+ mapper = routes.Mapper()
119+
120+ mapper.connect('/',
121+ controller=lambda *a, **kw: RootHandler(self)(*a, **kw))
122+ mapper.connect('/{bucket}/{object_name}',
123+ controller=lambda *a, **kw: ObjectHandler(self)(*a, **kw))
124+ mapper.connect('/{bucket_name}/',
125+ controller=lambda *a, **kw: BucketHandler(self)(*a, **kw))
126+ self.directory = os.path.abspath(root_directory)
127+ if not os.path.exists(self.directory):
128+ os.makedirs(self.directory)
129+ self.bucket_depth = bucket_depth
130+ super(S3Application, self).__init__(mapper)
131+
132+
133+class BaseRequestHandler(wsgi.Controller):
134+ def __init__(self, application):
135+ self.application = application
136+
137+ @webob.dec.wsgify
138+ def __call__(self, request):
139+ method = request.method.lower()
140+ f = getattr(self, method, self.invalid)
141+ self.request = request
142+ self.response = webob.Response()
143+ params = request.environ['wsgiorg.routing_args'][1]
144+ del params['controller']
145+ f(**params)
146+ return self.response
147+
148+ def get_argument(self, arg, default):
149+ return self.request.str_params.get(arg, default)
150+
151+ def set_header(self, header, value):
152+ self.response.headers[header] = value
153+
154+ def set_status(self, status_code):
155+ self.response.status = status_code
156+
157+ def finish(self, body=''):
158+ self.response.body = utils.utf8(body)
159+
160+ def invalid(self, **kwargs):
161+ pass
162+
163+ def render_xml(self, value):
164+ assert isinstance(value, dict) and len(value) == 1
165+ self.set_header("Content-Type", "application/xml; charset=UTF-8")
166+ name = value.keys()[0]
167+ parts = []
168+ parts.append('<' + utils.utf8(name) +
169+ ' xmlns="http://doc.s3.amazonaws.com/2006-03-01">')
170+ self._render_parts(value.values()[0], parts)
171+ parts.append('</' + utils.utf8(name) + '>')
172+ self.finish('<?xml version="1.0" encoding="UTF-8"?>\n' +
173+ ''.join(parts))
174+
175+ def _render_parts(self, value, parts=[]):
176+ if isinstance(value, basestring):
177+ parts.append(utils.xhtml_escape(value))
178+ elif isinstance(value, int) or isinstance(value, long):
179+ parts.append(str(value))
180+ elif isinstance(value, datetime.datetime):
181+ parts.append(value.strftime("%Y-%m-%dT%H:%M:%S.000Z"))
182+ elif isinstance(value, dict):
183+ for name, subvalue in value.iteritems():
184+ if not isinstance(subvalue, list):
185+ subvalue = [subvalue]
186+ for subsubvalue in subvalue:
187+ parts.append('<' + utils.utf8(name) + '>')
188+ self._render_parts(subsubvalue, parts)
189+ parts.append('</' + utils.utf8(name) + '>')
190+ else:
191+ raise Exception("Unknown S3 value type %r", value)
192+
193+ def _object_path(self, bucket, object_name):
194+ if self.application.bucket_depth < 1:
195+ return os.path.abspath(os.path.join(
196+ self.application.directory, bucket, object_name))
197+ hash = hashlib.md5(object_name).hexdigest()
198+ path = os.path.abspath(os.path.join(
199+ self.application.directory, bucket))
200+ for i in range(self.application.bucket_depth):
201+ path = os.path.join(path, hash[:2 * (i + 1)])
202+ return os.path.join(path, object_name)
203+
204+
205+class RootHandler(BaseRequestHandler):
206+ def get(self):
207+ names = os.listdir(self.application.directory)
208+ buckets = []
209+ for name in names:
210+ path = os.path.join(self.application.directory, name)
211+ info = os.stat(path)
212+ buckets.append({
213+ "Name": name,
214+ "CreationDate": datetime.datetime.utcfromtimestamp(
215+ info.st_ctime),
216+ })
217+ self.render_xml({"ListAllMyBucketsResult": {
218+ "Buckets": {"Bucket": buckets},
219+ }})
220+
221+
222+class BucketHandler(BaseRequestHandler):
223+ def get(self, bucket_name):
224+ prefix = self.get_argument("prefix", u"")
225+ marker = self.get_argument("marker", u"")
226+ max_keys = int(self.get_argument("max-keys", 50000))
227+ path = os.path.abspath(os.path.join(self.application.directory,
228+ bucket_name))
229+ terse = int(self.get_argument("terse", 0))
230+ if not path.startswith(self.application.directory) or \
231+ not os.path.isdir(path):
232+ self.set_status(404)
233+ return
234+ object_names = []
235+ for root, dirs, files in os.walk(path):
236+ for file_name in files:
237+ object_names.append(os.path.join(root, file_name))
238+ skip = len(path) + 1
239+ for i in range(self.application.bucket_depth):
240+ skip += 2 * (i + 1) + 1
241+ object_names = [n[skip:] for n in object_names]
242+ object_names.sort()
243+ contents = []
244+
245+ start_pos = 0
246+ if marker:
247+ start_pos = bisect.bisect_right(object_names, marker, start_pos)
248+ if prefix:
249+ start_pos = bisect.bisect_left(object_names, prefix, start_pos)
250+
251+ truncated = False
252+ for object_name in object_names[start_pos:]:
253+ if not object_name.startswith(prefix):
254+ break
255+ if len(contents) >= max_keys:
256+ truncated = True
257+ break
258+ object_path = self._object_path(bucket_name, object_name)
259+ c = {"Key": object_name}
260+ if not terse:
261+ info = os.stat(object_path)
262+ c.update({
263+ "LastModified": datetime.datetime.utcfromtimestamp(
264+ info.st_mtime),
265+ "Size": info.st_size,
266+ })
267+ contents.append(c)
268+ marker = object_name
269+ self.render_xml({"ListBucketResult": {
270+ "Name": bucket_name,
271+ "Prefix": prefix,
272+ "Marker": marker,
273+ "MaxKeys": max_keys,
274+ "IsTruncated": truncated,
275+ "Contents": contents,
276+ }})
277+
278+ def put(self, bucket_name):
279+ path = os.path.abspath(os.path.join(
280+ self.application.directory, bucket_name))
281+ if not path.startswith(self.application.directory) or \
282+ os.path.exists(path):
283+ self.set_status(403)
284+ return
285+ os.makedirs(path)
286+ self.finish()
287+
288+ def delete(self, bucket_name):
289+ path = os.path.abspath(os.path.join(
290+ self.application.directory, bucket_name))
291+ if not path.startswith(self.application.directory) or \
292+ not os.path.isdir(path):
293+ self.set_status(404)
294+ return
295+ if len(os.listdir(path)) > 0:
296+ self.set_status(403)
297+ return
298+ os.rmdir(path)
299+ self.set_status(204)
300+ self.finish()
301+
302+
303+class ObjectHandler(BaseRequestHandler):
304+ def get(self, bucket, object_name):
305+ object_name = urllib.unquote(object_name)
306+ path = self._object_path(bucket, object_name)
307+ if not path.startswith(self.application.directory) or \
308+ not os.path.isfile(path):
309+ self.set_status(404)
310+ return
311+ info = os.stat(path)
312+ self.set_header("Content-Type", "application/unknown")
313+ self.set_header("Last-Modified", datetime.datetime.utcfromtimestamp(
314+ info.st_mtime))
315+ object_file = open(path, "r")
316+ try:
317+ self.finish(object_file.read())
318+ finally:
319+ object_file.close()
320+
321+ def put(self, bucket, object_name):
322+ object_name = urllib.unquote(object_name)
323+ bucket_dir = os.path.abspath(os.path.join(
324+ self.application.directory, bucket))
325+ if not bucket_dir.startswith(self.application.directory) or \
326+ not os.path.isdir(bucket_dir):
327+ self.set_status(404)
328+ return
329+ path = self._object_path(bucket, object_name)
330+ if not path.startswith(bucket_dir) or os.path.isdir(path):
331+ self.set_status(403)
332+ return
333+ directory = os.path.dirname(path)
334+ if not os.path.exists(directory):
335+ os.makedirs(directory)
336+ object_file = open(path, "w")
337+ object_file.write(self.request.body)
338+ object_file.close()
339+ self.set_header('ETag',
340+ '"%s"' % hashlib.md5(self.request.body).hexdigest())
341+ self.finish()
342+
343+ def delete(self, bucket, object_name):
344+ object_name = urllib.unquote(object_name)
345+ path = self._object_path(bucket, object_name)
346+ if not path.startswith(self.application.directory) or \
347+ not os.path.isfile(path):
348+ self.set_status(404)
349+ return
350+ os.unlink(path)
351+ self.set_status(204)
352+ self.finish()
353
354=== modified file 'nova/test.py'
355--- nova/test.py 2011-03-01 00:28:46 +0000
356+++ nova/test.py 2011-03-04 05:34:14 +0000
357@@ -24,6 +24,7 @@
358
359
360 import datetime
361+import functools
362 import os
363 import shutil
364 import uuid
365@@ -32,6 +33,8 @@
366 import mox
367 import shutil
368 import stubout
369+from eventlet import greenpool
370+from eventlet import greenthread
371
372 from nova import context
373 from nova import db
374@@ -39,6 +42,7 @@
375 from nova import flags
376 from nova import rpc
377 from nova import service
378+from nova import wsgi
379
380
381 FLAGS = flags.FLAGS
382@@ -79,6 +83,7 @@
383 self.injected = []
384 self._services = []
385 self._monkey_patch_attach()
386+ self._monkey_patch_wsgi()
387 self._original_flags = FLAGS.FlagValuesDict()
388
389 def tearDown(self):
390@@ -99,7 +104,8 @@
391 self.reset_flags()
392
393 # Reset our monkey-patches
394- rpc.Consumer.attach_to_eventlet = self.originalAttach
395+ rpc.Consumer.attach_to_eventlet = self.original_attach
396+ wsgi.Server.start = self.original_start
397
398 # Stop any timers
399 for x in self.injected:
400@@ -141,12 +147,33 @@
401 return svc
402
403 def _monkey_patch_attach(self):
404- self.originalAttach = rpc.Consumer.attach_to_eventlet
405+ self.original_attach = rpc.Consumer.attach_to_eventlet
406
407- def _wrapped(innerSelf):
408- rv = self.originalAttach(innerSelf)
409+ def _wrapped(inner_self):
410+ rv = self.original_attach(inner_self)
411 self.injected.append(rv)
412 return rv
413
414- _wrapped.func_name = self.originalAttach.func_name
415+ _wrapped.func_name = self.original_attach.func_name
416 rpc.Consumer.attach_to_eventlet = _wrapped
417+
418+ def _monkey_patch_wsgi(self):
419+ """Allow us to kill servers spawned by wsgi.Server."""
420+ # TODO(termie): change these patterns to use functools
421+ self.original_start = wsgi.Server.start
422+
423+ @functools.wraps(self.original_start)
424+ def _wrapped_start(inner_self, *args, **kwargs):
425+ original_spawn_n = inner_self.pool.spawn_n
426+
427+ @functools.wraps(original_spawn_n)
428+ def _wrapped_spawn_n(*args, **kwargs):
429+ rv = greenthread.spawn(*args, **kwargs)
430+ self._services.append(rv)
431+
432+ inner_self.pool.spawn_n = _wrapped_spawn_n
433+ self.original_start(inner_self, *args, **kwargs)
434+ inner_self.pool.spawn_n = original_spawn_n
435+
436+ _wrapped_start.func_name = self.original_start.func_name
437+ wsgi.Server.start = _wrapped_start
438
439=== removed file 'nova/tests/objectstore_unittest.py'
440--- nova/tests/objectstore_unittest.py 2011-02-23 19:56:37 +0000
441+++ nova/tests/objectstore_unittest.py 1970-01-01 00:00:00 +0000
442@@ -1,315 +0,0 @@
443-# vim: tabstop=4 shiftwidth=4 softtabstop=4
444-
445-# Copyright 2010 United States Government as represented by the
446-# Administrator of the National Aeronautics and Space Administration.
447-# All Rights Reserved.
448-#
449-# Licensed under the Apache License, Version 2.0 (the "License"); you may
450-# not use this file except in compliance with the License. You may obtain
451-# a copy of the License at
452-#
453-# http://www.apache.org/licenses/LICENSE-2.0
454-#
455-# Unless required by applicable law or agreed to in writing, software
456-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
457-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
458-# License for the specific language governing permissions and limitations
459-# under the License.
460-
461-"""
462-Unittets for S3 objectstore clone.
463-"""
464-
465-import boto
466-import glob
467-import hashlib
468-import os
469-import shutil
470-import tempfile
471-
472-from boto.s3.connection import S3Connection, OrdinaryCallingFormat
473-from twisted.internet import reactor, threads, defer
474-from twisted.web import http, server
475-
476-from nova import context
477-from nova import flags
478-from nova import objectstore
479-from nova import test
480-from nova.auth import manager
481-from nova.exception import NotEmpty, NotFound
482-from nova.objectstore import image
483-from nova.objectstore.handler import S3
484-
485-
486-FLAGS = flags.FLAGS
487-
488-# Create a unique temporary directory. We don't delete after test to
489-# allow checking the contents after running tests. Users and/or tools
490-# running the tests need to remove the tests directories.
491-OSS_TEMPDIR = tempfile.mkdtemp(prefix='test_oss-')
492-
493-# Create bucket/images path
494-os.makedirs(os.path.join(OSS_TEMPDIR, 'images'))
495-os.makedirs(os.path.join(OSS_TEMPDIR, 'buckets'))
496-
497-
498-class ObjectStoreTestCase(test.TestCase):
499- """Test objectstore API directly."""
500-
501- def setUp(self):
502- """Setup users and projects."""
503- super(ObjectStoreTestCase, self).setUp()
504- self.flags(buckets_path=os.path.join(OSS_TEMPDIR, 'buckets'),
505- images_path=os.path.join(OSS_TEMPDIR, 'images'),
506- ca_path=os.path.join(os.path.dirname(__file__), 'CA'))
507-
508- self.auth_manager = manager.AuthManager()
509- self.auth_manager.create_user('user1')
510- self.auth_manager.create_user('user2')
511- self.auth_manager.create_user('admin_user', admin=True)
512- self.auth_manager.create_project('proj1', 'user1', 'a proj', ['user1'])
513- self.auth_manager.create_project('proj2', 'user2', 'a proj', ['user2'])
514- self.context = context.RequestContext('user1', 'proj1')
515-
516- def tearDown(self):
517- """Tear down users and projects."""
518- self.auth_manager.delete_project('proj1')
519- self.auth_manager.delete_project('proj2')
520- self.auth_manager.delete_user('user1')
521- self.auth_manager.delete_user('user2')
522- self.auth_manager.delete_user('admin_user')
523- super(ObjectStoreTestCase, self).tearDown()
524-
525- def test_buckets(self):
526- """Test the bucket API."""
527- objectstore.bucket.Bucket.create('new_bucket', self.context)
528- bucket = objectstore.bucket.Bucket('new_bucket')
529-
530- # creator is authorized to use bucket
531- self.assert_(bucket.is_authorized(self.context))
532-
533- # another user is not authorized
534- context2 = context.RequestContext('user2', 'proj2')
535- self.assertFalse(bucket.is_authorized(context2))
536-
537- # admin is authorized to use bucket
538- admin_context = context.RequestContext('admin_user', None)
539- self.assertTrue(bucket.is_authorized(admin_context))
540-
541- # new buckets are empty
542- self.assertTrue(bucket.list_keys()['Contents'] == [])
543-
544- # storing keys works
545- bucket['foo'] = "bar"
546-
547- self.assertEquals(len(bucket.list_keys()['Contents']), 1)
548-
549- self.assertEquals(bucket['foo'].read(), 'bar')
550-
551- # md5 of key works
552- self.assertEquals(bucket['foo'].md5, hashlib.md5('bar').hexdigest())
553-
554- # deleting non-empty bucket should throw a NotEmpty exception
555- self.assertRaises(NotEmpty, bucket.delete)
556-
557- # deleting key
558- del bucket['foo']
559-
560- # deleting empty bucket
561- bucket.delete()
562-
563- # accessing deleted bucket throws exception
564- self.assertRaises(NotFound, objectstore.bucket.Bucket, 'new_bucket')
565-
566- def test_images(self):
567- self.do_test_images('1mb.manifest.xml', True,
568- 'image_bucket1', 'i-testing1')
569-
570- def test_images_no_kernel_or_ramdisk(self):
571- self.do_test_images('1mb.no_kernel_or_ramdisk.manifest.xml',
572- False, 'image_bucket2', 'i-testing2')
573-
574- def do_test_images(self, manifest_file, expect_kernel_and_ramdisk,
575- image_bucket, image_name):
576- "Test the image API."
577-
578- # create a bucket for our bundle
579- objectstore.bucket.Bucket.create(image_bucket, self.context)
580- bucket = objectstore.bucket.Bucket(image_bucket)
581-
582- # upload an image manifest/parts
583- bundle_path = os.path.join(os.path.dirname(__file__), 'bundle')
584- for path in glob.glob(bundle_path + '/*'):
585- bucket[os.path.basename(path)] = open(path, 'rb').read()
586-
587- # register an image
588- image.Image.register_aws_image(image_name,
589- '%s/%s' % (image_bucket, manifest_file),
590- self.context)
591-
592- # verify image
593- my_img = image.Image(image_name)
594- result_image_file = os.path.join(my_img.path, 'image')
595- self.assertEqual(os.stat(result_image_file).st_size, 1048576)
596-
597- sha = hashlib.sha1(open(result_image_file).read()).hexdigest()
598- self.assertEqual(sha, '3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3')
599-
600- if expect_kernel_and_ramdisk:
601- # Verify the default kernel and ramdisk are set
602- self.assertEqual(my_img.metadata['kernelId'], 'aki-test')
603- self.assertEqual(my_img.metadata['ramdiskId'], 'ari-test')
604- else:
605- # Verify that the default kernel and ramdisk (the one from FLAGS)
606- # doesn't get embedded in the metadata
607- self.assertFalse('kernelId' in my_img.metadata)
608- self.assertFalse('ramdiskId' in my_img.metadata)
609-
610- # verify image permissions
611- context2 = context.RequestContext('user2', 'proj2')
612- self.assertFalse(my_img.is_authorized(context2))
613-
614- # change user-editable fields
615- my_img.update_user_editable_fields({'display_name': 'my cool image'})
616- self.assertEqual('my cool image', my_img.metadata['displayName'])
617- my_img.update_user_editable_fields({'display_name': ''})
618- self.assert_(not my_img.metadata['displayName'])
619-
620-
621-class TestHTTPChannel(http.HTTPChannel):
622- """Dummy site required for twisted.web"""
623-
624- def checkPersistence(self, _, __): # pylint: disable-msg=C0103
625- """Otherwise we end up with an unclean reactor."""
626- return False
627-
628-
629-class TestSite(server.Site):
630- """Dummy site required for twisted.web"""
631- protocol = TestHTTPChannel
632-
633-
634-class S3APITestCase(test.TestCase):
635- """Test objectstore through S3 API."""
636-
637- def setUp(self):
638- """Setup users, projects, and start a test server."""
639- super(S3APITestCase, self).setUp()
640-
641- FLAGS.auth_driver = 'nova.auth.ldapdriver.FakeLdapDriver'
642- FLAGS.buckets_path = os.path.join(OSS_TEMPDIR, 'buckets')
643-
644- self.auth_manager = manager.AuthManager()
645- self.admin_user = self.auth_manager.create_user('admin', admin=True)
646- self.admin_project = self.auth_manager.create_project('admin',
647- self.admin_user)
648-
649- shutil.rmtree(FLAGS.buckets_path)
650- os.mkdir(FLAGS.buckets_path)
651-
652- root = S3()
653- self.site = TestSite(root)
654- # pylint: disable-msg=E1101
655- self.listening_port = reactor.listenTCP(0, self.site,
656- interface='127.0.0.1')
657- # pylint: enable-msg=E1101
658- self.tcp_port = self.listening_port.getHost().port
659-
660- if not boto.config.has_section('Boto'):
661- boto.config.add_section('Boto')
662- boto.config.set('Boto', 'num_retries', '0')
663- self.conn = S3Connection(aws_access_key_id=self.admin_user.access,
664- aws_secret_access_key=self.admin_user.secret,
665- host='127.0.0.1',
666- port=self.tcp_port,
667- is_secure=False,
668- calling_format=OrdinaryCallingFormat())
669-
670- def get_http_connection(host, is_secure):
671- """Get a new S3 connection, don't attempt to reuse connections."""
672- return self.conn.new_http_connection(host, is_secure)
673-
674- self.conn.get_http_connection = get_http_connection
675-
676- def _ensure_no_buckets(self, buckets): # pylint: disable-msg=C0111
677- self.assertEquals(len(buckets), 0, "Bucket list was not empty")
678- return True
679-
680- def _ensure_one_bucket(self, buckets, name): # pylint: disable-msg=C0111
681- self.assertEquals(len(buckets), 1,
682- "Bucket list didn't have exactly one element in it")
683- self.assertEquals(buckets[0].name, name, "Wrong name")
684- return True
685-
686- def test_000_list_buckets(self):
687- """Make sure we are starting with no buckets."""
688- deferred = threads.deferToThread(self.conn.get_all_buckets)
689- deferred.addCallback(self._ensure_no_buckets)
690- return deferred
691-
692- def test_001_create_and_delete_bucket(self):
693- """Test bucket creation and deletion."""
694- bucket_name = 'testbucket'
695-
696- deferred = threads.deferToThread(self.conn.create_bucket, bucket_name)
697- deferred.addCallback(lambda _:
698- threads.deferToThread(self.conn.get_all_buckets))
699-
700- deferred.addCallback(self._ensure_one_bucket, bucket_name)
701-
702- deferred.addCallback(lambda _:
703- threads.deferToThread(self.conn.delete_bucket,
704- bucket_name))
705- deferred.addCallback(lambda _:
706- threads.deferToThread(self.conn.get_all_buckets))
707- deferred.addCallback(self._ensure_no_buckets)
708- return deferred
709-
710- def test_002_create_bucket_and_key_and_delete_key_again(self):
711- """Test key operations on buckets."""
712- bucket_name = 'testbucket'
713- key_name = 'somekey'
714- key_contents = 'somekey'
715-
716- deferred = threads.deferToThread(self.conn.create_bucket, bucket_name)
717- deferred.addCallback(lambda b:
718- threads.deferToThread(b.new_key, key_name))
719- deferred.addCallback(lambda k:
720- threads.deferToThread(k.set_contents_from_string,
721- key_contents))
722-
723- def ensure_key_contents(bucket_name, key_name, contents):
724- """Verify contents for a key in the given bucket."""
725- bucket = self.conn.get_bucket(bucket_name)
726- key = bucket.get_key(key_name)
727- self.assertEquals(key.get_contents_as_string(), contents,
728- "Bad contents")
729-
730- deferred.addCallback(lambda _:
731- threads.deferToThread(ensure_key_contents,
732- bucket_name, key_name,
733- key_contents))
734-
735- def delete_key(bucket_name, key_name):
736- """Delete a key for the given bucket."""
737- bucket = self.conn.get_bucket(bucket_name)
738- key = bucket.get_key(key_name)
739- key.delete()
740-
741- deferred.addCallback(lambda _:
742- threads.deferToThread(delete_key, bucket_name,
743- key_name))
744- deferred.addCallback(lambda _:
745- threads.deferToThread(self.conn.get_bucket,
746- bucket_name))
747- deferred.addCallback(lambda b: threads.deferToThread(b.get_all_keys))
748- deferred.addCallback(self._ensure_no_buckets)
749- return deferred
750-
751- def tearDown(self):
752- """Tear down auth and test server."""
753- self.auth_manager.delete_user('admin')
754- self.auth_manager.delete_project('admin')
755- stop_listening = defer.maybeDeferred(self.listening_port.stopListening)
756- super(S3APITestCase, self).tearDown()
757- return defer.DeferredList([stop_listening])
758
759=== added file 'nova/tests/test_objectstore.py'
760--- nova/tests/test_objectstore.py 1970-01-01 00:00:00 +0000
761+++ nova/tests/test_objectstore.py 2011-03-04 05:34:14 +0000
762@@ -0,0 +1,150 @@
763+# vim: tabstop=4 shiftwidth=4 softtabstop=4
764+
765+# Copyright 2010 United States Government as represented by the
766+# Administrator of the National Aeronautics and Space Administration.
767+# All Rights Reserved.
768+#
769+# Licensed under the Apache License, Version 2.0 (the "License"); you may
770+# not use this file except in compliance with the License. You may obtain
771+# a copy of the License at
772+#
773+# http://www.apache.org/licenses/LICENSE-2.0
774+#
775+# Unless required by applicable law or agreed to in writing, software
776+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
777+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
778+# License for the specific language governing permissions and limitations
779+# under the License.
780+
781+"""
782+Unittets for S3 objectstore clone.
783+"""
784+
785+import boto
786+import glob
787+import hashlib
788+import os
789+import shutil
790+import tempfile
791+
792+from boto import exception as boto_exception
793+from boto.s3 import connection as s3
794+
795+from nova import context
796+from nova import exception
797+from nova import flags
798+from nova import objectstore
799+from nova import wsgi
800+from nova import test
801+from nova.auth import manager
802+#from nova.exception import NotEmpty, NotFound
803+from nova.objectstore import s3server
804+
805+
806+FLAGS = flags.FLAGS
807+
808+# Create a unique temporary directory. We don't delete after test to
809+# allow checking the contents after running tests. Users and/or tools
810+# running the tests need to remove the tests directories.
811+OSS_TEMPDIR = tempfile.mkdtemp(prefix='test_oss-')
812+
813+# Create bucket/images path
814+os.makedirs(os.path.join(OSS_TEMPDIR, 'images'))
815+os.makedirs(os.path.join(OSS_TEMPDIR, 'buckets'))
816+
817+
818+class S3APITestCase(test.TestCase):
819+ """Test objectstore through S3 API."""
820+
821+ def setUp(self):
822+ """Setup users, projects, and start a test server."""
823+ super(S3APITestCase, self).setUp()
824+ self.flags(auth_driver='nova.auth.ldapdriver.FakeLdapDriver',
825+ buckets_path=os.path.join(OSS_TEMPDIR, 'buckets'),
826+ s3_host='127.0.0.1')
827+
828+ self.auth_manager = manager.AuthManager()
829+ self.admin_user = self.auth_manager.create_user('admin', admin=True)
830+ self.admin_project = self.auth_manager.create_project('admin',
831+ self.admin_user)
832+
833+ shutil.rmtree(FLAGS.buckets_path)
834+ os.mkdir(FLAGS.buckets_path)
835+
836+ router = s3server.S3Application(FLAGS.buckets_path)
837+ server = wsgi.Server()
838+ server.start(router, FLAGS.s3_port, host=FLAGS.s3_host)
839+
840+ if not boto.config.has_section('Boto'):
841+ boto.config.add_section('Boto')
842+ boto.config.set('Boto', 'num_retries', '0')
843+ conn = s3.S3Connection(aws_access_key_id=self.admin_user.access,
844+ aws_secret_access_key=self.admin_user.secret,
845+ host=FLAGS.s3_host,
846+ port=FLAGS.s3_port,
847+ is_secure=False,
848+ calling_format=s3.OrdinaryCallingFormat())
849+ self.conn = conn
850+
851+ def get_http_connection(host, is_secure):
852+ """Get a new S3 connection, don't attempt to reuse connections."""
853+ return self.conn.new_http_connection(host, is_secure)
854+
855+ self.conn.get_http_connection = get_http_connection
856+
857+ def _ensure_no_buckets(self, buckets): # pylint: disable-msg=C0111
858+ self.assertEquals(len(buckets), 0, "Bucket list was not empty")
859+ return True
860+
861+ def _ensure_one_bucket(self, buckets, name): # pylint: disable-msg=C0111
862+ self.assertEquals(len(buckets), 1,
863+ "Bucket list didn't have exactly one element in it")
864+ self.assertEquals(buckets[0].name, name, "Wrong name")
865+ return True
866+
867+ def test_000_list_buckets(self):
868+ """Make sure we are starting with no buckets."""
869+ self._ensure_no_buckets(self.conn.get_all_buckets())
870+
871+ def test_001_create_and_delete_bucket(self):
872+ """Test bucket creation and deletion."""
873+ bucket_name = 'testbucket'
874+
875+ self.conn.create_bucket(bucket_name)
876+ self._ensure_one_bucket(self.conn.get_all_buckets(), bucket_name)
877+ self.conn.delete_bucket(bucket_name)
878+ self._ensure_no_buckets(self.conn.get_all_buckets())
879+
880+ def test_002_create_bucket_and_key_and_delete_key_again(self):
881+ """Test key operations on buckets."""
882+ bucket_name = 'testbucket'
883+ key_name = 'somekey'
884+ key_contents = 'somekey'
885+
886+ b = self.conn.create_bucket(bucket_name)
887+ k = b.new_key(key_name)
888+ k.set_contents_from_string(key_contents)
889+
890+ bucket = self.conn.get_bucket(bucket_name)
891+
892+ # make sure the contents are correct
893+ key = bucket.get_key(key_name)
894+ self.assertEquals(key.get_contents_as_string(), key_contents,
895+ "Bad contents")
896+
897+ # delete the key
898+ key.delete()
899+
900+ self._ensure_no_buckets(bucket.get_all_keys())
901+
902+ def test_unknown_bucket(self):
903+ bucket_name = 'falalala'
904+ self.assertRaises(boto_exception.S3ResponseError,
905+ self.conn.get_bucket,
906+ bucket_name)
907+
908+ def tearDown(self):
909+ """Tear down auth and test server."""
910+ self.auth_manager.delete_user('admin')
911+ self.auth_manager.delete_project('admin')
912+ super(S3APITestCase, self).tearDown()