Merge lp:~termie/nova/eventlet_objectstore into lp:~hudson-openstack/nova/trunk
- eventlet_objectstore
- Merge into trunk
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 | ||||
Related bugs: |
|
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.
Commit message
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.
- 760. By Rick Harris
-
Use %s for instance-delete logging in case instance_id comes through as a string.
- 761. By Todd Willey
-
Fix renaming of instance fields using update_instance api method.
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?
- 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 1extended 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 NovaUnauthorize dError( )
elif e.status == 503:
raise NovaUnavailable Error(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 availableRather than:
> euca-attach-volume -i i-1 vol-2 -d /dev/vdb
> Unknown: Unknown: Volume status must be availableThough 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_extension
s_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/hyperviso r-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 retrivalDocumentation: 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
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() |
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.