Merge lp:~citrix-openstack/nova/xenapi-netinject-prop into lp:~citrix-openstack/nova/xenapi

Proposed by Salvatore Orlando
Status: Merged
Merged at revision: 450
Proposed branch: lp:~citrix-openstack/nova/xenapi-netinject-prop
Merge into: lp:~citrix-openstack/nova/xenapi
Diff against target: 32984 lines (+24930/-2617) (has conflicts)
177 files modified
.mailmap (+6/-1)
Authors (+16/-2)
MANIFEST.in (+1/-0)
bin/nova-ajax-console-proxy (+1/-1)
bin/nova-api (+1/-1)
bin/nova-dhcpbridge (+1/-1)
bin/nova-direct-api (+50/-8)
bin/nova-instancemonitor (+1/-1)
bin/nova-manage (+469/-28)
bin/nova-objectstore (+9/-6)
bin/stack (+12/-2)
contrib/boto_v6/ec2/connection.py (+100/-0)
contrib/nova.sh (+1/-0)
doc/source/_static/tweaks.css (+147/-0)
doc/source/_theme/layout.html (+10/-1)
doc/source/vmwareapi_readme.rst (+218/-0)
etc/api-paste.ini (+18/-8)
nova/api/direct.py (+50/-0)
nova/api/ec2/__init__.py (+15/-4)
nova/api/ec2/admin.py (+119/-1)
nova/api/ec2/cloud.py (+52/-9)
nova/api/openstack/__init__.py (+61/-5)
nova/api/openstack/accounts.py (+174/-85)
nova/api/openstack/auth.py (+9/-0)
nova/api/openstack/common.py (+88/-0)
nova/api/openstack/extensions.py (+369/-0)
nova/api/openstack/faults.py (+48/-3)
nova/api/openstack/flavors.py (+20/-0)
nova/api/openstack/images.py (+124/-25)
nova/api/openstack/limits.py (+358/-0)
nova/api/openstack/server_metadata.py (+78/-0)
nova/api/openstack/servers.py (+490/-97)
nova/api/openstack/users.py (+107/-0)
nova/api/openstack/views/addresses.py (+42/-0)
nova/api/openstack/views/flavors.py (+34/-0)
nova/api/openstack/views/images.py (+34/-0)
nova/api/openstack/views/servers.py (+125/-0)
nova/api/openstack/zones.py (+104/-0)
nova/auth/dbdriver.py (+2/-0)
nova/auth/fakeldap.py (+5/-5)
nova/auth/ldapdriver.py (+3/-1)
nova/auth/manager.py (+11/-2)
nova/compute/api.py (+262/-63)
nova/compute/manager.py (+503/-8)
nova/compute/power_state.py (+24/-0)
nova/console/manager.py (+1/-1)
nova/console/vmrc.py (+144/-0)
nova/console/vmrc_manager.py (+158/-0)
nova/console/xvp.py (+0/-4)
nova/crypto.py (+9/-0)
nova/db/api.py (+205/-6)
nova/db/base.py (+1/-1)
nova/db/sqlalchemy/api.py (+485/-33)
nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py (+90/-0)
nova/db/sqlalchemy/migrate_repo/versions/011_live_migration.py (+83/-0)
nova/db/sqlalchemy/migrate_repo/versions/012_add_ipv6_flatmanager.py (+154/-0)
nova/db/sqlalchemy/migrate_repo/versions/013_add_flavors_to_migrations.py (+50/-0)
nova/db/sqlalchemy/models.py (+74/-14)
nova/exception.py (+1/-1)
nova/flags.py (+12/-0)
nova/image/glance.py (+172/-9)
nova/image/local.py (+33/-0)
nova/image/service.py (+88/-8)
nova/manager.py (+30/-1)
nova/network/linux_net.py (+459/-59)
nova/network/manager.py (+60/-5)
nova/network/vmwareapi_net.py (+91/-0)
nova/objectstore/handler.py (+0/-478)
nova/objectstore/s3server.py (+335/-0)
nova/objectstore/stored.py (+0/-63)
nova/quota.py (+57/-13)
nova/rpc.py (+64/-23)
nova/scheduler/api.py (+244/-0)
nova/scheduler/driver.py (+244/-0)
nova/scheduler/manager.py (+87/-8)
nova/scheduler/zone_manager.py (+179/-0)
nova/service.py (+12/-3)
nova/test.py (+117/-8)
nova/tests/api/openstack/__init__.py (+1/-1)
nova/tests/api/openstack/extensions/foxinsocks.py (+98/-0)
nova/tests/api/openstack/fakes.py (+196/-37)
nova/tests/api/openstack/test_accounts.py (+126/-0)
nova/tests/api/openstack/test_adminapi.py (+0/-1)
nova/tests/api/openstack/test_auth.py (+77/-19)
nova/tests/api/openstack/test_extensions.py (+236/-0)
nova/tests/api/openstack/test_flavors.py (+96/-3)
nova/tests/api/openstack/test_images.py (+227/-101)
nova/tests/api/openstack/test_limits.py (+513/-30)
nova/tests/api/openstack/test_server_metadata.py (+164/-0)
nova/tests/api/openstack/test_servers.py (+940/-13)
nova/tests/api/openstack/test_users.py (+162/-0)
nova/tests/api/openstack/test_zones.py (+195/-0)
nova/tests/api/test_wsgi.py (+122/-0)
nova/tests/db/fakes.py (+94/-8)
nova/tests/fake_utils.py (+106/-0)
nova/tests/hyperv_unittest.py (+1/-1)
nova/tests/image/__init__.py (+16/-0)
nova/tests/image/test_glance.py (+191/-0)
nova/tests/integrated/__init__.py (+20/-0)
nova/tests/integrated/api/__init__.py (+20/-0)
nova/tests/integrated/api/client.py (+210/-0)
nova/tests/integrated/integrated_helpers.py (+146/-0)
nova/tests/integrated/test_login.py (+79/-0)
nova/tests/network/__init__.py (+67/-0)
nova/tests/network/base.py (+154/-0)
nova/tests/test_api.py (+18/-2)
nova/tests/test_auth.py (+7/-0)
nova/tests/test_cloud.py (+21/-14)
nova/tests/test_compute.py (+392/-2)
nova/tests/test_direct.py (+28/-0)
nova/tests/test_flat_network.py (+161/-0)
nova/tests/test_localization.py (+4/-3)
nova/tests/test_middleware.py (+2/-2)
nova/tests/test_misc.py (+98/-6)
nova/tests/test_network.py (+147/-5)
nova/tests/test_objectstore.py (+50/-211)
nova/tests/test_quota.py (+97/-12)
nova/tests/test_rpc.py (+2/-2)
nova/tests/test_scheduler.py (+724/-1)
nova/tests/test_service.py (+60/-9)
nova/tests/test_test.py (+43/-0)
nova/tests/test_utils.py (+429/-174)
nova/tests/test_virt.py (+320/-9)
nova/tests/test_vlan_network.py (+242/-0)
nova/tests/test_vmwareapi.py (+252/-0)
nova/tests/test_volume.py (+196/-0)
nova/tests/test_xenapi.py (+354/-18)
nova/tests/test_zones.py (+381/-172)
nova/tests/vmwareapi/__init__.py (+21/-0)
nova/tests/vmwareapi/db_fakes.py (+109/-0)
nova/tests/vmwareapi/stubs.py (+46/-0)
nova/tests/xenapi/stubs.py (+124/-16)
nova/utils.py (+285/-7)
nova/virt/connection.py (+7/-2)
nova/virt/cpuinfo.xml.template (+9/-0)
nova/virt/disk.py (+37/-10)
nova/virt/driver.py (+234/-0)
nova/virt/fake.py (+51/-11)
nova/virt/hyperv.py (+20/-2)
nova/virt/interfaces.template (+17/-10)
nova/virt/libvirt.xml.template (+12/-9)
nova/virt/libvirt_conn.py (+773/-77)
nova/virt/vmwareapi/__init__.py (+19/-0)
nova/virt/vmwareapi/error_util.py (+96/-0)
nova/virt/vmwareapi/fake.py (+711/-0)
nova/virt/vmwareapi/io_util.py (+168/-0)
nova/virt/vmwareapi/network_utils.py (+149/-0)
nova/virt/vmwareapi/read_write_util.py (+182/-0)
nova/virt/vmwareapi/vim.py (+176/-0)
nova/virt/vmwareapi/vim_util.py (+217/-0)
nova/virt/vmwareapi/vm_util.py (+306/-0)
nova/virt/vmwareapi/vmops.py (+789/-0)
nova/virt/vmwareapi/vmware_images.py (+201/-0)
nova/virt/vmwareapi_conn.py (+375/-0)
nova/virt/xenapi/fake.py (+53/-9)
nova/virt/xenapi/vm_utils.py (+204/-15)
nova/virt/xenapi/vmops.py (+630/-58)
nova/virt/xenapi_conn.py (+98/-19)
nova/volume/api.py (+2/-1)
nova/volume/driver.py (+206/-1)
nova/volume/manager.py (+10/-3)
plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py (+81/-11)
plugins/xenserver/xenapi/etc/xapi.d/plugins/glance (+279/-0)
plugins/xenserver/xenapi/etc/xapi.d/plugins/migration (+121/-0)
po/nova.pot (+733/-365)
pylintrc (+12/-1)
run_tests.py (+2/-0)
smoketests/base.py (+37/-5)
smoketests/proxy.sh (+32/-0)
smoketests/public_network_smoketests.py (+8/-6)
smoketests/run_tests.py (+310/-0)
smoketests/test_admin.py (+0/-7)
smoketests/test_netadmin.py (+186/-0)
smoketests/test_sysadmin.py (+49/-9)
tools/esx/guest_tool.py (+345/-0)
tools/euca-get-ajax-console (+1/-1)
tools/pip-requires (+1/-0)
Text conflict in .mailmap
Text conflict in Authors
Text conflict in bin/nova-direct-api
Text conflict in bin/nova-manage
Text conflict in nova/api/direct.py
Text conflict in nova/api/ec2/__init__.py
Text conflict in nova/api/ec2/admin.py
Text conflict in nova/api/ec2/cloud.py
Text conflict in nova/api/openstack/__init__.py
Text conflict in nova/api/openstack/accounts.py
Text conflict in nova/api/openstack/auth.py
Text conflict in nova/api/openstack/common.py
Text conflict in nova/api/openstack/faults.py
Text conflict in nova/api/openstack/flavors.py
Text conflict in nova/api/openstack/images.py
Text conflict in nova/api/openstack/servers.py
Text conflict in nova/api/openstack/users.py
Text conflict in nova/api/openstack/zones.py
Text conflict in nova/compute/api.py
Text conflict in nova/compute/manager.py
Text conflict in nova/compute/power_state.py
Text conflict in nova/crypto.py
Text conflict in nova/db/api.py
Text conflict in nova/db/sqlalchemy/api.py
Text conflict in nova/db/sqlalchemy/migrate_repo/versions/008_add_instance_types.py
Text conflict in nova/db/sqlalchemy/models.py
Text conflict in nova/flags.py
Text conflict in nova/image/glance.py
Text conflict in nova/image/local.py
Text conflict in nova/image/service.py
Text conflict in nova/network/linux_net.py
Text conflict in nova/network/manager.py
Contents conflict in nova/objectstore/bucket.py
Contents conflict in nova/objectstore/image.py
Text conflict in nova/quota.py
Text conflict in nova/scheduler/api.py
Text conflict in nova/scheduler/manager.py
Text conflict in nova/scheduler/zone_manager.py
Text conflict in nova/test.py
Text conflict in nova/tests/api/openstack/fakes.py
Text conflict in nova/tests/api/openstack/test_accounts.py
Text conflict in nova/tests/api/openstack/test_auth.py
Text conflict in nova/tests/api/openstack/test_flavors.py
Text conflict in nova/tests/api/openstack/test_images.py
Text conflict in nova/tests/api/openstack/test_limits.py
Text conflict in nova/tests/api/openstack/test_servers.py
Text conflict in nova/tests/api/openstack/test_users.py
Text conflict in nova/tests/api/openstack/test_zones.py
Text conflict in nova/tests/api/test_wsgi.py
Text conflict in nova/tests/db/fakes.py
Text conflict in nova/tests/test_api.py
Text conflict in nova/tests/test_cloud.py
Text conflict in nova/tests/test_compute.py
Text conflict in nova/tests/test_direct.py
Text conflict in nova/tests/test_misc.py
Text conflict in nova/tests/test_network.py
Text conflict in nova/tests/test_objectstore.py
Text conflict in nova/tests/test_quota.py
Text conflict in nova/tests/test_test.py
Text conflict in nova/tests/test_utils.py
Text conflict in nova/tests/test_virt.py
Text conflict in nova/tests/test_xenapi.py
Text conflict in nova/tests/test_zones.py
Text conflict in nova/tests/xenapi/stubs.py
Text conflict in nova/utils.py
Text conflict in nova/virt/disk.py
Text conflict in nova/virt/libvirt_conn.py
Text conflict in nova/virt/xenapi/fake.py
Text conflict in nova/virt/xenapi/vm_utils.py
Text conflict in nova/virt/xenapi/vmops.py
Text conflict in nova/virt/xenapi_conn.py
Text conflict in nova/volume/driver.py
Text conflict in plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py
Text conflict in plugins/xenserver/xenapi/etc/xapi.d/plugins/glance
Text conflict in plugins/xenserver/xenapi/etc/xapi.d/plugins/migration
Text conflict in po/nova.pot
Text conflict in smoketests/base.py
Path conflict: smoketests/netadmin_smoketests.py / smoketests/test_netadmin.py
Text conflict in smoketests/proxy.sh
Path conflict: smoketests/sysadmin_smoketests.py / smoketests/test_sysadmin.py
Text conflict in smoketests/test_netadmin.py
Text conflict in smoketests/test_sysadmin.py
To merge this branch: bzr merge lp:~citrix-openstack/nova/xenapi-netinject-prop
Reviewer Review Type Date Requested Status
Armando Migliaccio (community) Needs Information
Review via email: mp+52437@code.launchpad.net

Commit message

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
NOVA-CORE DEVELOPERS SHOULD NOT REVIEW THIS MERGE PROPOSAL
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This is for Citrix OpenStack team only. We propose for merge into a cache of lp:nova to generate diffs for our internal peer review.

Description of the change

Proposing a new approach for _get_vm_opaqueref:

1) assume instance_or_vm parameter is an instance object, try to fetch name attribute
2) if AttributeError or KeyError is raised, check if instance_or_vm is integer.
2.a) if yes, it could be an instance id. Fetch instance from database; use instance name for looking up VM.
2.b) if no, it could be an instance name. Use it for looking up VM.
3) lookup VM on XenServer backend
4) check result of lookup operation
4.a) if lookup succeeds (vm!=None) return VM ref.
4.b) if lookup fails, instance_or_vm might already be a VM ref. To this aim, try to fetch VM record using instance_or_vm as vm_ref. If the operation succeeds return instance_or_vm as VM ref.
5) if none of the above has succeeded, raise Exception.

To post a comment you must log in.
697. By Salvatore Orlando

merge trunk

now using refactored _get_vm_opaqueref

698. By Salvatore Orlando

using get_uuid in place of get_record in _get_vm_opaqueref
changed SessionBase._getter in fake xenapi in order to return HANDLE_INVALID failure when reference is not in DB
(was NotImplementedException)

Revision history for this message
Armando Migliaccio (armando-migliaccio) wrote :

does this branch depends on another branch? I see a lot of new stuff that does not seem related to network injection

review: Needs Information
Revision history for this message
Salvatore Orlando (salvatore-orlando) wrote :

> does this branch depends on another branch? I see a lot of new stuff that does
> not seem related to network injection

nova/xenapi is not synchronized with trunk. I'll do that now.

699. By Salvatore Orlando

Merge trunk
Conflicts solved

700. By Salvatore Orlando

Merged with trunk

Fixed testing instrastructure:
 - stubbed out LoopingCall.start
 - unstubbed db.instance_create
 - now using fake context in spawn tests
 - moved fake xenstore ops into fake driver to avoid code duplication

Fixed pep8 errors

701. By Salvatore Orlando

merge trunk

702. By Salvatore Orlando

Removed excess LOG.debug line

703. By Salvatore Orlando

merge trunk

704. By Salvatore Orlando

merge trunk

705. By Salvatore Orlando

Merge trunk

706. By Salvatore Orlando

merge trunk

707. By Salvatore Orlando

merge trunk

708. By Salvatore Orlando

merge trunk

709. By Salvatore Orlando

Fixed issue arisen from recent feature update (utils.execute)

710. By Salvatore Orlando

merge trunk

711. By Salvatore Orlando

merge trunk

712. By Salvatore Orlando

merged with trunk
Updated xenapi network injection for IPv6
Updated unit tests

713. By Salvatore Orlando

Addressing Rick Clark's comments.

714. By Salvatore Orlando

merge trunk

715. By Salvatore Orlando

merge trunk

716. By Salvatore Orlando

Sorted out a problem occurred with units tests for VM migration

717. By Salvatore Orlando

merge trunk

718. By Salvatore Orlando

Stubbing out utils.execute for migrate tests

719. By Salvatore Orlando

Addressing Trey's comments.
Removed disk_get_injectables, using _get_network_info's return value.

720. By Salvatore Orlando

Merged with trunk
Updated net injection for xenapi reflecting recent changes for libvirt

721. By Salvatore Orlando

merge trunk

722. By Salvatore Orlando

minor pep8 fix in db/fakes.py

723. By Salvatore Orlando

merge trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.mailmap'
2--- .mailmap 2011-03-03 05:10:42 +0000
3+++ .mailmap 2011-03-25 13:43:55 +0000
4@@ -28,7 +28,12 @@
5 <matt.dietz@rackspace.com> <matthewdietz@Matthew-Dietzs-MacBook-Pro.local>
6 <matt.dietz@rackspace.com> <mdietz@openstack>
7 <mordred@inaugust.com> <mordred@hudson>
8-<paul@openstack.org> <paul.voccio@rackspace.com>
9+<<<<<<< TREE
10+<paul@openstack.org> <paul.voccio@rackspace.com>
11+=======
12+<nirmal.ranganathan@rackspace.com> <nirmal.ranganathan@rackspace.coom>
13+<paul@openstack.org> <paul.voccio@rackspace.com>
14+>>>>>>> MERGE-SOURCE
15 <paul@openstack.org> <pvoccio@castor.local>
16 <rconradharris@gmail.com> <rick.harris@rackspace.com>
17 <rlane@wikimedia.org> <laner@controller>
18
19=== modified file 'Authors'
20--- Authors 2011-03-03 05:10:42 +0000
21+++ Authors 2011-03-25 13:43:55 +0000
22@@ -1,4 +1,5 @@
23 Andy Smith <code@term.ie>
24+Andy Southgate <andy.southgate@citrix.com>
25 Anne Gentle <anne@openstack.org>
26 Anthony Young <sleepsonthefloor@gmail.com>
27 Antony Messerli <ant@openstack.org>
28@@ -19,7 +20,9 @@
29 Ed Leafe <ed@leafe.com>
30 Eldar Nugaev <enugaev@griddynamics.com>
31 Eric Day <eday@oddments.org>
32+Eric Windisch <eric@cloudscaling.com>
33 Ewan Mellor <ewan.mellor@citrix.com>
34+Gabe Westmaas <gabe.westmaas@rackspace.com>
35 Hisaharu Ishii <ishii.hisaharu@lab.ntt.co.jp>
36 Hisaki Ohara <hisaki.ohara@intel.com>
37 Ilya Alekseyev <ialekseev@griddynamics.com>
38@@ -31,7 +34,12 @@
39 Jonathan Bryce <jbryce@jbryce.com>
40 Jordan Rinke <jordan@openstack.org>
41 Josh Durgin <joshd@hq.newdream.net>
42-Josh Kearney <josh@jk0.org>
43+<<<<<<< TREE
44+Josh Kearney <josh@jk0.org>
45+=======
46+Josh Kearney <josh@jk0.org>
47+Josh Kleinpeter <josh@kleinpeter.org>
48+>>>>>>> MERGE-SOURCE
49 Joshua McKenty <jmckenty@gmail.com>
50 Justin Santa Barbara <justin@fathomdb.com>
51 Kei Masumoto <masumotok@nttdata.co.jp>
52@@ -39,7 +47,12 @@
53 Kevin L. Mitchell <kevin.mitchell@rackspace.com>
54 Koji Iida <iida.koji@lab.ntt.co.jp>
55 Lorin Hochstein <lorin@isi.edu>
56-Masanori Itoh <itoumsn@nttdata.co.jp>
57+<<<<<<< TREE
58+Masanori Itoh <itoumsn@nttdata.co.jp>
59+=======
60+Mark Washenberger <mark.washenberger@rackspace.com>
61+Masanori Itoh <itoumsn@nttdata.co.jp>
62+>>>>>>> MERGE-SOURCE
63 Matt Dietz <matt.dietz@rackspace.com>
64 Michael Gundlach <michael.gundlach@rackspace.com>
65 Monsyne Dragon <mdragon@rackspace.com>
66@@ -58,6 +71,7 @@
67 Ryan Lucio <rlucio@internap.com>
68 Salvatore Orlando <salvatore.orlando@eu.citrix.com>
69 Sandy Walsh <sandy.walsh@rackspace.com>
70+Sateesh Chodapuneedi <sateesh.chodapuneedi@citrix.com>
71 Soren Hansen <soren.hansen@rackspace.com>
72 Thierry Carrez <thierry@openstack.org>
73 Todd Willey <todd@ansolabs.com>
74
75=== modified file 'MANIFEST.in'
76--- MANIFEST.in 2011-02-24 14:19:29 +0000
77+++ MANIFEST.in 2011-03-25 13:43:55 +0000
78@@ -25,6 +25,7 @@
79 include nova/db/sqlalchemy/migrate_repo/README
80 include nova/virt/interfaces.template
81 include nova/virt/libvirt*.xml.template
82+include nova/virt/cpuinfo.xml.template
83 include nova/tests/CA/
84 include nova/tests/CA/cacert.pem
85 include nova/tests/CA/private/
86
87=== modified file 'bin/nova-ajax-console-proxy'
88--- bin/nova-ajax-console-proxy 2011-02-25 21:15:51 +0000
89+++ bin/nova-ajax-console-proxy 2011-03-25 13:43:55 +0000
90@@ -1,5 +1,5 @@
91 #!/usr/bin/env python
92-# pylint: disable-msg=C0103
93+# pylint: disable=C0103
94 # vim: tabstop=4 shiftwidth=4 softtabstop=4
95
96 # Copyright 2010 United States Government as represented by the
97
98=== modified file 'bin/nova-api'
99--- bin/nova-api 2011-03-09 00:45:20 +0000
100+++ bin/nova-api 2011-03-25 13:43:55 +0000
101@@ -1,5 +1,5 @@
102 #!/usr/bin/env python
103-# pylint: disable-msg=C0103
104+# pylint: disable=C0103
105 # vim: tabstop=4 shiftwidth=4 softtabstop=4
106
107 # Copyright 2010 United States Government as represented by the
108
109=== modified file 'bin/nova-dhcpbridge'
110--- bin/nova-dhcpbridge 2011-02-23 19:20:52 +0000
111+++ bin/nova-dhcpbridge 2011-03-25 13:43:55 +0000
112@@ -94,7 +94,7 @@
113 """Get the list of hosts for an interface."""
114 ctxt = context.get_admin_context()
115 network_ref = db.network_get_by_bridge(ctxt, interface)
116- return linux_net.get_dhcp_hosts(ctxt, network_ref['id'])
117+ return linux_net.get_dhcp_leases(ctxt, network_ref['id'])
118
119
120 def main():
121
122=== modified file 'bin/nova-direct-api'
123--- bin/nova-direct-api 2011-02-23 23:26:52 +0000
124+++ bin/nova-direct-api 2011-03-25 13:43:55 +0000
125@@ -1,5 +1,5 @@
126 #!/usr/bin/env python
127-# pylint: disable-msg=C0103
128+# pylint: disable=C0103
129 # vim: tabstop=4 shiftwidth=4 softtabstop=4
130
131 # Copyright 2010 United States Government as represented by the
132@@ -34,29 +34,71 @@
133
134 gettext.install('nova', unicode=1)
135
136+from nova import compute
137 from nova import flags
138-from nova import log as logging
139+<<<<<<< TREE
140+from nova import log as logging
141+=======
142+from nova import log as logging
143+from nova import network
144+>>>>>>> MERGE-SOURCE
145 from nova import utils
146+from nova import volume
147 from nova import wsgi
148 from nova.api import direct
149-from nova.compute import api as compute_api
150
151
152 FLAGS = flags.FLAGS
153 flags.DEFINE_integer('direct_port', 8001, 'Direct API port')
154 flags.DEFINE_string('direct_host', '0.0.0.0', 'Direct API host')
155-flags.DEFINE_flag(flags.HelpFlag())
156-flags.DEFINE_flag(flags.HelpshortFlag())
157-flags.DEFINE_flag(flags.HelpXMLFlag())
158-
159+<<<<<<< TREE
160+flags.DEFINE_flag(flags.HelpFlag())
161+flags.DEFINE_flag(flags.HelpshortFlag())
162+flags.DEFINE_flag(flags.HelpXMLFlag())
163+
164+=======
165+flags.DEFINE_flag(flags.HelpFlag())
166+flags.DEFINE_flag(flags.HelpshortFlag())
167+flags.DEFINE_flag(flags.HelpXMLFlag())
168+
169+
170+# An example of an API that only exposes read-only methods.
171+# In this case we're just limiting which methods are exposed.
172+class ReadOnlyCompute(direct.Limited):
173+ """Read-only Compute API."""
174+
175+ _allowed = ['get', 'get_all', 'get_console_output']
176+
177+
178+# An example of an API that provides a backwards compatibility layer.
179+# In this case we're overwriting the implementation to ensure
180+# compatibility with an older version. In reality we would want the
181+# "description=None" to be part of the actual API so that code
182+# like this isn't even necessary, but this example shows what one can
183+# do if that isn't the situation.
184+class VolumeVersionOne(direct.Limited):
185+ _allowed = ['create', 'delete', 'update', 'get']
186+
187+ def create(self, context, size, name):
188+ self.proxy.create(context, size, name, description=None)
189+
190+>>>>>>> MERGE-SOURCE
191
192 if __name__ == '__main__':
193 utils.default_flagfile()
194 FLAGS(sys.argv)
195 logging.setup()
196
197- direct.register_service('compute', compute_api.API())
198+ direct.register_service('compute', compute.API())
199+ direct.register_service('volume', volume.API())
200+ direct.register_service('network', network.API())
201 direct.register_service('reflect', direct.Reflection())
202+
203+ # Here is how we could expose the code in the examples above.
204+ #direct.register_service('compute-readonly',
205+ # ReadOnlyCompute(compute.API()))
206+ #direct.register_service('volume-v1', VolumeVersionOne(volume.API()))
207+
208 router = direct.Router()
209 with_json = direct.JsonParamsMiddleware(router)
210 with_req = direct.PostParamsMiddleware(with_json)
211
212=== modified file 'bin/nova-instancemonitor'
213--- bin/nova-instancemonitor 2011-02-21 21:46:41 +0000
214+++ bin/nova-instancemonitor 2011-03-25 13:43:55 +0000
215@@ -50,7 +50,7 @@
216
217 if __name__ == '__builtin__':
218 LOG.warn(_('Starting instance monitor'))
219- # pylint: disable-msg=C0103
220+ # pylint: disable=C0103
221 monitor = monitor.InstanceMonitor()
222
223 # This is the parent service that twistd will be looking for when it
224
225=== modified file 'bin/nova-manage'
226--- bin/nova-manage 2011-03-13 09:49:56 +0000
227+++ bin/nova-manage 2011-03-25 13:43:55 +0000
228@@ -96,10 +96,18 @@
229 flags.DECLARE('vlan_start', 'nova.network.manager')
230 flags.DECLARE('vpn_start', 'nova.network.manager')
231 flags.DECLARE('fixed_range_v6', 'nova.network.manager')
232-flags.DECLARE('images_path', 'nova.image.local')
233-flags.DEFINE_flag(flags.HelpFlag())
234-flags.DEFINE_flag(flags.HelpshortFlag())
235-flags.DEFINE_flag(flags.HelpXMLFlag())
236+<<<<<<< TREE
237+flags.DECLARE('images_path', 'nova.image.local')
238+flags.DEFINE_flag(flags.HelpFlag())
239+flags.DEFINE_flag(flags.HelpshortFlag())
240+flags.DEFINE_flag(flags.HelpXMLFlag())
241+=======
242+flags.DECLARE('images_path', 'nova.image.local')
243+flags.DECLARE('libvirt_type', 'nova.virt.libvirt_conn')
244+flags.DEFINE_flag(flags.HelpFlag())
245+flags.DEFINE_flag(flags.HelpshortFlag())
246+flags.DEFINE_flag(flags.HelpXMLFlag())
247+>>>>>>> MERGE-SOURCE
248
249
250 def param2id(object_id):
251@@ -437,6 +445,7 @@
252 "been created.\nPlease create a database by running a "
253 "nova-api server on this host.")
254
255+<<<<<<< TREE
256 AccountCommands = ProjectCommands
257
258
259@@ -470,6 +479,46 @@
260 fixed_ip['address'],
261 mac_address, hostname, host)
262
263+=======
264+AccountCommands = ProjectCommands
265+
266+
267+class FixedIpCommands(object):
268+ """Class for managing fixed ip."""
269+
270+ def list(self, host=None):
271+ """Lists all fixed ips (optionally by host) arguments: [host]"""
272+ ctxt = context.get_admin_context()
273+
274+ try:
275+ if host == None:
276+ fixed_ips = db.fixed_ip_get_all(ctxt)
277+ else:
278+ fixed_ips = db.fixed_ip_get_all_by_host(ctxt, host)
279+ except exception.NotFound as ex:
280+ print "error: %s" % ex
281+ sys.exit(2)
282+
283+ print "%-18s\t%-15s\t%-17s\t%-15s\t%s" % (_('network'),
284+ _('IP address'),
285+ _('MAC address'),
286+ _('hostname'),
287+ _('host'))
288+ for fixed_ip in fixed_ips:
289+ hostname = None
290+ host = None
291+ mac_address = None
292+ if fixed_ip['instance']:
293+ instance = fixed_ip['instance']
294+ hostname = instance['hostname']
295+ host = instance['host']
296+ mac_address = instance['mac_address']
297+ print "%-18s\t%-15s\t%-17s\t%-15s\t%s" % (
298+ fixed_ip['network']['cidr'],
299+ fixed_ip['address'],
300+ mac_address, hostname, host)
301+
302+>>>>>>> MERGE-SOURCE
303
304 class FloatingIpCommands(object):
305 """Class for managing floating ip."""
306@@ -513,11 +562,12 @@
307 network_size=None, vlan_start=None,
308 vpn_start=None, fixed_range_v6=None, label='public'):
309 """Creates fixed ips for host by range
310- arguments: [fixed_range=FLAG], [num_networks=FLAG],
311+ arguments: fixed_range=FLAG, [num_networks=FLAG],
312 [network_size=FLAG], [vlan_start=FLAG],
313 [vpn_start=FLAG], [fixed_range_v6=FLAG]"""
314 if not fixed_range:
315- fixed_range = FLAGS.fixed_range
316+ raise TypeError(_('Fixed range in the form of 10.0.0.0/8 is '
317+ 'required to create networks.'))
318 if not num_networks:
319 num_networks = FLAGS.num_networks
320 if not network_size:
321@@ -536,28 +586,89 @@
322 vlan_start=int(vlan_start),
323 vpn_start=int(vpn_start),
324 cidr_v6=fixed_range_v6,
325- label=label)
326-
327- def list(self):
328- """List all created networks"""
329- print "%-18s\t%-15s\t%-15s\t%-15s" % (_('network'),
330- _('netmask'),
331- _('start address'),
332- 'DNS')
333- for network in db.network_get_all(context.get_admin_context()):
334- print "%-18s\t%-15s\t%-15s\t%-15s" % (network.cidr,
335- network.netmask,
336- network.dhcp_start,
337- network.dns)
338-
339- def delete(self, fixed_range):
340- """Deletes a network"""
341- network = db.network_get_by_cidr(context.get_admin_context(), \
342- fixed_range)
343- if network.project_id is not None:
344- raise ValueError(_('Network must be disassociated from project %s'
345- ' before delete' % network.project_id))
346- db.network_delete_safe(context.get_admin_context(), network.id)
347+<<<<<<< TREE
348+ label=label)
349+
350+ def list(self):
351+ """List all created networks"""
352+ print "%-18s\t%-15s\t%-15s\t%-15s" % (_('network'),
353+ _('netmask'),
354+ _('start address'),
355+ 'DNS')
356+ for network in db.network_get_all(context.get_admin_context()):
357+ print "%-18s\t%-15s\t%-15s\t%-15s" % (network.cidr,
358+ network.netmask,
359+ network.dhcp_start,
360+ network.dns)
361+
362+ def delete(self, fixed_range):
363+ """Deletes a network"""
364+ network = db.network_get_by_cidr(context.get_admin_context(), \
365+ fixed_range)
366+ if network.project_id is not None:
367+ raise ValueError(_('Network must be disassociated from project %s'
368+ ' before delete' % network.project_id))
369+ db.network_delete_safe(context.get_admin_context(), network.id)
370+=======
371+ label=label)
372+
373+ def list(self):
374+ """List all created networks"""
375+ print "%-18s\t%-15s\t%-15s\t%-15s" % (_('network'),
376+ _('netmask'),
377+ _('start address'),
378+ 'DNS')
379+ for network in db.network_get_all(context.get_admin_context()):
380+ print "%-18s\t%-15s\t%-15s\t%-15s" % (network.cidr,
381+ network.netmask,
382+ network.dhcp_start,
383+ network.dns)
384+
385+ def delete(self, fixed_range):
386+ """Deletes a network"""
387+ network = db.network_get_by_cidr(context.get_admin_context(), \
388+ fixed_range)
389+ if network.project_id is not None:
390+ raise ValueError(_('Network must be disassociated from project %s'
391+ ' before delete' % network.project_id))
392+ db.network_delete_safe(context.get_admin_context(), network.id)
393+
394+
395+class VmCommands(object):
396+ """Class for mangaging VM instances."""
397+
398+ def live_migration(self, ec2_id, dest):
399+ """Migrates a running instance to a new machine.
400+
401+ :param ec2_id: instance id which comes from euca-describe-instance.
402+ :param dest: destination host name.
403+
404+ """
405+
406+ ctxt = context.get_admin_context()
407+ instance_id = ec2utils.ec2_id_to_id(ec2_id)
408+
409+ if (FLAGS.connection_type != 'libvirt' or
410+ (FLAGS.connection_type == 'libvirt' and
411+ FLAGS.libvirt_type not in ['kvm', 'qemu'])):
412+ msg = _('Only KVM and QEmu are supported for now. Sorry!')
413+ raise exception.Error(msg)
414+
415+ if (FLAGS.volume_driver != 'nova.volume.driver.AOEDriver' and \
416+ FLAGS.volume_driver != 'nova.volume.driver.ISCSIDriver'):
417+ msg = _("Support only AOEDriver and ISCSIDriver. Sorry!")
418+ raise exception.Error(msg)
419+
420+ rpc.call(ctxt,
421+ FLAGS.scheduler_topic,
422+ {"method": "live_migration",
423+ "args": {"instance_id": instance_id,
424+ "dest": dest,
425+ "topic": FLAGS.compute_topic}})
426+
427+ print _('Migration of %s initiated.'
428+ 'Check its progress using euca-describe-instances.') % ec2_id
429+>>>>>>> MERGE-SOURCE
430
431
432 class ServiceCommands(object):
433@@ -604,6 +715,59 @@
434 return
435 db.service_update(ctxt, svc['id'], {'disabled': True})
436
437+ def describe_resource(self, host):
438+ """Describes cpu/memory/hdd info for host.
439+
440+ :param host: hostname.
441+
442+ """
443+
444+ result = rpc.call(context.get_admin_context(),
445+ FLAGS.scheduler_topic,
446+ {"method": "show_host_resources",
447+ "args": {"host": host}})
448+
449+ if type(result) != dict:
450+ print _('An unexpected error has occurred.')
451+ print _('[Result]'), result
452+ else:
453+ cpu = result['resource']['vcpus']
454+ mem = result['resource']['memory_mb']
455+ hdd = result['resource']['local_gb']
456+ cpu_u = result['resource']['vcpus_used']
457+ mem_u = result['resource']['memory_mb_used']
458+ hdd_u = result['resource']['local_gb_used']
459+
460+ print 'HOST\t\t\tPROJECT\t\tcpu\tmem(mb)\tdisk(gb)'
461+ print '%s(total)\t\t\t%s\t%s\t%s' % (host, cpu, mem, hdd)
462+ print '%s(used)\t\t\t%s\t%s\t%s' % (host, cpu_u, mem_u, hdd_u)
463+ for p_id, val in result['usage'].items():
464+ print '%s\t\t%s\t\t%s\t%s\t%s' % (host,
465+ p_id,
466+ val['vcpus'],
467+ val['memory_mb'],
468+ val['local_gb'])
469+
470+ def update_resource(self, host):
471+ """Updates available vcpu/memory/disk info for host.
472+
473+ :param host: hostname.
474+
475+ """
476+
477+ ctxt = context.get_admin_context()
478+ service_refs = db.service_get_all_by_host(ctxt, host)
479+ if len(service_refs) <= 0:
480+ raise exception.Invalid(_('%s does not exist.') % host)
481+
482+ service_refs = [s for s in service_refs if s['topic'] == 'compute']
483+ if len(service_refs) <= 0:
484+ raise exception.Invalid(_('%s is not compute node.') % host)
485+
486+ rpc.call(ctxt,
487+ db.queue_get_for(ctxt, FLAGS.compute_topic, host),
488+ {"method": "update_available_resource"})
489+
490
491 class LogCommands(object):
492 def request(self, request_id, logfile='/var/log/nova.log'):
493@@ -629,6 +793,49 @@
494 print migration.db_version()
495
496
497+class InstanceCommands(object):
498+ """Class for managing instances."""
499+
500+ def list(self, host=None, instance=None):
501+ """Show a list of all instances"""
502+ print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
503+ " %-10s %-10s %-10s %-5s" % (
504+ _('instance'),
505+ _('node'),
506+ _('type'),
507+ _('state'),
508+ _('launched'),
509+ _('image'),
510+ _('kernel'),
511+ _('ramdisk'),
512+ _('project'),
513+ _('user'),
514+ _('zone'),
515+ _('index'))
516+
517+ if host == None:
518+ instances = db.instance_get_all(context.get_admin_context())
519+ else:
520+ instances = db.instance_get_all_by_host(
521+ context.get_admin_context(), host)
522+
523+ for instance in instances:
524+ print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
525+ " %-10s %-10s %-10s %-5d" % (
526+ instance['hostname'],
527+ instance['host'],
528+ instance['instance_type'],
529+ instance['state_description'],
530+ instance['launched_at'],
531+ instance['image_id'],
532+ instance['kernel_id'],
533+ instance['ramdisk_id'],
534+ instance['project_id'],
535+ instance['user_id'],
536+ instance['availability_zone'],
537+ instance['launch_index'])
538+
539+
540 class VolumeCommands(object):
541 """Methods for dealing with a cloud in an odd state"""
542
543@@ -676,6 +883,7 @@
544 "mountpoint": volume['mountpoint']}})
545
546
547+<<<<<<< TREE
548 class InstanceTypeCommands(object):
549 """Class for managing instance types / flavors."""
550
551@@ -898,6 +1106,230 @@
552 self._convert_images(machine_images)
553
554
555+=======
556+class InstanceTypeCommands(object):
557+ """Class for managing instance types / flavors."""
558+
559+ def _print_instance_types(self, n, val):
560+ deleted = ('', ', inactive')[val["deleted"] == 1]
561+ print ("%s: Memory: %sMB, VCPUS: %s, Storage: %sGB, FlavorID: %s, "
562+ "Swap: %sGB, RXTX Quota: %sGB, RXTX Cap: %sMB%s") % (
563+ n, val["memory_mb"], val["vcpus"], val["local_gb"],
564+ val["flavorid"], val["swap"], val["rxtx_quota"],
565+ val["rxtx_cap"], deleted)
566+
567+ def create(self, name, memory, vcpus, local_gb, flavorid,
568+ swap=0, rxtx_quota=0, rxtx_cap=0):
569+ """Creates instance types / flavors
570+ arguments: name memory vcpus local_gb flavorid [swap] [rxtx_quota]
571+ [rxtx_cap]
572+ """
573+ try:
574+ instance_types.create(name, memory, vcpus, local_gb,
575+ flavorid, swap, rxtx_quota, rxtx_cap)
576+ except exception.InvalidInputException:
577+ print "Must supply valid parameters to create instance type"
578+ print e
579+ sys.exit(1)
580+ except exception.DBError, e:
581+ print "DB Error: %s" % e
582+ sys.exit(2)
583+ except:
584+ print "Unknown error"
585+ sys.exit(3)
586+ else:
587+ print "%s created" % name
588+
589+ def delete(self, name, purge=None):
590+ """Marks instance types / flavors as deleted
591+ arguments: name"""
592+ try:
593+ if purge == "--purge":
594+ instance_types.purge(name)
595+ verb = "purged"
596+ else:
597+ instance_types.destroy(name)
598+ verb = "deleted"
599+ except exception.ApiError:
600+ print "Valid instance type name is required"
601+ sys.exit(1)
602+ except exception.DBError, e:
603+ print "DB Error: %s" % e
604+ sys.exit(2)
605+ except:
606+ sys.exit(3)
607+ else:
608+ print "%s %s" % (name, verb)
609+
610+ def list(self, name=None):
611+ """Lists all active or specific instance types / flavors
612+ arguments: [name]"""
613+ try:
614+ if name == None:
615+ inst_types = instance_types.get_all_types()
616+ elif name == "--all":
617+ inst_types = instance_types.get_all_types(True)
618+ else:
619+ inst_types = instance_types.get_instance_type(name)
620+ except exception.DBError, e:
621+ _db_error(e)
622+ if isinstance(inst_types.values()[0], dict):
623+ for k, v in inst_types.iteritems():
624+ self._print_instance_types(k, v)
625+ else:
626+ self._print_instance_types(name, inst_types)
627+
628+
629+class ImageCommands(object):
630+ """Methods for dealing with a cloud in an odd state"""
631+
632+ def __init__(self, *args, **kwargs):
633+ self.image_service = utils.import_object(FLAGS.image_service)
634+
635+ def _register(self, image_type, disk_format, container_format,
636+ path, owner, name=None, is_public='T',
637+ architecture='x86_64', kernel_id=None, ramdisk_id=None):
638+ meta = {'is_public': True,
639+ 'name': name,
640+ 'disk_format': disk_format,
641+ 'container_format': container_format,
642+ 'properties': {'image_state': 'available',
643+ 'owner': owner,
644+ 'type': image_type,
645+ 'architecture': architecture,
646+ 'image_location': 'local',
647+ 'is_public': (is_public == 'T')}}
648+ print image_type, meta
649+ if kernel_id:
650+ meta['properties']['kernel_id'] = int(kernel_id)
651+ if ramdisk_id:
652+ meta['properties']['ramdisk_id'] = int(ramdisk_id)
653+ elevated = context.get_admin_context()
654+ try:
655+ with open(path) as ifile:
656+ image = self.image_service.create(elevated, meta, ifile)
657+ new = image['id']
658+ print _("Image registered to %(new)s (%(new)08x).") % locals()
659+ return new
660+ except Exception as exc:
661+ print _("Failed to register %(path)s: %(exc)s") % locals()
662+
663+ def all_register(self, image, kernel, ramdisk, owner, name=None,
664+ is_public='T', architecture='x86_64'):
665+ """Uploads an image, kernel, and ramdisk into the image_service
666+ arguments: image kernel ramdisk owner [name] [is_public='T']
667+ [architecture='x86_64']"""
668+ kernel_id = self.kernel_register(kernel, owner, None,
669+ is_public, architecture)
670+ ramdisk_id = self.ramdisk_register(ramdisk, owner, None,
671+ is_public, architecture)
672+ self.image_register(image, owner, name, is_public,
673+ architecture, kernel_id, ramdisk_id)
674+
675+ def image_register(self, path, owner, name=None, is_public='T',
676+ architecture='x86_64', kernel_id=None, ramdisk_id=None,
677+ disk_format='ami', container_format='ami'):
678+ """Uploads an image into the image_service
679+ arguments: path owner [name] [is_public='T'] [architecture='x86_64']
680+ [kernel_id=None] [ramdisk_id=None]
681+ [disk_format='ami'] [container_format='ami']"""
682+ return self._register('machine', disk_format, container_format, path,
683+ owner, name, is_public, architecture,
684+ kernel_id, ramdisk_id)
685+
686+ def kernel_register(self, path, owner, name=None, is_public='T',
687+ architecture='x86_64'):
688+ """Uploads a kernel into the image_service
689+ arguments: path owner [name] [is_public='T'] [architecture='x86_64']
690+ """
691+ return self._register('kernel', 'aki', 'aki', path, owner, name,
692+ is_public, architecture)
693+
694+ def ramdisk_register(self, path, owner, name=None, is_public='T',
695+ architecture='x86_64'):
696+ """Uploads a ramdisk into the image_service
697+ arguments: path owner [name] [is_public='T'] [architecture='x86_64']
698+ """
699+ return self._register('ramdisk', 'ari', 'ari', path, owner, name,
700+ is_public, architecture)
701+
702+ def _lookup(self, old_image_id):
703+ try:
704+ internal_id = ec2utils.ec2_id_to_id(old_image_id)
705+ image = self.image_service.show(context, internal_id)
706+ except exception.NotFound:
707+ image = self.image_service.show_by_name(context, old_image_id)
708+ return image['id']
709+
710+ def _old_to_new(self, old):
711+ mapping = {'machine': 'ami',
712+ 'kernel': 'aki',
713+ 'ramdisk': 'ari'}
714+ container_format = mapping[old['type']]
715+ disk_format = container_format
716+ new = {'disk_format': disk_format,
717+ 'container_format': container_format,
718+ 'is_public': True,
719+ 'name': old['imageId'],
720+ 'properties': {'image_state': old['imageState'],
721+ 'owner': old['imageOwnerId'],
722+ 'architecture': old['architecture'],
723+ 'type': old['type'],
724+ 'image_location': old['imageLocation'],
725+ 'is_public': old['isPublic']}}
726+ if old.get('kernelId'):
727+ new['properties']['kernel_id'] = self._lookup(old['kernelId'])
728+ if old.get('ramdiskId'):
729+ new['properties']['ramdisk_id'] = self._lookup(old['ramdiskId'])
730+ return new
731+
732+ def _convert_images(self, images):
733+ elevated = context.get_admin_context()
734+ for image_path, image_metadata in images.iteritems():
735+ meta = self._old_to_new(image_metadata)
736+ old = meta['name']
737+ try:
738+ with open(image_path) as ifile:
739+ image = self.image_service.create(elevated, meta, ifile)
740+ new = image['id']
741+ print _("Image %(old)s converted to " \
742+ "%(new)s (%(new)08x).") % locals()
743+ except Exception as exc:
744+ print _("Failed to convert %(old)s: %(exc)s") % locals()
745+
746+ def convert(self, directory):
747+ """Uploads old objectstore images in directory to new service
748+ arguments: directory"""
749+ machine_images = {}
750+ other_images = {}
751+ directory = os.path.abspath(directory)
752+ # NOTE(vish): If we're importing from the images path dir, attempt
753+ # to move the files out of the way before importing
754+ # so we aren't writing to the same directory. This
755+ # may fail if the dir was a mointpoint.
756+ if (FLAGS.image_service == 'nova.image.local.LocalImageService'
757+ and directory == os.path.abspath(FLAGS.images_path)):
758+ new_dir = "%s_bak" % directory
759+ os.move(directory, new_dir)
760+ os.mkdir(directory)
761+ directory = new_dir
762+ for fn in glob.glob("%s/*/info.json" % directory):
763+ try:
764+ image_path = os.path.join(fn.rpartition('/')[0], 'image')
765+ with open(fn) as metadata_file:
766+ image_metadata = json.load(metadata_file)
767+ if image_metadata['type'] == 'machine':
768+ machine_images[image_path] = image_metadata
769+ else:
770+ other_images[image_path] = image_metadata
771+ except Exception as exc:
772+ print _("Failed to load %(fn)s.") % locals()
773+ # NOTE(vish): do kernels and ramdisks first so images
774+ self._convert_images(other_images)
775+ self._convert_images(machine_images)
776+
777+
778+>>>>>>> MERGE-SOURCE
779 CATEGORIES = [
780 ('user', UserCommands),
781 ('account', AccountCommands),
782@@ -908,13 +1340,22 @@
783 ('fixed', FixedIpCommands),
784 ('floating', FloatingIpCommands),
785 ('network', NetworkCommands),
786+ ('vm', VmCommands),
787 ('service', ServiceCommands),
788 ('log', LogCommands),
789 ('db', DbCommands),
790+<<<<<<< TREE
791 ('volume', VolumeCommands),
792 ('instance_type', InstanceTypeCommands),
793 ('image', ImageCommands),
794 ('flavor', InstanceTypeCommands)]
795+=======
796+ ('volume', VolumeCommands),
797+ ('instance_type', InstanceTypeCommands),
798+ ('image', ImageCommands),
799+ ('flavor', InstanceTypeCommands),
800+ ('instance', InstanceCommands)]
801+>>>>>>> MERGE-SOURCE
802
803
804 def lazy_match(name, key_value_tuples):
805
806=== modified file 'bin/nova-objectstore'
807--- bin/nova-objectstore 2010-12-14 23:22:03 +0000
808+++ bin/nova-objectstore 2011-03-25 13:43:55 +0000
809@@ -36,9 +36,10 @@
810 gettext.install('nova', unicode=1)
811
812 from nova import flags
813+from nova import log as logging
814 from nova import utils
815-from nova import twistd
816-from nova.objectstore import handler
817+from nova import wsgi
818+from nova.objectstore import s3server
819
820
821 FLAGS = flags.FLAGS
822@@ -46,7 +47,9 @@
823
824 if __name__ == '__main__':
825 utils.default_flagfile()
826- twistd.serve(__file__)
827-
828-if __name__ == '__builtin__':
829- application = handler.get_application() # pylint: disable-msg=C0103
830+ FLAGS(sys.argv)
831+ logging.setup()
832+ router = s3server.S3Application(FLAGS.buckets_path)
833+ server = wsgi.Server()
834+ server.start(router, FLAGS.s3_port, host=FLAGS.s3_host)
835+ server.wait()
836
837=== modified file 'bin/stack'
838--- bin/stack 2011-01-18 15:24:20 +0000
839+++ bin/stack 2011-03-25 13:43:55 +0000
840@@ -59,11 +59,21 @@
841
842 def format_help(d):
843 """Format help text, keys are labels and values are descriptions."""
844+ MAX_INDENT = 30
845 indent = max([len(k) for k in d])
846+ if indent > MAX_INDENT:
847+ indent = MAX_INDENT - 6
848+
849 out = []
850 for k, v in d.iteritems():
851- t = textwrap.TextWrapper(initial_indent=' %s ' % k.ljust(indent),
852- subsequent_indent=' ' * (indent + 6))
853+ if (len(k) + 6) > MAX_INDENT:
854+ out.extend([' %s' % k])
855+ initial_indent = ' ' * (indent + 6)
856+ else:
857+ initial_indent = ' %s ' % k.ljust(indent)
858+ subsequent_indent = ' ' * (indent + 6)
859+ t = textwrap.TextWrapper(initial_indent=initial_indent,
860+ subsequent_indent=subsequent_indent)
861 out.extend(t.wrap(v))
862 return out
863
864
865=== modified file 'contrib/boto_v6/ec2/connection.py'
866--- contrib/boto_v6/ec2/connection.py 2011-01-12 03:05:27 +0000
867+++ contrib/boto_v6/ec2/connection.py 2011-03-25 13:43:55 +0000
868@@ -4,8 +4,10 @@
869 @author: Nachi Ueno <ueno.nachi@lab.ntt.co.jp>
870 '''
871 import boto
872+import base64
873 import boto.ec2
874 from boto_v6.ec2.instance import ReservationV6
875+from boto.ec2.securitygroup import SecurityGroup
876
877
878 class EC2ConnectionV6(boto.ec2.EC2Connection):
879@@ -39,3 +41,101 @@
880 self.build_filter_params(params, filters)
881 return self.get_list('DescribeInstancesV6', params,
882 [('item', ReservationV6)])
883+
884+ def run_instances(self, image_id, min_count=1, max_count=1,
885+ key_name=None, security_groups=None,
886+ user_data=None, addressing_type=None,
887+ instance_type='m1.small', placement=None,
888+ kernel_id=None, ramdisk_id=None,
889+ monitoring_enabled=False, subnet_id=None,
890+ block_device_map=None):
891+ """
892+ Runs an image on EC2.
893+
894+ :type image_id: string
895+ :param image_id: The ID of the image to run
896+
897+ :type min_count: int
898+ :param min_count: The minimum number of instances to launch
899+
900+ :type max_count: int
901+ :param max_count: The maximum number of instances to launch
902+
903+ :type key_name: string
904+ :param key_name: The name of the key pair with which to
905+ launch instances
906+
907+ :type security_groups: list of strings
908+ :param security_groups: The names of the security groups with
909+ which to associate instances
910+
911+ :type user_data: string
912+ :param user_data: The user data passed to the launched instances
913+
914+ :type instance_type: string
915+ :param instance_type: The type of instance to run
916+ (m1.small, m1.large, m1.xlarge)
917+
918+ :type placement: string
919+ :param placement: The availability zone in which to launch
920+ the instances
921+
922+ :type kernel_id: string
923+ :param kernel_id: The ID of the kernel with which to
924+ launch the instances
925+
926+ :type ramdisk_id: string
927+ :param ramdisk_id: The ID of the RAM disk with which to
928+ launch the instances
929+
930+ :type monitoring_enabled: bool
931+ :param monitoring_enabled: Enable CloudWatch monitoring
932+ on the instance.
933+
934+ :type subnet_id: string
935+ :param subnet_id: The subnet ID within which to launch
936+ the instances for VPC.
937+
938+ :type block_device_map:
939+ :class:`boto.ec2.blockdevicemapping.BlockDeviceMapping`
940+ :param block_device_map: A BlockDeviceMapping data structure
941+ describing the EBS volumes associated
942+ with the Image.
943+
944+ :rtype: Reservation
945+ :return: The :class:`boto.ec2.instance.ReservationV6`
946+ associated with the request for machines
947+ """
948+ params = {'ImageId': image_id,
949+ 'MinCount': min_count,
950+ 'MaxCount': max_count}
951+ if key_name:
952+ params['KeyName'] = key_name
953+ if security_groups:
954+ l = []
955+ for group in security_groups:
956+ if isinstance(group, SecurityGroup):
957+ l.append(group.name)
958+ else:
959+ l.append(group)
960+ self.build_list_params(params, l, 'SecurityGroup')
961+ if user_data:
962+ params['UserData'] = base64.b64encode(user_data)
963+ if addressing_type:
964+ params['AddressingType'] = addressing_type
965+ if instance_type:
966+ params['InstanceType'] = instance_type
967+ if placement:
968+ params['Placement.AvailabilityZone'] = placement
969+ if kernel_id:
970+ params['KernelId'] = kernel_id
971+ if ramdisk_id:
972+ params['RamdiskId'] = ramdisk_id
973+ if monitoring_enabled:
974+ params['Monitoring.Enabled'] = 'true'
975+ if subnet_id:
976+ params['SubnetId'] = subnet_id
977+ if block_device_map:
978+ block_device_map.build_list_params(params)
979+ return self.get_object('RunInstances', params,
980+ ReservationV6, verb='POST')
981
982=== modified file 'contrib/nova.sh'
983--- contrib/nova.sh 2011-03-08 00:01:43 +0000
984+++ contrib/nova.sh 2011-03-25 13:43:55 +0000
985@@ -76,6 +76,7 @@
986 sudo apt-get install -y python-migrate python-eventlet python-gflags python-ipy python-tempita
987 sudo apt-get install -y python-libvirt python-libxml2 python-routes python-cheetah
988 sudo apt-get install -y python-netaddr python-paste python-pastedeploy python-glance
989+ sudo apt-get install -y python-multiprocessing
990
991 if [ "$USE_IPV6" == 1 ]; then
992 sudo apt-get install -y radvd
993
994=== modified file 'doc/source/_static/tweaks.css'
995--- doc/source/_static/tweaks.css 2010-11-11 22:32:24 +0000
996+++ doc/source/_static/tweaks.css 2011-03-25 13:43:55 +0000
997@@ -69,3 +69,150 @@
998 .tweet_list li .tweet_avatar {
999 float: left;
1000 }
1001+
1002+/* ------------------------------------------
1003+PURE CSS SPEECH BUBBLES
1004+by Nicolas Gallagher
1005+- http://nicolasgallagher.com/pure-css-speech-bubbles/
1006+
1007+http://nicolasgallagher.com
1008+http://twitter.com/necolas
1009+
1010+Created: 02 March 2010
1011+Version: 1.1 (21 October 2010)
1012+
1013+Dual licensed under MIT and GNU GPLv2 © Nicolas Gallagher
1014+------------------------------------------ */
1015+/* THE SPEECH BUBBLE
1016+------------------------------------------------------------------------------------------------------------------------------- */
1017+
1018+/* THE SPEECH BUBBLE
1019+------------------------------------------------------------------------------------------------------------------------------- */
1020+
1021+.triangle-border {
1022+ position:relative;
1023+ padding:15px;
1024+ margin:1em 0 3em;
1025+ border:5px solid #BC1518;
1026+ color:#333;
1027+ background:#fff;
1028+
1029+ /* css3 */
1030+ -moz-border-radius:10px;
1031+ -webkit-border-radius:10px;
1032+ border-radius:10px;
1033+}
1034+
1035+/* Variant : for left positioned triangle
1036+------------------------------------------ */
1037+
1038+.triangle-border.left {
1039+ margin-left:30px;
1040+}
1041+
1042+/* Variant : for right positioned triangle
1043+------------------------------------------ */
1044+
1045+.triangle-border.right {
1046+ margin-right:30px;
1047+}
1048+
1049+/* THE TRIANGLE
1050+------------------------------------------------------------------------------------------------------------------------------- */
1051+
1052+.triangle-border:before {
1053+ content:"";
1054+ display:block; /* reduce the damage in FF3.0 */
1055+ position:absolute;
1056+ bottom:-40px; /* value = - border-top-width - border-bottom-width */
1057+ left:40px; /* controls horizontal position */
1058+ width:0;
1059+ height:0;
1060+ border:20px solid transparent;
1061+ border-top-color:#BC1518;
1062+}
1063+
1064+/* creates the smaller triangle */
1065+.triangle-border:after {
1066+ content:"";
1067+ display:block; /* reduce the damage in FF3.0 */
1068+ position:absolute;
1069+ bottom:-26px; /* value = - border-top-width - border-bottom-width */
1070+ left:47px; /* value = (:before left) + (:before border-left) - (:after border-left) */
1071+ width:0;
1072+ height:0;
1073+ border:13px solid transparent;
1074+ border-top-color:#fff;
1075+}
1076+
1077+/* Variant : top
1078+------------------------------------------ */
1079+
1080+/* creates the larger triangle */
1081+.triangle-border.top:before {
1082+ top:-40px; /* value = - border-top-width - border-bottom-width */
1083+ right:40px; /* controls horizontal position */
1084+ bottom:auto;
1085+ left:auto;
1086+ border:20px solid transparent;
1087+ border-bottom-color:#BC1518;
1088+}
1089+
1090+/* creates the smaller triangle */
1091+.triangle-border.top:after {
1092+ top:-26px; /* value = - border-top-width - border-bottom-width */
1093+ right:47px; /* value = (:before right) + (:before border-right) - (:after border-right) */
1094+ bottom:auto;
1095+ left:auto;
1096+ border:13px solid transparent;
1097+ border-bottom-color:#fff;
1098+}
1099+
1100+/* Variant : left
1101+------------------------------------------ */
1102+
1103+/* creates the larger triangle */
1104+.triangle-border.left:before {
1105+ top:10px; /* controls vertical position */
1106+ left:-30px; /* value = - border-left-width - border-right-width */
1107+ bottom:auto;
1108+ border-width:15px 30px 15px 0;
1109+ border-style:solid;
1110+ border-color:transparent #BC1518;
1111+}
1112+
1113+/* creates the smaller triangle */
1114+.triangle-border.left:after {
1115+ top:16px; /* value = (:before top) + (:before border-top) - (:after border-top) */
1116+ left:-21px; /* value = - border-left-width - border-right-width */
1117+ bottom:auto;
1118+ border-width:9px 21px 9px 0;
1119+ border-style:solid;
1120+ border-color:transparent #fff;
1121+}
1122+
1123+/* Variant : right
1124+------------------------------------------ */
1125+
1126+/* creates the larger triangle */
1127+.triangle-border.right:before {
1128+ top:10px; /* controls vertical position */
1129+ right:-30px; /* value = - border-left-width - border-right-width */
1130+ bottom:auto;
1131+ left:auto;
1132+ border-width:15px 0 15px 30px;
1133+ border-style:solid;
1134+ border-color:transparent #BC1518;
1135+}
1136+
1137+/* creates the smaller triangle */
1138+.triangle-border.right:after {
1139+ top:16px; /* value = (:before top) + (:before border-top) - (:after border-top) */
1140+ right:-21px; /* value = - border-left-width - border-right-width */
1141+ bottom:auto;
1142+ left:auto;
1143+ border-width:9px 0 9px 21px;
1144+ border-style:solid;
1145+ border-color:transparent #fff;
1146+}
1147+
1148
1149=== modified file 'doc/source/_theme/layout.html'
1150--- doc/source/_theme/layout.html 2010-11-05 22:52:59 +0000
1151+++ doc/source/_theme/layout.html 2011-03-25 13:43:55 +0000
1152@@ -71,12 +71,21 @@
1153 </p>
1154 </div>
1155 <script type="text/javascript">$('#searchbox').show(0);</script>
1156+
1157+ <p class="triangle-border right">
1158+ Psst... hey. You're reading the latest content, but it might be out of sync with code. You can read <a href="http://nova.openstack.org/2011.1">Nova 2011.1 docs</a> or <a href="http://docs.openstack.org">all OpenStack docs</a> too.
1159+ </p>
1160+
1161 {%- endif %}
1162
1163 {%- if pagename == "index" %}
1164- <h3>{{ _('Twitter Feed') }}</h3>
1165+
1166+
1167+ <h3>{{ _('Twitter Feed') }}</h3>
1168 <div id="twitter_feed" class='twitter_feed'></div>
1169 {%- endif %}
1170+
1171+
1172
1173
1174 {%- endblock %}
1175
1176=== added file 'doc/source/images/vmwareapi_blockdiagram.jpg'
1177Binary files doc/source/images/vmwareapi_blockdiagram.jpg 1970-01-01 00:00:00 +0000 and doc/source/images/vmwareapi_blockdiagram.jpg 2011-03-25 13:43:55 +0000 differ
1178=== added file 'doc/source/vmwareapi_readme.rst'
1179--- doc/source/vmwareapi_readme.rst 1970-01-01 00:00:00 +0000
1180+++ doc/source/vmwareapi_readme.rst 2011-03-25 13:43:55 +0000
1181@@ -0,0 +1,218 @@
1182+..
1183+ Copyright (c) 2010 Citrix Systems, Inc.
1184+ Copyright 2010 OpenStack LLC.
1185+
1186+ Licensed under the Apache License, Version 2.0 (the "License"); you may
1187+ not use this file except in compliance with the License. You may obtain
1188+ a copy of the License at
1189+
1190+ http://www.apache.org/licenses/LICENSE-2.0
1191+
1192+ Unless required by applicable law or agreed to in writing, software
1193+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1194+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1195+ License for the specific language governing permissions and limitations
1196+ under the License.
1197+
1198+VMware ESX/ESXi Server Support for OpenStack Compute
1199+====================================================
1200+
1201+Introduction
1202+------------
1203+A module named 'vmwareapi' is added to 'nova.virt' to add support of VMware ESX/ESXi hypervisor to OpenStack compute (Nova). Nova may now use VMware vSphere as a compute provider.
1204+
1205+The basic requirement is to support VMware vSphere 4.1 as a compute provider within Nova. As the deployment architecture, support both ESX and ESXi. VM storage is restricted to VMFS volumes on local drives. vCenter is not required by the current design, and is not currently supported. Instead, Nova Compute talks directly to ESX/ESXi.
1206+
1207+The 'vmwareapi' module is integrated with Glance, so that VM images can be streamed from there for boot on ESXi using Glance server for image storage & retrieval.
1208+
1209+Currently supports Nova's flat networking model (Flat Manager) & VLAN networking model.
1210+
1211+.. image:: images/vmwareapi_blockdiagram.jpg
1212+
1213+
1214+System Requirements
1215+-------------------
1216+Following software components are required for building the cloud using OpenStack on top of ESX/ESXi Server(s):
1217+
1218+* OpenStack
1219+* Glance Image service
1220+* VMware ESX v4.1 or VMware ESXi(licensed) v4.1
1221+
1222+VMware ESX Requirements
1223+-----------------------
1224+* ESX credentials with administration/root privileges
1225+* Single local hard disk at the ESX host
1226+* An ESX Virtual Machine Port Group (For Flat Networking)
1227+* An ESX physical network adapter (For VLAN networking)
1228+* Need to enable "vSphere Web Access" in "vSphere client" UI at Configuration->Security Profile->Firewall
1229+
1230+Python dependencies
1231+-------------------
1232+* suds-0.4
1233+
1234+* Installation procedure on Ubuntu/Debian
1235+
1236+::
1237+
1238+ easy_install suds==0.4
1239+
1240+
1241+Configuration flags required for nova-compute
1242+---------------------------------------------
1243+::
1244+
1245+ --connection_type=vmwareapi
1246+ --vmwareapi_host_ip=<VMware ESX Host IP>
1247+ --vmwareapi_host_username=<VMware ESX Username>
1248+ --vmwareapi_host_password=<VMware ESX Password>
1249+ --network_driver=nova.network.vmwareapi_net [Optional, only for VLAN Networking]
1250+ --vlan_interface=<Physical ethernet adapter name in VMware ESX host for vlan networking E.g vmnic0> [Optional, only for VLAN Networking]
1251+
1252+
1253+Configuration flags required for nova-network
1254+---------------------------------------------
1255+::
1256+
1257+ --network_manager=nova.network.manager.FlatManager [or nova.network.manager.VlanManager]
1258+ --flat_network_bridge=<ESX Virtual Machine Port Group> [Optional, only for Flat Networking]
1259+
1260+
1261+Configuration flags required for nova-console
1262+---------------------------------------------
1263+::
1264+
1265+ --console_manager=nova.console.vmrc_manager.ConsoleVMRCManager
1266+ --console_driver=nova.console.vmrc.VMRCSessionConsole [Optional, only for OTP (One time Passwords) as against host credentials]
1267+
1268+
1269+Other flags
1270+-----------
1271+::
1272+
1273+ --image_service=nova.image.glance.GlanceImageService
1274+ --glance_host=<Glance Host>
1275+ --vmwareapi_wsdl_loc=<http://<WEB SERVER>/vimService.wsdl>
1276+
1277+Note:- Due to a faulty wsdl being shipped with ESX vSphere 4.1 we need a working wsdl which can to be mounted on any webserver. Follow the below steps to download the SDK,
1278+
1279+* Go to http://www.vmware.com/support/developer/vc-sdk/
1280+* Go to section VMware vSphere Web Services SDK 4.0
1281+* Click "Downloads"
1282+* Enter VMware credentials when prompted for download
1283+* Unzip the downloaded file vi-sdk-4.0.0-xxx.zip
1284+* Go to SDK->WSDL->vim25 & host the files "vimService.wsdl" and "vim.wsdl" in a WEB SERVER
1285+* Set the flag "--vmwareapi_wsdl_loc" with url, "http://<WEB SERVER>/vimService.wsdl"
1286+
1287+
1288+VLAN Network Manager
1289+--------------------
1290+VLAN network support is added through a custom network driver in the nova-compute node i.e "nova.network.vmwareapi_net" and it uses a Physical ethernet adapter on the VMware ESX/ESXi host for VLAN Networking (the name of the ethernet adapter is specified as vlan_interface flag in the nova-compute configuration flag) in the nova-compute node.
1291+
1292+Using the physical adapter name the associated Virtual Switch will be determined. In VMware ESX there can be only one Virtual Switch associated with a Physical adapter.
1293+
1294+When VM Spawn request is issued with a VLAN ID the work flow looks like,
1295+
1296+1. Check that a Physical adapter with the given name exists. If no, throw an error.If yes, goto next step.
1297+
1298+2. Check if a Virtual Switch is associated with the Physical ethernet adapter with vlan interface name. If no, throw an error. If yes, goto next step.
1299+
1300+3. Check if a port group with the network bridge name exists. If no, create a port group in the Virtual switch with the give name and VLAN id and goto step 6. If yes, goto next step.
1301+
1302+4. Check if the port group is associated with the Virtual Switch. If no, throw an error. If yes, goto next step.
1303+
1304+5. Check if the port group is associated with the given VLAN Id. If no, throw an error. If yes, goto next step.
1305+
1306+6. Spawn the VM using this Port Group as the Network Name for the VM.
1307+
1308+
1309+Guest console Support
1310+---------------------
1311+| VMware VMRC console is a built-in console method providing graphical control of the VM remotely.
1312+|
1313+| VMRC Console types supported:
1314+| # Host based credentials
1315+| Not secure (Sends ESX admin credentials in clear text)
1316+|
1317+| # OTP (One time passwords)
1318+| Secure but creates multiple session entries in DB for each OpenStack console create request.
1319+| Console sessions created is can be used only once.
1320+|
1321+| Install browser based VMware ESX plugins/activex on the client machine to connect
1322+|
1323+| Windows:-
1324+| Internet Explorer:
1325+| https://<VMware ESX Host>/ui/plugin/vmware-vmrc-win32-x86.exe
1326+|
1327+| Mozilla Firefox:
1328+| https://<VMware ESX Host>/ui/plugin/vmware-vmrc-win32-x86.xpi
1329+|
1330+| Linux:-
1331+| Mozilla Firefox
1332+| 32-Bit Linux:
1333+| https://<VMware ESX Host>/ui/plugin/vmware-vmrc-linux-x86.xpi
1334+|
1335+| 64-Bit Linux:
1336+| https://<VMware ESX Host>/ui/plugin/vmware-vmrc-linux-x64.xpi
1337+|
1338+| OpenStack Console Details:
1339+| console_type = vmrc+credentials | vmrc+session
1340+| host = <VMware ESX Host>
1341+| port = <VMware ESX Port>
1342+| password = {'vm_id': <VMware VM ID>,'username':<VMware ESX Username>, 'password':<VMware ESX Password>} //base64 + json encoded
1343+|
1344+| Instantiate the plugin/activex object
1345+| # In Internet Explorer
1346+| <object id='vmrc' classid='CLSID:B94C2238-346E-4C5E-9B36-8CC627F35574'>
1347+| </object>
1348+|
1349+| # Mozilla Firefox and other browsers
1350+| <object id='vmrc' type='application/x-vmware-vmrc;version=2.5.0.0'>
1351+| </object>
1352+|
1353+| Open vmrc connection
1354+| # Host based credentials [type=vmrc+credentials]
1355+| <script type="text/javascript">
1356+| var MODE_WINDOW = 2;
1357+| var vmrc = document.getElementById('vmrc');
1358+| vmrc.connect(<VMware ESX Host> + ':' + <VMware ESX Port>, <VMware ESX Username>, <VMware ESX Password>, '', <VMware VM ID>, MODE_WINDOW);
1359+| </script>
1360+|
1361+| # OTP (One time passwords) [type=vmrc+session]
1362+| <script type="text/javascript">
1363+| var MODE_WINDOW = 2;
1364+| var vmrc = document.getElementById('vmrc');
1365+| vmrc.connectWithSession(<VMware ESX Host> + ':' + <VMware ESX Port>, <VMware VM ID>, <VMware ESX Password>, MODE_WINDOW);
1366+| </script>
1367+
1368+
1369+Assumptions
1370+-----------
1371+1. The VMware images uploaded to the image repositories have VMware Tools installed.
1372+
1373+
1374+FAQ
1375+---
1376+
1377+1. What type of disk images are supported?
1378+
1379+* Only VMware VMDK's are currently supported and of that support is available only for thick disks, thin provisioned disks are not supported.
1380+
1381+
1382+2. How is IP address information injected into the guest?
1383+
1384+* IP address information is injected through 'machine.id' vmx parameter (equivalent to XenStore in XenServer). This information can be retrived inside the guest using VMware tools.
1385+
1386+
1387+3. What is the guest tool?
1388+
1389+* The guest tool is a small python script that should be run either as a service or added to system startup. This script configures networking on the guest. The guest tool is available at tools/esx/guest_tool.py
1390+
1391+
1392+4. What type of consoles are supported?
1393+
1394+* VMware VMRC based consoles are supported. There are 2 options for credentials one is OTP (Secure but creates multiple session entries in DB for each OpenStack console create request.) & other is host based credentials (It may not be secure as ESX credentials are transmitted as clear text).
1395+
1396+5. What does 'Vim' refer to as far as vmwareapi module is concerned?
1397+
1398+* Vim refers to VMware Virtual Infrastructure Methodology. This is not to be confused with "VIM" editor.
1399+
1400
1401=== modified file 'etc/api-paste.ini'
1402--- etc/api-paste.ini 2011-03-07 19:33:24 +0000
1403+++ etc/api-paste.ini 2011-03-25 13:43:55 +0000
1404@@ -67,10 +67,14 @@
1405 [composite:osapi]
1406 use = egg:Paste#urlmap
1407 /: osversions
1408-/v1.0: openstackapi
1409-
1410-[pipeline:openstackapi]
1411-pipeline = faultwrap auth ratelimit osapiapp
1412+/v1.0: openstackapi10
1413+/v1.1: openstackapi11
1414+
1415+[pipeline:openstackapi10]
1416+pipeline = faultwrap auth ratelimit osapiapp10
1417+
1418+[pipeline:openstackapi11]
1419+pipeline = faultwrap auth ratelimit extensions osapiapp11
1420
1421 [filter:faultwrap]
1422 paste.filter_factory = nova.api.openstack:FaultWrapper.factory
1423@@ -79,10 +83,16 @@
1424 paste.filter_factory = nova.api.openstack.auth:AuthMiddleware.factory
1425
1426 [filter:ratelimit]
1427-paste.filter_factory = nova.api.openstack.ratelimiting:RateLimitingMiddleware.factory
1428-
1429-[app:osapiapp]
1430-paste.app_factory = nova.api.openstack:APIRouter.factory
1431+paste.filter_factory = nova.api.openstack.limits:RateLimitingMiddleware.factory
1432+
1433+[filter:extensions]
1434+paste.filter_factory = nova.api.openstack.extensions:ExtensionMiddleware.factory
1435+
1436+[app:osapiapp10]
1437+paste.app_factory = nova.api.openstack:APIRouterV10.factory
1438+
1439+[app:osapiapp11]
1440+paste.app_factory = nova.api.openstack:APIRouterV11.factory
1441
1442 [pipeline:osversions]
1443 pipeline = faultwrap osversionapp
1444
1445=== modified file 'nova/api/direct.py'
1446--- nova/api/direct.py 2011-03-09 20:08:11 +0000
1447+++ nova/api/direct.py 2011-03-25 13:43:55 +0000
1448@@ -38,6 +38,7 @@
1449 import webob
1450
1451 from nova import context
1452+from nova import exception
1453 from nova import flags
1454 from nova import utils
1455 from nova import wsgi
1456@@ -205,10 +206,59 @@
1457 # NOTE(vish): make sure we have no unicode keys for py2.6.
1458 params = dict([(str(k), v) for (k, v) in params.iteritems()])
1459 result = method(context, **params)
1460+<<<<<<< TREE
1461 if type(result) is dict or type(result) is list:
1462 return self._serialize(result, req.best_match_content_type())
1463 else:
1464+=======
1465+ if result is None or type(result) is str or type(result) is unicode:
1466+>>>>>>> MERGE-SOURCE
1467 return result
1468+ try:
1469+ return self._serialize(result, req.best_match_content_type())
1470+ except:
1471+ raise exception.Error("returned non-serializable type: %s"
1472+ % result)
1473+
1474+
1475+class Limited(object):
1476+ __notdoc = """Limit the available methods on a given object.
1477+
1478+ (Not a docstring so that the docstring can be conditionally overriden.)
1479+
1480+ Useful when defining a public API that only exposes a subset of an
1481+ internal API.
1482+
1483+ Expected usage of this class is to define a subclass that lists the allowed
1484+ methods in the 'allowed' variable.
1485+
1486+ Additionally where appropriate methods can be added or overwritten, for
1487+ example to provide backwards compatibility.
1488+
1489+ The wrapping approach has been chosen so that the wrapped API can maintain
1490+ its own internal consistency, for example if it calls "self.create" it
1491+ should get its own create method rather than anything we do here.
1492+
1493+ """
1494+
1495+ _allowed = None
1496+
1497+ def __init__(self, proxy):
1498+ self._proxy = proxy
1499+ if not self.__doc__:
1500+ self.__doc__ = proxy.__doc__
1501+ if not self._allowed:
1502+ self._allowed = []
1503+
1504+ def __getattr__(self, key):
1505+ """Only return methods that are named in self._allowed."""
1506+ if key not in self._allowed:
1507+ raise AttributeError()
1508+ return getattr(self._proxy, key)
1509+
1510+ def __dir__(self):
1511+ """Only return methods that are named in self._allowed."""
1512+ return [x for x in dir(self._proxy) if x in self._allowed]
1513
1514
1515 class Proxy(object):
1516
1517=== modified file 'nova/api/ec2/__init__.py'
1518--- nova/api/ec2/__init__.py 2011-03-09 19:20:26 +0000
1519+++ nova/api/ec2/__init__.py 2011-03-25 13:43:55 +0000
1520@@ -31,7 +31,7 @@
1521 from nova import utils
1522 from nova import wsgi
1523 from nova.api.ec2 import apirequest
1524-from nova.api.ec2 import cloud
1525+from nova.api.ec2 import ec2utils
1526 from nova.auth import manager
1527
1528
1529@@ -60,11 +60,22 @@
1530 self.log_request_completion(rv, req, start)
1531 return rv
1532
1533+<<<<<<< TREE
1534 def log_request_completion(self, response, request, start):
1535 controller = request.environ.get('ec2.controller', None)
1536 if controller:
1537 controller = controller.__class__.__name__
1538 action = request.environ.get('ec2.action', None)
1539+=======
1540+ def log_request_completion(self, response, request, start):
1541+ apireq = request.environ.get('ec2.request', None)
1542+ if apireq:
1543+ controller = apireq.controller
1544+ action = apireq.action
1545+ else:
1546+ controller = None
1547+ action = None
1548+>>>>>>> MERGE-SOURCE
1549 ctxt = request.environ.get('ec2.context', None)
1550 delta = utils.utcnow() - start
1551 seconds = delta.seconds
1552@@ -75,7 +86,7 @@
1553 microseconds,
1554 request.remote_addr,
1555 request.method,
1556- request.path_info,
1557+ "%s%s" % (request.script_name, request.path_info),
1558 controller,
1559 action,
1560 response.status_int,
1561@@ -319,13 +330,13 @@
1562 except exception.InstanceNotFound as ex:
1563 LOG.info(_('InstanceNotFound raised: %s'), unicode(ex),
1564 context=context)
1565- ec2_id = cloud.id_to_ec2_id(ex.instance_id)
1566+ ec2_id = ec2utils.id_to_ec2_id(ex.instance_id)
1567 message = _('Instance %s not found') % ec2_id
1568 return self._error(req, context, type(ex).__name__, message)
1569 except exception.VolumeNotFound as ex:
1570 LOG.info(_('VolumeNotFound raised: %s'), unicode(ex),
1571 context=context)
1572- ec2_id = cloud.id_to_ec2_id(ex.volume_id, 'vol-%08x')
1573+ ec2_id = ec2utils.id_to_ec2_id(ex.volume_id, 'vol-%08x')
1574 message = _('Volume %s not found') % ec2_id
1575 return self._error(req, context, type(ex).__name__, message)
1576 except exception.NotFound as ex:
1577
1578=== modified file 'nova/api/ec2/admin.py'
1579--- nova/api/ec2/admin.py 2011-03-03 00:12:22 +0000
1580+++ nova/api/ec2/admin.py 2011-03-25 13:43:55 +0000
1581@@ -27,7 +27,12 @@
1582 from nova import exception
1583 from nova import flags
1584 from nova import log as logging
1585-from nova import utils
1586+<<<<<<< TREE
1587+from nova import utils
1588+=======
1589+from nova import utils
1590+from nova.api.ec2 import ec2utils
1591+>>>>>>> MERGE-SOURCE
1592 from nova.auth import manager
1593
1594
1595@@ -60,6 +65,7 @@
1596
1597 def host_dict(host, compute_service, instances, volume_service, volumes, now):
1598 """Convert a host model object to a result dict"""
1599+<<<<<<< TREE
1600 rv = {'hostanme': host, 'instance_count': len(instances),
1601 'volume_count': len(volumes)}
1602 if compute_service:
1603@@ -81,12 +87,36 @@
1604
1605 def instance_dict(inst):
1606 return {'name': inst['name'],
1607+=======
1608+ rv = {'hostname': host, 'instance_count': len(instances),
1609+ 'volume_count': len(volumes)}
1610+ if compute_service:
1611+ latest = compute_service['updated_at'] or compute_service['created_at']
1612+ delta = now - latest
1613+ if delta.seconds <= FLAGS.service_down_time:
1614+ rv['compute'] = 'up'
1615+ else:
1616+ rv['compute'] = 'down'
1617+ if volume_service:
1618+ latest = volume_service['updated_at'] or volume_service['created_at']
1619+ delta = now - latest
1620+ if delta.seconds <= FLAGS.service_down_time:
1621+ rv['volume'] = 'up'
1622+ else:
1623+ rv['volume'] = 'down'
1624+ return rv
1625+
1626+
1627+def instance_dict(inst):
1628+ return {'name': inst['name'],
1629+>>>>>>> MERGE-SOURCE
1630 'memory_mb': inst['memory_mb'],
1631 'vcpus': inst['vcpus'],
1632 'disk_gb': inst['local_gb'],
1633 'flavor_id': inst['flavorid']}
1634
1635
1636+<<<<<<< TREE
1637 def vpn_dict(project, vpn_instance):
1638 rv = {'project_id': project.id,
1639 'public_ip': project.vpn_ip,
1640@@ -106,6 +136,30 @@
1641 return rv
1642
1643
1644+=======
1645+def vpn_dict(project, vpn_instance):
1646+ rv = {'project_id': project.id,
1647+ 'public_ip': project.vpn_ip,
1648+ 'public_port': project.vpn_port}
1649+ if vpn_instance:
1650+ rv['instance_id'] = ec2utils.id_to_ec2_id(vpn_instance['id'])
1651+ rv['created_at'] = utils.isotime(vpn_instance['created_at'])
1652+ address = vpn_instance.get('fixed_ip', None)
1653+ if address:
1654+ rv['internal_ip'] = address['address']
1655+ if project.vpn_ip and project.vpn_port:
1656+ if utils.vpn_ping(project.vpn_ip, project.vpn_port):
1657+ rv['state'] = 'running'
1658+ else:
1659+ rv['state'] = 'down'
1660+ else:
1661+ rv['state'] = 'down - invalid project vpn config'
1662+ else:
1663+ rv['state'] = 'pending'
1664+ return rv
1665+
1666+
1667+>>>>>>> MERGE-SOURCE
1668 class AdminController(object):
1669 """
1670 API Controller for users, hosts, nodes, and workers.
1671@@ -114,9 +168,16 @@
1672 def __str__(self):
1673 return 'AdminController'
1674
1675+<<<<<<< TREE
1676 def describe_instance_types(self, context, **_kwargs):
1677 """Returns all active instance types data (vcpus, memory, etc.)"""
1678 return {'instanceTypeSet': [db.instance_type_get_all(context)]}
1679+=======
1680+ def describe_instance_types(self, context, **_kwargs):
1681+ """Returns all active instance types data (vcpus, memory, etc.)"""
1682+ return {'instanceTypeSet': [instance_dict(v) for v in
1683+ db.instance_type_get_all(context).values()]}
1684+>>>>>>> MERGE-SOURCE
1685
1686 def describe_user(self, _context, name, **_kwargs):
1687 """Returns user data, including access and secret keys."""
1688@@ -258,6 +319,7 @@
1689 raise exception.ApiError(_('operation must be add or remove'))
1690 return True
1691
1692+<<<<<<< TREE
1693 def _vpn_for(self, context, project_id):
1694 """Get the VPN instance for a project ID."""
1695 for instance in db.instance_get_all_by_project(context, project_id):
1696@@ -288,6 +350,38 @@
1697 vpns.append(vpn_dict(project, instance))
1698 return {'items': vpns}
1699
1700+=======
1701+ def _vpn_for(self, context, project_id):
1702+ """Get the VPN instance for a project ID."""
1703+ for instance in db.instance_get_all_by_project(context, project_id):
1704+ if (instance['image_id'] == FLAGS.vpn_image_id
1705+ and not instance['state_description'] in
1706+ ['shutting_down', 'shutdown']):
1707+ return instance
1708+
1709+ def start_vpn(self, context, project):
1710+ instance = self._vpn_for(context, project)
1711+ if not instance:
1712+ # NOTE(vish) import delayed because of __init__.py
1713+ from nova.cloudpipe import pipelib
1714+ pipe = pipelib.CloudPipe()
1715+ try:
1716+ pipe.launch_vpn_instance(project)
1717+ except db.NoMoreNetworks:
1718+ raise exception.ApiError("Unable to claim IP for VPN instance"
1719+ ", ensure it isn't running, and try "
1720+ "again in a few minutes")
1721+ instance = self._vpn_for(context, project)
1722+ return {'instance_id': ec2utils.id_to_ec2_id(instance['id'])}
1723+
1724+ def describe_vpns(self, context):
1725+ vpns = []
1726+ for project in manager.AuthManager().get_projects():
1727+ instance = self._vpn_for(context, project.id)
1728+ vpns.append(vpn_dict(project, instance))
1729+ return {'items': vpns}
1730+
1731+>>>>>>> MERGE-SOURCE
1732 # FIXME(vish): these host commands don't work yet, perhaps some of the
1733 # required data can be retrieved from service objects?
1734
1735@@ -299,6 +393,7 @@
1736 * Volume (up, down, None)
1737 * Volume Count
1738 """
1739+<<<<<<< TREE
1740 services = db.service_get_all(context)
1741 now = datetime.datetime.utcnow()
1742 hosts = []
1743@@ -320,6 +415,29 @@
1744 rv.append(host_dict(host, compute, instances, volume, volumes,
1745 now))
1746 return {'hosts': rv}
1747+=======
1748+ services = db.service_get_all(context, False)
1749+ now = datetime.datetime.utcnow()
1750+ hosts = []
1751+ rv = []
1752+ for host in [service['host'] for service in services]:
1753+ if not host in hosts:
1754+ hosts.append(host)
1755+ for host in hosts:
1756+ compute = [s for s in services if s['host'] == host \
1757+ and s['binary'] == 'nova-compute']
1758+ if compute:
1759+ compute = compute[0]
1760+ instances = db.instance_get_all_by_host(context, host)
1761+ volume = [s for s in services if s['host'] == host \
1762+ and s['binary'] == 'nova-volume']
1763+ if volume:
1764+ volume = volume[0]
1765+ volumes = db.volume_get_all_by_host(context, host)
1766+ rv.append(host_dict(host, compute, instances, volume, volumes,
1767+ now))
1768+ return {'hosts': rv}
1769+>>>>>>> MERGE-SOURCE
1770
1771 def describe_host(self, _context, name, **_kwargs):
1772 """Returns status info for single node."""
1773
1774=== modified file 'nova/api/ec2/cloud.py'
1775--- nova/api/ec2/cloud.py 2011-03-03 23:05:00 +0000
1776+++ nova/api/ec2/cloud.py 2011-03-25 13:43:55 +0000
1777@@ -145,10 +145,15 @@
1778 availability_zone = self._get_availability_zone_by_host(ctxt, host)
1779 floating_ip = db.instance_get_floating_address(ctxt,
1780 instance_ref['id'])
1781+<<<<<<< TREE
1782 ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])
1783 image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'machine')
1784 k_ec2_id = self._image_ec2_id(instance_ref['kernel_id'], 'kernel')
1785 r_ec2_id = self._image_ec2_id(instance_ref['ramdisk_id'], 'ramdisk')
1786+=======
1787+ ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])
1788+ image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'machine')
1789+>>>>>>> MERGE-SOURCE
1790 data = {
1791 'user-data': base64.b64decode(instance_ref['user_data']),
1792 'meta-data': {
1793@@ -167,8 +172,11 @@
1794 'instance-type': instance_ref['instance_type'],
1795 'local-hostname': hostname,
1796 'local-ipv4': address,
1797+<<<<<<< TREE
1798 'kernel-id': k_ec2_id,
1799 'ramdisk-id': r_ec2_id,
1800+=======
1801+>>>>>>> MERGE-SOURCE
1802 'placement': {'availability-zone': availability_zone},
1803 'public-hostname': hostname,
1804 'public-ipv4': floating_ip or '',
1805@@ -176,6 +184,13 @@
1806 'reservation-id': instance_ref['reservation_id'],
1807 'security-groups': '',
1808 'mpi': mpi}}
1809+
1810+ for image_type in ['kernel', 'ramdisk']:
1811+ if '%s_id' % image_type in instance_ref:
1812+ ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type],
1813+ image_type)
1814+ data['meta-data']['%s-id' % image_type] = ec2_id
1815+
1816 if False: # TODO(vish): store ancestor ids
1817 data['ancestor-ami-ids'] = []
1818 if False: # TODO(vish): store product codes
1819@@ -192,9 +207,15 @@
1820 return self._describe_availability_zones(context, **kwargs)
1821
1822 def _describe_availability_zones(self, context, **kwargs):
1823+<<<<<<< TREE
1824 ctxt = context.elevated()
1825 enabled_services = db.service_get_all(ctxt)
1826 disabled_services = db.service_get_all(ctxt, True)
1827+=======
1828+ ctxt = context.elevated()
1829+ enabled_services = db.service_get_all(ctxt, False)
1830+ disabled_services = db.service_get_all(ctxt, True)
1831+>>>>>>> MERGE-SOURCE
1832 available_zones = []
1833 for zone in [service.availability_zone for service
1834 in enabled_services]:
1835@@ -218,7 +239,7 @@
1836 rv = {'availabilityZoneInfo': [{'zoneName': 'nova',
1837 'zoneState': 'available'}]}
1838
1839- services = db.service_get_all(context)
1840+ services = db.service_get_all(context, False)
1841 now = datetime.datetime.utcnow()
1842 hosts = []
1843 for host in [service['host'] for service in services]:
1844@@ -537,8 +558,13 @@
1845 if volume_id:
1846 volumes = []
1847 for ec2_id in volume_id:
1848+<<<<<<< TREE
1849 internal_id = ec2utils.ec2_id_to_id(ec2_id)
1850 volume = self.volume_api.get(context, internal_id)
1851+=======
1852+ internal_id = ec2utils.ec2_id_to_id(ec2_id)
1853+ volume = self.volume_api.get(context, volume_id=internal_id)
1854+>>>>>>> MERGE-SOURCE
1855 volumes.append(volume)
1856 else:
1857 volumes = self.volume_api.get_all(context)
1858@@ -582,9 +608,11 @@
1859
1860 def create_volume(self, context, size, **kwargs):
1861 LOG.audit(_("Create volume of %s GB"), size, context=context)
1862- volume = self.volume_api.create(context, size,
1863- kwargs.get('display_name'),
1864- kwargs.get('display_description'))
1865+ volume = self.volume_api.create(
1866+ context,
1867+ size=size,
1868+ name=kwargs.get('display_name'),
1869+ description=kwargs.get('display_description'))
1870 # TODO(vish): Instance should be None at db layer instead of
1871 # trying to lazy load, but for now we turn it into
1872 # a dict to avoid an error.
1873@@ -603,7 +631,9 @@
1874 if field in kwargs:
1875 changes[field] = kwargs[field]
1876 if changes:
1877- self.volume_api.update(context, volume_id, kwargs)
1878+ self.volume_api.update(context,
1879+ volume_id=volume_id,
1880+ fields=changes)
1881 return True
1882
1883 def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
1884@@ -616,7 +646,7 @@
1885 instance_id=instance_id,
1886 volume_id=volume_id,
1887 device=device)
1888- volume = self.volume_api.get(context, volume_id)
1889+ volume = self.volume_api.get(context, volume_id=volume_id)
1890 return {'attachTime': volume['attach_time'],
1891 'device': volume['mountpoint'],
1892 'instanceId': ec2utils.id_to_ec2_id(instance_id),
1893@@ -627,7 +657,7 @@
1894 def detach_volume(self, context, volume_id, **kwargs):
1895 volume_id = ec2utils.ec2_id_to_id(volume_id)
1896 LOG.audit(_("Detach volume %s"), volume_id, context=context)
1897- volume = self.volume_api.get(context, volume_id)
1898+ volume = self.volume_api.get(context, volume_id=volume_id)
1899 instance = self.compute_api.detach_volume(context, volume_id=volume_id)
1900 return {'attachTime': volume['attach_time'],
1901 'device': volume['mountpoint'],
1902@@ -765,7 +795,7 @@
1903
1904 def release_address(self, context, public_ip, **kwargs):
1905 LOG.audit(_("Release address %s"), public_ip, context=context)
1906- self.network_api.release_floating_ip(context, public_ip)
1907+ self.network_api.release_floating_ip(context, address=public_ip)
1908 return {'releaseResponse': ["Address released."]}
1909
1910 def associate_address(self, context, instance_id, public_ip, **kwargs):
1911@@ -779,7 +809,7 @@
1912
1913 def disassociate_address(self, context, public_ip, **kwargs):
1914 LOG.audit(_("Disassociate address %s"), public_ip, context=context)
1915- self.network_api.disassociate_floating_ip(context, public_ip)
1916+ self.network_api.disassociate_floating_ip(context, address=public_ip)
1917 return {'disassociateResponse': ["Address disassociated."]}
1918
1919 def run_instances(self, context, **kwargs):
1920@@ -949,6 +979,7 @@
1921 if not operation_type in ['add', 'remove']:
1922 raise exception.ApiError(_('operation_type must be add or remove'))
1923 LOG.audit(_("Updating image %s publicity"), image_id, context=context)
1924+<<<<<<< TREE
1925
1926 try:
1927 image = self._get_image(context, image_id)
1928@@ -959,6 +990,18 @@
1929 raise Exception(image)
1930 image['properties']['is_public'] = (operation_type == 'add')
1931 return self.image_service.update(context, internal_id, image)
1932+=======
1933+
1934+ try:
1935+ image = self._get_image(context, image_id)
1936+ except exception.NotFound:
1937+ raise exception.NotFound(_('Image %s not found') % image_id)
1938+ internal_id = image['id']
1939+ del(image['id'])
1940+
1941+ image['properties']['is_public'] = (operation_type == 'add')
1942+ return self.image_service.update(context, internal_id, image)
1943+>>>>>>> MERGE-SOURCE
1944
1945 def update_image(self, context, image_id, **kwargs):
1946 internal_id = ec2utils.ec2_id_to_id(image_id)
1947
1948=== modified file 'nova/api/openstack/__init__.py'
1949--- nova/api/openstack/__init__.py 2011-03-11 19:49:32 +0000
1950+++ nova/api/openstack/__init__.py 2011-03-25 13:43:55 +0000
1951@@ -33,7 +33,9 @@
1952 from nova.api.openstack import consoles
1953 from nova.api.openstack import flavors
1954 from nova.api.openstack import images
1955+from nova.api.openstack import limits
1956 from nova.api.openstack import servers
1957+from nova.api.openstack import server_metadata
1958 from nova.api.openstack import shared_ip_groups
1959 from nova.api.openstack import users
1960 from nova.api.openstack import zones
1961@@ -70,10 +72,15 @@
1962 """Simple paste factory, :class:`nova.wsgi.Router` doesn't have one"""
1963 return cls()
1964
1965- def __init__(self):
1966+ def __init__(self, ext_mgr=None):
1967+ self.server_members = {}
1968 mapper = routes.Mapper()
1969+ self._setup_routes(mapper)
1970+ super(APIRouter, self).__init__(mapper)
1971
1972- server_members = {'action': 'POST'}
1973+ def _setup_routes(self, mapper):
1974+ server_members = self.server_members
1975+ server_members['action'] = 'POST'
1976 if FLAGS.allow_admin_api:
1977 LOG.debug(_("Including admin operations in API."))
1978
1979@@ -83,6 +90,7 @@
1980 server_members['actions'] = 'GET'
1981 server_members['suspend'] = 'POST'
1982 server_members['resume'] = 'POST'
1983+<<<<<<< TREE
1984 server_members['rescue'] = 'POST'
1985 server_members['unrescue'] = 'POST'
1986 server_members['reset_network'] = 'POST'
1987@@ -101,6 +109,22 @@
1988 mapper.resource("server", "servers", controller=servers.Controller(),
1989 collection={'detail': 'GET'},
1990 member=server_members)
1991+=======
1992+ server_members['rescue'] = 'POST'
1993+ server_members['unrescue'] = 'POST'
1994+ server_members['reset_network'] = 'POST'
1995+ server_members['inject_network_info'] = 'POST'
1996+
1997+ mapper.resource("zone", "zones", controller=zones.Controller(),
1998+ collection={'detail': 'GET', 'info': 'GET'}),
1999+
2000+ mapper.resource("user", "users", controller=users.Controller(),
2001+ collection={'detail': 'GET'})
2002+
2003+ mapper.resource("account", "accounts",
2004+ controller=accounts.Controller(),
2005+ collection={'detail': 'GET'})
2006+>>>>>>> MERGE-SOURCE
2007
2008 mapper.resource("backup_schedule", "backup_schedule",
2009 controller=backup_schedules.Controller(),
2010@@ -114,13 +138,42 @@
2011
2012 mapper.resource("image", "images", controller=images.Controller(),
2013 collection={'detail': 'GET'})
2014+
2015 mapper.resource("flavor", "flavors", controller=flavors.Controller(),
2016 collection={'detail': 'GET'})
2017+
2018 mapper.resource("shared_ip_group", "shared_ip_groups",
2019 collection={'detail': 'GET'},
2020 controller=shared_ip_groups.Controller())
2021
2022- super(APIRouter, self).__init__(mapper)
2023+ _limits = limits.LimitsController()
2024+ mapper.resource("limit", "limits", controller=_limits)
2025+
2026+
2027+class APIRouterV10(APIRouter):
2028+ """Define routes specific to OpenStack API V1.0."""
2029+
2030+ def _setup_routes(self, mapper):
2031+ super(APIRouterV10, self)._setup_routes(mapper)
2032+ mapper.resource("server", "servers",
2033+ controller=servers.ControllerV10(),
2034+ collection={'detail': 'GET'},
2035+ member=self.server_members)
2036+
2037+
2038+class APIRouterV11(APIRouter):
2039+ """Define routes specific to OpenStack API V1.1."""
2040+
2041+ def _setup_routes(self, mapper):
2042+ super(APIRouterV11, self)._setup_routes(mapper)
2043+ mapper.resource("server", "servers",
2044+ controller=servers.ControllerV11(),
2045+ collection={'detail': 'GET'},
2046+ member=self.server_members)
2047+ mapper.resource("server_meta", "meta",
2048+ controller=server_metadata.Controller(),
2049+ parent_resource=dict(member_name='server',
2050+ collection_name='servers'))
2051
2052
2053 class Versions(wsgi.Application):
2054@@ -128,8 +181,11 @@
2055 def __call__(self, req):
2056 """Respond to a request for all OpenStack API versions."""
2057 response = {
2058- "versions": [
2059- dict(status="CURRENT", id="v1.0")]}
2060+ "versions": [
2061+ dict(status="DEPRECATED", id="v1.0"),
2062+ dict(status="CURRENT", id="v1.1"),
2063+ ],
2064+ }
2065 metadata = {
2066 "application/xml": {
2067 "attributes": dict(version=["status", "id"])}}
2068
2069=== modified file 'nova/api/openstack/accounts.py'
2070--- nova/api/openstack/accounts.py 2011-03-11 19:49:32 +0000
2071+++ nova/api/openstack/accounts.py 2011-03-25 13:43:55 +0000
2072@@ -1,85 +1,174 @@
2073-# Copyright 2011 OpenStack LLC.
2074-# All Rights Reserved.
2075-#
2076-# Licensed under the Apache License, Version 2.0 (the "License"); you may
2077-# not use this file except in compliance with the License. You may obtain
2078-# a copy of the License at
2079-#
2080-# http://www.apache.org/licenses/LICENSE-2.0
2081-#
2082-# Unless required by applicable law or agreed to in writing, software
2083-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2084-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2085-# License for the specific language governing permissions and limitations
2086-# under the License.
2087-
2088-import common
2089-
2090-from nova import exception
2091-from nova import flags
2092-from nova import log as logging
2093-from nova import wsgi
2094-
2095-from nova.auth import manager
2096-from nova.api.openstack import faults
2097-
2098-FLAGS = flags.FLAGS
2099-LOG = logging.getLogger('nova.api.openstack')
2100-
2101-
2102-def _translate_keys(account):
2103- return dict(id=account.id,
2104- name=account.name,
2105- description=account.description,
2106- manager=account.project_manager_id)
2107-
2108-
2109-class Controller(wsgi.Controller):
2110-
2111- _serialization_metadata = {
2112- 'application/xml': {
2113- "attributes": {
2114- "account": ["id", "name", "description", "manager"]}}}
2115-
2116- def __init__(self):
2117- self.manager = manager.AuthManager()
2118-
2119- def _check_admin(self, context):
2120- """We cannot depend on the db layer to check for admin access
2121- for the auth manager, so we do it here"""
2122- if not context.is_admin:
2123- raise exception.NotAuthorized(_("Not admin user."))
2124-
2125- def index(self, req):
2126- raise faults.Fault(exc.HTTPNotImplemented())
2127-
2128- def detail(self, req):
2129- raise faults.Fault(exc.HTTPNotImplemented())
2130-
2131- def show(self, req, id):
2132- """Return data about the given account id"""
2133- account = self.manager.get_project(id)
2134- return dict(account=_translate_keys(account))
2135-
2136- def delete(self, req, id):
2137- self._check_admin(req.environ['nova.context'])
2138- self.manager.delete_project(id)
2139- return {}
2140-
2141- def create(self, req):
2142- """We use update with create-or-update semantics
2143- because the id comes from an external source"""
2144- raise faults.Fault(exc.HTTPNotImplemented())
2145-
2146- def update(self, req, id):
2147- """This is really create or update."""
2148- self._check_admin(req.environ['nova.context'])
2149- env = self._deserialize(req.body, req.get_content_type())
2150- description = env['account'].get('description')
2151- manager = env['account'].get('manager')
2152- try:
2153- account = self.manager.get_project(id)
2154- self.manager.modify_project(id, manager, description)
2155- except exception.NotFound:
2156- account = self.manager.create_project(id, manager, description)
2157- return dict(account=_translate_keys(account))
2158+<<<<<<< TREE
2159+# Copyright 2011 OpenStack LLC.
2160+# All Rights Reserved.
2161+#
2162+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2163+# not use this file except in compliance with the License. You may obtain
2164+# a copy of the License at
2165+#
2166+# http://www.apache.org/licenses/LICENSE-2.0
2167+#
2168+# Unless required by applicable law or agreed to in writing, software
2169+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2170+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2171+# License for the specific language governing permissions and limitations
2172+# under the License.
2173+
2174+import common
2175+
2176+from nova import exception
2177+from nova import flags
2178+from nova import log as logging
2179+from nova import wsgi
2180+
2181+from nova.auth import manager
2182+from nova.api.openstack import faults
2183+
2184+FLAGS = flags.FLAGS
2185+LOG = logging.getLogger('nova.api.openstack')
2186+
2187+
2188+def _translate_keys(account):
2189+ return dict(id=account.id,
2190+ name=account.name,
2191+ description=account.description,
2192+ manager=account.project_manager_id)
2193+
2194+
2195+class Controller(wsgi.Controller):
2196+
2197+ _serialization_metadata = {
2198+ 'application/xml': {
2199+ "attributes": {
2200+ "account": ["id", "name", "description", "manager"]}}}
2201+
2202+ def __init__(self):
2203+ self.manager = manager.AuthManager()
2204+
2205+ def _check_admin(self, context):
2206+ """We cannot depend on the db layer to check for admin access
2207+ for the auth manager, so we do it here"""
2208+ if not context.is_admin:
2209+ raise exception.NotAuthorized(_("Not admin user."))
2210+
2211+ def index(self, req):
2212+ raise faults.Fault(exc.HTTPNotImplemented())
2213+
2214+ def detail(self, req):
2215+ raise faults.Fault(exc.HTTPNotImplemented())
2216+
2217+ def show(self, req, id):
2218+ """Return data about the given account id"""
2219+ account = self.manager.get_project(id)
2220+ return dict(account=_translate_keys(account))
2221+
2222+ def delete(self, req, id):
2223+ self._check_admin(req.environ['nova.context'])
2224+ self.manager.delete_project(id)
2225+ return {}
2226+
2227+ def create(self, req):
2228+ """We use update with create-or-update semantics
2229+ because the id comes from an external source"""
2230+ raise faults.Fault(exc.HTTPNotImplemented())
2231+
2232+ def update(self, req, id):
2233+ """This is really create or update."""
2234+ self._check_admin(req.environ['nova.context'])
2235+ env = self._deserialize(req.body, req.get_content_type())
2236+ description = env['account'].get('description')
2237+ manager = env['account'].get('manager')
2238+ try:
2239+ account = self.manager.get_project(id)
2240+ self.manager.modify_project(id, manager, description)
2241+ except exception.NotFound:
2242+ account = self.manager.create_project(id, manager, description)
2243+ return dict(account=_translate_keys(account))
2244+=======
2245+# Copyright 2011 OpenStack LLC.
2246+# All Rights Reserved.
2247+#
2248+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2249+# not use this file except in compliance with the License. You may obtain
2250+# a copy of the License at
2251+#
2252+# http://www.apache.org/licenses/LICENSE-2.0
2253+#
2254+# Unless required by applicable law or agreed to in writing, software
2255+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2256+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2257+# License for the specific language governing permissions and limitations
2258+# under the License.
2259+
2260+import common
2261+import webob.exc
2262+
2263+from nova import exception
2264+from nova import flags
2265+from nova import log as logging
2266+from nova import wsgi
2267+
2268+from nova.auth import manager
2269+from nova.api.openstack import faults
2270+
2271+FLAGS = flags.FLAGS
2272+LOG = logging.getLogger('nova.api.openstack')
2273+
2274+
2275+def _translate_keys(account):
2276+ return dict(id=account.id,
2277+ name=account.name,
2278+ description=account.description,
2279+ manager=account.project_manager_id)
2280+
2281+
2282+class Controller(wsgi.Controller):
2283+
2284+ _serialization_metadata = {
2285+ 'application/xml': {
2286+ "attributes": {
2287+ "account": ["id", "name", "description", "manager"]}}}
2288+
2289+ def __init__(self):
2290+ self.manager = manager.AuthManager()
2291+
2292+ def _check_admin(self, context):
2293+ """We cannot depend on the db layer to check for admin access
2294+ for the auth manager, so we do it here"""
2295+ if not context.is_admin:
2296+ raise exception.NotAuthorized(_("Not admin user."))
2297+
2298+ def index(self, req):
2299+ raise faults.Fault(webob.exc.HTTPNotImplemented())
2300+
2301+ def detail(self, req):
2302+ raise faults.Fault(webob.exc.HTTPNotImplemented())
2303+
2304+ def show(self, req, id):
2305+ """Return data about the given account id"""
2306+ account = self.manager.get_project(id)
2307+ return dict(account=_translate_keys(account))
2308+
2309+ def delete(self, req, id):
2310+ self._check_admin(req.environ['nova.context'])
2311+ self.manager.delete_project(id)
2312+ return {}
2313+
2314+ def create(self, req):
2315+ """We use update with create-or-update semantics
2316+ because the id comes from an external source"""
2317+ raise faults.Fault(webob.exc.HTTPNotImplemented())
2318+
2319+ def update(self, req, id):
2320+ """This is really create or update."""
2321+ self._check_admin(req.environ['nova.context'])
2322+ env = self._deserialize(req.body, req.get_content_type())
2323+ description = env['account'].get('description')
2324+ manager = env['account'].get('manager')
2325+ try:
2326+ account = self.manager.get_project(id)
2327+ self.manager.modify_project(id, manager, description)
2328+ except exception.NotFound:
2329+ account = self.manager.create_project(id, manager, description)
2330+ return dict(account=_translate_keys(account))
2331+>>>>>>> MERGE-SOURCE
2332
2333=== modified file 'nova/api/openstack/auth.py'
2334--- nova/api/openstack/auth.py 2011-03-11 19:49:32 +0000
2335+++ nova/api/openstack/auth.py 2011-03-25 13:43:55 +0000
2336@@ -135,8 +135,17 @@
2337 req - wsgi.Request object
2338 """
2339 ctxt = context.get_admin_context()
2340+<<<<<<< TREE
2341 user = self.auth.get_user_from_access_key(key)
2342
2343+=======
2344+
2345+ try:
2346+ user = self.auth.get_user_from_access_key(key)
2347+ except exception.NotFound:
2348+ user = None
2349+
2350+>>>>>>> MERGE-SOURCE
2351 if user and user.name == username:
2352 token_hash = hashlib.sha1('%s%s%f' % (username, key,
2353 time.time())).hexdigest()
2354
2355=== modified file 'nova/api/openstack/common.py'
2356--- nova/api/openstack/common.py 2011-03-09 22:10:24 +0000
2357+++ nova/api/openstack/common.py 2011-03-25 13:43:55 +0000
2358@@ -15,9 +15,17 @@
2359 # License for the specific language governing permissions and limitations
2360 # under the License.
2361
2362+<<<<<<< TREE
2363 import webob.exc
2364
2365+=======
2366+from urlparse import urlparse
2367+
2368+import webob
2369+
2370+>>>>>>> MERGE-SOURCE
2371 from nova import exception
2372+<<<<<<< TREE
2373
2374
2375 def limited(items, request, max_limit=1000):
2376@@ -50,10 +58,77 @@
2377 raise webob.exc.HTTPBadRequest(_('offset param must be positive'))
2378
2379 limit = min(max_limit, limit or max_limit)
2380+=======
2381+from nova import flags
2382+
2383+FLAGS = flags.FLAGS
2384+
2385+
2386+def limited(items, request, max_limit=FLAGS.osapi_max_limit):
2387+ """
2388+ Return a slice of items according to requested offset and limit.
2389+
2390+ @param items: A sliceable entity
2391+ @param request: `wsgi.Request` possibly containing 'offset' and 'limit'
2392+ GET variables. 'offset' is where to start in the list,
2393+ and 'limit' is the maximum number of items to return. If
2394+ 'limit' is not specified, 0, or > max_limit, we default
2395+ to max_limit. Negative values for either offset or limit
2396+ will cause exc.HTTPBadRequest() exceptions to be raised.
2397+ @kwarg max_limit: The maximum number of items to return from 'items'
2398+ """
2399+ try:
2400+ offset = int(request.GET.get('offset', 0))
2401+ except ValueError:
2402+ raise webob.exc.HTTPBadRequest(_('offset param must be an integer'))
2403+
2404+ try:
2405+ limit = int(request.GET.get('limit', max_limit))
2406+ except ValueError:
2407+ raise webob.exc.HTTPBadRequest(_('limit param must be an integer'))
2408+
2409+ if limit < 0:
2410+ raise webob.exc.HTTPBadRequest(_('limit param must be positive'))
2411+
2412+ if offset < 0:
2413+ raise webob.exc.HTTPBadRequest(_('offset param must be positive'))
2414+
2415+ limit = min(max_limit, limit or max_limit)
2416+>>>>>>> MERGE-SOURCE
2417 range_end = offset + limit
2418 return items[offset:range_end]
2419
2420
2421+def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit):
2422+ """Return a slice of items according to the requested marker and limit."""
2423+
2424+ try:
2425+ marker = int(request.GET.get('marker', 0))
2426+ except ValueError:
2427+ raise webob.exc.HTTPBadRequest(_('marker param must be an integer'))
2428+
2429+ try:
2430+ limit = int(request.GET.get('limit', max_limit))
2431+ except ValueError:
2432+ raise webob.exc.HTTPBadRequest(_('limit param must be an integer'))
2433+
2434+ if limit < 0:
2435+ raise webob.exc.HTTPBadRequest(_('limit param must be positive'))
2436+
2437+ limit = min(max_limit, limit)
2438+ start_index = 0
2439+ if marker:
2440+ start_index = -1
2441+ for i, item in enumerate(items):
2442+ if item['id'] == marker:
2443+ start_index = i + 1
2444+ break
2445+ if start_index < 0:
2446+ raise webob.exc.HTTPBadRequest(_('marker [%s] not found' % marker))
2447+ range_end = start_index + limit
2448+ return items[start_index:range_end]
2449+
2450+
2451 def get_image_id_from_image_hash(image_service, context, image_hash):
2452 """Given an Image ID Hash, return an objectstore Image ID.
2453
2454@@ -74,3 +149,16 @@
2455 if abs(hash(image_id)) == int(image_hash):
2456 return image_id
2457 raise exception.NotFound(image_hash)
2458+
2459+
2460+def get_id_from_href(href):
2461+ """Return the id portion of a url as an int.
2462+
2463+ Given: http://www.foo.com/bar/123?q=4
2464+ Returns: 123
2465+
2466+ """
2467+ try:
2468+ return int(urlparse(href).path.split('/')[-1])
2469+ except:
2470+ raise webob.exc.HTTPBadRequest(_('could not parse id from href'))
2471
2472=== added file 'nova/api/openstack/extensions.py'
2473--- nova/api/openstack/extensions.py 1970-01-01 00:00:00 +0000
2474+++ nova/api/openstack/extensions.py 2011-03-25 13:43:55 +0000
2475@@ -0,0 +1,369 @@
2476+# vim: tabstop=4 shiftwidth=4 softtabstop=4
2477+
2478+# Copyright 2011 OpenStack LLC.
2479+# All Rights Reserved.
2480+#
2481+# Licensed under the Apache License, Version 2.0 (the "License"); you may
2482+# not use this file except in compliance with the License. You may obtain
2483+# a copy of the License at
2484+#
2485+# http://www.apache.org/licenses/LICENSE-2.0
2486+#
2487+# Unless required by applicable law or agreed to in writing, software
2488+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2489+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2490+# License for the specific language governing permissions and limitations
2491+# under the License.
2492+
2493+import imp
2494+import os
2495+import sys
2496+import routes
2497+import webob.dec
2498+import webob.exc
2499+
2500+from nova import flags
2501+from nova import log as logging
2502+from nova import wsgi
2503+from nova.api.openstack import faults
2504+
2505+
2506+LOG = logging.getLogger('extensions')
2507+
2508+
2509+FLAGS = flags.FLAGS
2510+
2511+
2512+class ActionExtensionController(wsgi.Controller):
2513+
2514+ def __init__(self, application):
2515+
2516+ self.application = application
2517+ self.action_handlers = {}
2518+
2519+ def add_action(self, action_name, handler):
2520+ self.action_handlers[action_name] = handler
2521+
2522+ def action(self, req, id):
2523+
2524+ input_dict = self._deserialize(req.body, req.get_content_type())
2525+ for action_name, handler in self.action_handlers.iteritems():
2526+ if action_name in input_dict:
2527+ return handler(input_dict, req, id)
2528+ # no action handler found (bump to downstream application)
2529+ res = self.application
2530+ return res
2531+
2532+
2533+class ResponseExtensionController(wsgi.Controller):
2534+
2535+ def __init__(self, application):
2536+ self.application = application
2537+ self.handlers = []
2538+
2539+ def add_handler(self, handler):
2540+ self.handlers.append(handler)
2541+
2542+ def process(self, req, *args, **kwargs):
2543+ res = req.get_response(self.application)
2544+ content_type = req.best_match_content_type()
2545+ # currently response handlers are un-ordered
2546+ for handler in self.handlers:
2547+ res = handler(res)
2548+ try:
2549+ body = res.body
2550+ headers = res.headers
2551+ except AttributeError:
2552+ body = self._serialize(res, content_type)
2553+ headers = {"Content-Type": content_type}
2554+ res = webob.Response()
2555+ res.body = body
2556+ res.headers = headers
2557+ return res
2558+
2559+
2560+class ExtensionController(wsgi.Controller):
2561+
2562+ def __init__(self, extension_manager):
2563+ self.extension_manager = extension_manager
2564+
2565+ def _translate(self, ext):
2566+ ext_data = {}
2567+ ext_data['name'] = ext.get_name()
2568+ ext_data['alias'] = ext.get_alias()
2569+ ext_data['description'] = ext.get_description()
2570+ ext_data['namespace'] = ext.get_namespace()
2571+ ext_data['updated'] = ext.get_updated()
2572+ ext_data['links'] = [] # TODO: implement extension links
2573+ return ext_data
2574+
2575+ def index(self, req):
2576+ extensions = []
2577+ for alias, ext in self.extension_manager.extensions.iteritems():
2578+ extensions.append(self._translate(ext))
2579+ return dict(extensions=extensions)
2580+
2581+ def show(self, req, id):
2582+ # NOTE: the extensions alias is used as the 'id' for show
2583+ ext = self.extension_manager.extensions[id]
2584+ return self._translate(ext)
2585+
2586+ def delete(self, req, id):
2587+ raise faults.Fault(exc.HTTPNotFound())
2588+
2589+ def create(self, req):
2590+ raise faults.Fault(exc.HTTPNotFound())
2591+
2592+ def delete(self, req, id):
2593+ raise faults.Fault(exc.HTTPNotFound())
2594+
2595+
2596+class ExtensionMiddleware(wsgi.Middleware):
2597+ """
2598+ Extensions middleware that intercepts configured routes for extensions.
2599+ """
2600+ @classmethod
2601+ def factory(cls, global_config, **local_config):
2602+ """ paste factory """
2603+ def _factory(app):
2604+ return cls(app, **local_config)
2605+ return _factory
2606+
2607+ def _action_ext_controllers(self, application, ext_mgr, mapper):
2608+ """
2609+ Return a dict of ActionExtensionController objects by collection
2610+ """
2611+ action_controllers = {}
2612+ for action in ext_mgr.get_actions():
2613+ if not action.collection in action_controllers.keys():
2614+ controller = ActionExtensionController(application)
2615+ mapper.connect("/%s/:(id)/action.:(format)" %
2616+ action.collection,
2617+ action='action',
2618+ controller=controller,
2619+ conditions=dict(method=['POST']))
2620+ mapper.connect("/%s/:(id)/action" % action.collection,
2621+ action='action',
2622+ controller=controller,
2623+ conditions=dict(method=['POST']))
2624+ action_controllers[action.collection] = controller
2625+
2626+ return action_controllers
2627+
2628+ def _response_ext_controllers(self, application, ext_mgr, mapper):
2629+ """
2630+ Return a dict of ResponseExtensionController objects by collection
2631+ """
2632+ response_ext_controllers = {}
2633+ for resp_ext in ext_mgr.get_response_extensions():
2634+ if not resp_ext.key in response_ext_controllers.keys():
2635+ controller = ResponseExtensionController(application)
2636+ mapper.connect(resp_ext.url_route + '.:(format)',
2637+ action='process',
2638+ controller=controller,
2639+ conditions=resp_ext.conditions)
2640+
2641+ mapper.connect(resp_ext.url_route,
2642+ action='process',
2643+ controller=controller,
2644+ conditions=resp_ext.conditions)
2645+ response_ext_controllers[resp_ext.key] = controller
2646+
2647+ return response_ext_controllers
2648+
2649+ def __init__(self, application, ext_mgr=None):
2650+
2651+ if ext_mgr is None:
2652+ ext_mgr = ExtensionManager(FLAGS.osapi_extensions_path)
2653+ self.ext_mgr = ext_mgr
2654+
2655+ mapper = routes.Mapper()
2656+
2657+ # extended resources
2658+ for resource in ext_mgr.get_resources():
2659+ LOG.debug(_('Extended resource: %s'),
2660+ resource.collection)
2661+ mapper.resource(resource.collection, resource.collection,
2662+ controller=resource.controller,
2663+ collection=resource.collection_actions,
2664+ member=resource.member_actions,
2665+ parent_resource=resource.parent)
2666+
2667+ # extended actions
2668+ action_controllers = self._action_ext_controllers(application, ext_mgr,
2669+ mapper)
2670+ for action in ext_mgr.get_actions():
2671+ LOG.debug(_('Extended action: %s'), action.action_name)
2672+ controller = action_controllers[action.collection]
2673+ controller.add_action(action.action_name, action.handler)
2674+
2675+ # extended responses
2676+ resp_controllers = self._response_ext_controllers(application, ext_mgr,
2677+ mapper)
2678+ for response_ext in ext_mgr.get_response_extensions():
2679+ LOG.debug(_('Extended response: %s'), response_ext.key)
2680+ controller = resp_controllers[response_ext.key]
2681+ controller.add_handler(response_ext.handler)
2682+
2683+ self._router = routes.middleware.RoutesMiddleware(self._dispatch,
2684+ mapper)
2685+
2686+ super(ExtensionMiddleware, self).__init__(application)
2687+
2688+ @webob.dec.wsgify(RequestClass=wsgi.Request)
2689+ def __call__(self, req):
2690+ """
2691+ Route the incoming request with router.
2692+ """
2693+ req.environ['extended.app'] = self.application
2694+ return self._router
2695+
2696+ @staticmethod
2697+ @webob.dec.wsgify(RequestClass=wsgi.Request)
2698+ def _dispatch(req):
2699+ """
2700+ Returns the routed WSGI app's response or defers to the extended
2701+ application.
2702+ """
2703+ match = req.environ['wsgiorg.routing_args'][1]
2704+ if not match:
2705+ return req.environ['extended.app']
2706+ app = match['controller']
2707+ return app
2708+
2709+
2710+class ExtensionManager(object):
2711+ """
2712+ Load extensions from the configured extension path.
2713+ See nova/tests/api/openstack/extensions/foxinsocks.py for an example
2714+ extension implementation.
2715+ """
2716+
2717+ def __init__(self, path):
2718+ LOG.audit(_('Initializing extension manager.'))
2719+
2720+ self.path = path
2721+ self.extensions = {}
2722+ self._load_extensions()
2723+
2724+ def get_resources(self):
2725+ """
2726+ returns a list of ResourceExtension objects
2727+ """
2728+ resources = []
2729+ resources.append(ResourceExtension('extensions',
2730+ ExtensionController(self)))
2731+ for alias, ext in self.extensions.iteritems():
2732+ try:
2733+ resources.extend(ext.get_resources())
2734+ except AttributeError:
2735+ # NOTE: Extension aren't required to have resource extensions
2736+ pass
2737+ return resources
2738+
2739+ def get_actions(self):
2740+ """
2741+ returns a list of ActionExtension objects
2742+ """
2743+ actions = []
2744+ for alias, ext in self.extensions.iteritems():
2745+ try:
2746+ actions.extend(ext.get_actions())
2747+ except AttributeError:
2748+ # NOTE: Extension aren't required to have action extensions
2749+ pass
2750+ return actions
2751+
2752+ def get_response_extensions(self):
2753+ """
2754+ returns a list of ResponseExtension objects
2755+ """
2756+ response_exts = []
2757+ for alias, ext in self.extensions.iteritems():
2758+ try:
2759+ response_exts.extend(ext.get_response_extensions())
2760+ except AttributeError:
2761+ # NOTE: Extension aren't required to have response extensions
2762+ pass
2763+ return response_exts
2764+
2765+ def _check_extension(self, extension):
2766+ """
2767+ Checks for required methods in extension objects.
2768+ """
2769+ try:
2770+ LOG.debug(_('Ext name: %s'), extension.get_name())
2771+ LOG.debug(_('Ext alias: %s'), extension.get_alias())
2772+ LOG.debug(_('Ext description: %s'), extension.get_description())
2773+ LOG.debug(_('Ext namespace: %s'), extension.get_namespace())
2774+ LOG.debug(_('Ext updated: %s'), extension.get_updated())
2775+ except AttributeError as ex:
2776+ LOG.exception(_("Exception loading extension: %s"), unicode(ex))
2777+
2778+ def _load_extensions(self):
2779+ """
2780+ Load extensions from the configured path. The extension name is
2781+ constructed from the module_name. If your extension module was named
2782+ widgets.py the extension class within that module should be
2783+ 'Widgets'.
2784+
2785+ See nova/tests/api/openstack/extensions/foxinsocks.py for an example
2786+ extension implementation.
2787+ """
2788+ if not os.path.exists(self.path):
2789+ return
2790+
2791+ for f in os.listdir(self.path):
2792+ LOG.audit(_('Loading extension file: %s'), f)
2793+ mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
2794+ ext_path = os.path.join(self.path, f)
2795+ if file_ext.lower() == '.py':
2796+ mod = imp.load_source(mod_name, ext_path)
2797+ ext_name = mod_name[0].upper() + mod_name[1:]
2798+ try:
2799+ new_ext = getattr(mod, ext_name)()
2800+ self._check_extension(new_ext)
2801+ self.extensions[new_ext.get_alias()] = new_ext
2802+ except AttributeError as ex:
2803+ LOG.exception(_("Exception loading extension: %s"),
2804+ unicode(ex))
2805+
2806+
2807+class ResponseExtension(object):
2808+ """
2809+ ResponseExtension objects can be used to add data to responses from
2810+ core nova OpenStack API controllers.
2811+ """
2812+
2813+ def __init__(self, method, url_route, handler):
2814+ self.url_route = url_route
2815+ self.handler = handler
2816+ self.conditions = dict(method=[method])
2817+ self.key = "%s-%s" % (method, url_route)
2818+
2819+
2820+class ActionExtension(object):
2821+ """
2822+ ActionExtension objects can be used to add custom actions to core nova
2823+ nova OpenStack API controllers.
2824+ """
2825+
2826+ def __init__(self, collection, action_name, handler):
2827+ self.collection = collection
2828+ self.action_name = action_name
2829+ self.handler = handler
2830+
2831+
2832+class ResourceExtension(object):
2833+ """
2834+ ResourceExtension objects can be used to add top level resources
2835+ to the OpenStack API in nova.
2836+ """
2837+
2838+ def __init__(self, collection, controller, parent=None,
2839+ collection_actions={}, member_actions={}):
2840+ self.collection = collection
2841+ self.controller = controller
2842+ self.parent = parent
2843+ self.collection_actions = collection_actions
2844+ self.member_actions = member_actions
2845
2846=== modified file 'nova/api/openstack/faults.py'
2847--- nova/api/openstack/faults.py 2011-03-09 20:08:11 +0000
2848+++ nova/api/openstack/faults.py 2011-03-25 13:43:55 +0000
2849@@ -57,7 +57,52 @@
2850 fault_data[fault_name]['retryAfter'] = retry
2851 # 'code' is an attribute on the fault tag itself
2852 metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}
2853- serializer = wsgi.Serializer(metadata)
2854- content_type = req.best_match_content_type()
2855- self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
2856+<<<<<<< TREE
2857+ serializer = wsgi.Serializer(metadata)
2858+ content_type = req.best_match_content_type()
2859+ self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
2860+=======
2861+ serializer = wsgi.Serializer(metadata)
2862+ content_type = req.best_match_content_type()
2863+ self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
2864+ return self.wrapped_exc
2865+
2866+
2867+class OverLimitFault(webob.exc.HTTPException):
2868+ """
2869+ Rate-limited request response.
2870+ """
2871+
2872+ _serialization_metadata = {
2873+ "application/xml": {
2874+ "attributes": {
2875+ "overLimitFault": "code",
2876+ },
2877+ },
2878+ }
2879+
2880+ def __init__(self, message, details, retry_time):
2881+ """
2882+ Initialize new `OverLimitFault` with relevant information.
2883+ """
2884+ self.wrapped_exc = webob.exc.HTTPForbidden()
2885+ self.content = {
2886+ "overLimitFault": {
2887+ "code": self.wrapped_exc.status_int,
2888+ "message": message,
2889+ "details": details,
2890+ },
2891+ }
2892+
2893+ @webob.dec.wsgify(RequestClass=wsgi.Request)
2894+ def __call__(self, request):
2895+ """
2896+ Return the wrapped exception with a serialized body conforming to our
2897+ error format.
2898+ """
2899+ serializer = wsgi.Serializer(self._serialization_metadata)
2900+ content_type = request.best_match_content_type()
2901+ content = serializer.serialize(self.content, content_type)
2902+ self.wrapped_exc.body = content
2903+>>>>>>> MERGE-SOURCE
2904 return self.wrapped_exc
2905
2906=== modified file 'nova/api/openstack/flavors.py'
2907--- nova/api/openstack/flavors.py 2011-03-09 18:10:45 +0000
2908+++ nova/api/openstack/flavors.py 2011-03-25 13:43:55 +0000
2909@@ -22,6 +22,7 @@
2910 from nova.api.openstack import faults
2911 from nova.api.openstack import common
2912 from nova.compute import instance_types
2913+from nova.api.openstack.views import flavors as flavors_views
2914 from nova import wsgi
2915 import nova.api.openstack
2916
2917@@ -46,14 +47,33 @@
2918
2919 def show(self, req, id):
2920 """Return data about the given flavor id."""
2921+<<<<<<< TREE
2922 ctxt = req.environ['nova.context']
2923 values = db.instance_type_get_by_flavor_id(ctxt, id)
2924 return dict(flavor=values)
2925 raise faults.Fault(exc.HTTPNotFound())
2926+=======
2927+ ctxt = req.environ['nova.context']
2928+ flavor = db.api.instance_type_get_by_flavor_id(ctxt, id)
2929+ values = {
2930+ "id": flavor["flavorid"],
2931+ "name": flavor["name"],
2932+ "ram": flavor["memory_mb"],
2933+ "disk": flavor["local_gb"],
2934+ }
2935+ return dict(flavor=values)
2936+>>>>>>> MERGE-SOURCE
2937
2938 def _all_ids(self, req):
2939 """Return the list of all flavorids."""
2940+<<<<<<< TREE
2941 ctxt = req.environ['nova.context']
2942 inst_types = db.instance_type_get_all(ctxt)
2943 flavor_ids = [inst_types[i]['flavorid'] for i in inst_types.keys()]
2944 return sorted(flavor_ids)
2945+=======
2946+ ctxt = req.environ['nova.context']
2947+ inst_types = db.api.instance_type_get_all(ctxt)
2948+ flavor_ids = [inst_types[i]['flavorid'] for i in inst_types.keys()]
2949+ return sorted(flavor_ids)
2950+>>>>>>> MERGE-SOURCE
2951
2952=== modified file 'nova/api/openstack/images.py'
2953--- nova/api/openstack/images.py 2011-03-11 19:49:32 +0000
2954+++ nova/api/openstack/images.py 2011-03-25 13:43:55 +0000
2955@@ -15,10 +15,17 @@
2956 # License for the specific language governing permissions and limitations
2957 # under the License.
2958
2959+<<<<<<< TREE
2960+=======
2961+import datetime
2962+
2963+>>>>>>> MERGE-SOURCE
2964 from webob import exc
2965
2966 from nova import compute
2967+from nova import exception
2968 from nova import flags
2969+from nova import log
2970 from nova import utils
2971 from nova import wsgi
2972 import nova.api.openstack
2973@@ -27,6 +34,8 @@
2974 import nova.image.service
2975
2976
2977+LOG = log.getLogger('nova.api.openstack.images')
2978+
2979 FLAGS = flags.FLAGS
2980
2981
2982@@ -84,8 +93,6 @@
2983 # S3ImageService
2984 pass
2985
2986- return item
2987-
2988
2989 def _filter_keys(item, keys):
2990 """
2991@@ -104,6 +111,100 @@
2992 image['id'] = image_id
2993
2994
2995+def _translate_s3_like_images(image_metadata):
2996+ """Work-around for leaky S3ImageService abstraction"""
2997+ api_metadata = image_metadata.copy()
2998+ _convert_image_id_to_hash(api_metadata)
2999+ api_metadata = _translate_keys(api_metadata)
3000+ _translate_status(api_metadata)
3001+ return api_metadata
3002+
3003+
3004+def _translate_from_image_service_to_api(image_metadata):
3005+ """Translate from ImageService to OpenStack API style attribute names
3006+
3007+ This involves 4 steps:
3008+
3009+ 1. Filter out attributes that the OpenStack API doesn't need
3010+
3011+ 2. Translate from base image attributes from names used by
3012+ BaseImageService to names used by OpenStack API
3013+
3014+ 3. Add in any image properties
3015+
3016+ 4. Format values according to API spec (for example dates must
3017+ look like "2010-08-10T12:00:00Z")
3018+ """
3019+ service_metadata = image_metadata.copy()
3020+ properties = service_metadata.pop('properties', {})
3021+
3022+ # 1. Filter out unecessary attributes
3023+ api_keys = ['id', 'name', 'updated_at', 'created_at', 'status']
3024+ api_metadata = utils.subset_dict(service_metadata, api_keys)
3025+
3026+ # 2. Translate base image attributes
3027+ api_map = {'updated_at': 'updated', 'created_at': 'created'}
3028+ api_metadata = utils.map_dict_keys(api_metadata, api_map)
3029+
3030+ # 3. Add in any image properties
3031+ # 3a. serverId is used for backups and snapshots
3032+ try:
3033+ api_metadata['serverId'] = int(properties['instance_id'])
3034+ except KeyError:
3035+ pass # skip if it's not present
3036+ except ValueError:
3037+ pass # skip if it's not an integer
3038+
3039+ # 3b. Progress special case
3040+ # TODO(sirp): ImageService doesn't have a notion of progress yet, so for
3041+ # now just fake it
3042+ if service_metadata['status'] == 'saving':
3043+ api_metadata['progress'] = 0
3044+
3045+ # 4. Format values
3046+ # 4a. Format Image Status (API requires uppercase)
3047+ api_metadata['status'] = _format_status_for_api(api_metadata['status'])
3048+
3049+ # 4b. Format timestamps
3050+ for attr in ('created', 'updated'):
3051+ if attr in api_metadata:
3052+ api_metadata[attr] = _format_datetime_for_api(
3053+ api_metadata[attr])
3054+
3055+ return api_metadata
3056+
3057+
3058+def _format_status_for_api(status):
3059+ """Return status in a format compliant with OpenStack API"""
3060+ mapping = {'queued': 'QUEUED',
3061+ 'preparing': 'PREPARING',
3062+ 'saving': 'SAVING',
3063+ 'active': 'ACTIVE',
3064+ 'killed': 'FAILED'}
3065+ return mapping[status]
3066+
3067+
3068+def _format_datetime_for_api(datetime_):
3069+ """Stringify datetime objects in a format compliant with OpenStack API"""
3070+ API_DATETIME_FMT = '%Y-%m-%dT%H:%M:%SZ'
3071+ return datetime_.strftime(API_DATETIME_FMT)
3072+
3073+
3074+def _safe_translate(image_metadata):
3075+ """Translate attributes for OpenStack API, temporary workaround for
3076+ S3ImageService attribute leakage.
3077+ """
3078+ # FIXME(sirp): The S3ImageService appears to be leaking implementation
3079+ # details, including its internal attribute names, and internal
3080+ # `status` values. Working around it for now.
3081+ s3_like_image = ('imageId' in image_metadata)
3082+ if s3_like_image:
3083+ translate = _translate_s3_like_images
3084+ else:
3085+ translate = _translate_from_image_service_to_api
3086+ return translate(image_metadata)
3087+
3088+
3089 class Controller(wsgi.Controller):
3090
3091 _serialization_metadata = {
3092@@ -117,33 +218,32 @@
3093
3094 def index(self, req):
3095 """Return all public images in brief"""
3096- items = self._service.index(req.environ['nova.context'])
3097- items = common.limited(items, req)
3098- items = [_filter_keys(item, ('id', 'name')) for item in items]
3099- return dict(images=items)
3100+ context = req.environ['nova.context']
3101+ image_metas = self._service.index(context)
3102+ image_metas = common.limited(image_metas, req)
3103+ return dict(images=image_metas)
3104
3105 def detail(self, req):
3106 """Return all public images in detail"""
3107- try:
3108- items = self._service.detail(req.environ['nova.context'])
3109- except NotImplementedError:
3110- items = self._service.index(req.environ['nova.context'])
3111- for image in items:
3112- _convert_image_id_to_hash(image)
3113-
3114- items = common.limited(items, req)
3115- items = [_translate_keys(item) for item in items]
3116- items = [_translate_status(item) for item in items]
3117- return dict(images=items)
3118+ context = req.environ['nova.context']
3119+ image_metas = self._service.detail(context)
3120+ image_metas = common.limited(image_metas, req)
3121+ api_image_metas = [_safe_translate(image_meta)
3122+ for image_meta in image_metas]
3123+ return dict(images=api_image_metas)
3124
3125 def show(self, req, id):
3126 """Return data about the given image id"""
3127- image_id = common.get_image_id_from_image_hash(self._service,
3128- req.environ['nova.context'], id)
3129+ context = req.environ['nova.context']
3130+ try:
3131+ image_id = common.get_image_id_from_image_hash(
3132+ self._service, context, id)
3133+ except exception.NotFound:
3134+ raise faults.Fault(exc.HTTPNotFound())
3135
3136- image = self._service.show(req.environ['nova.context'], image_id)
3137- _convert_image_id_to_hash(image)
3138- return dict(image=image)
3139+ image_meta = self._service.show(context, image_id)
3140+ api_image_meta = _safe_translate(image_meta)
3141+ return dict(image=api_image_meta)
3142
3143 def delete(self, req, id):
3144 # Only public images are supported for now.
3145@@ -154,11 +254,10 @@
3146 env = self._deserialize(req.body, req.get_content_type())
3147 instance_id = env["image"]["serverId"]
3148 name = env["image"]["name"]
3149-
3150 image_meta = compute.API().snapshot(
3151 context, instance_id, name)
3152-
3153- return dict(image=image_meta)
3154+ api_image_meta = _safe_translate(image_meta)
3155+ return dict(image=api_image_meta)
3156
3157 def update(self, req, id):
3158 # Users may not modify public images, and that's all that
3159
3160=== added file 'nova/api/openstack/limits.py'
3161--- nova/api/openstack/limits.py 1970-01-01 00:00:00 +0000
3162+++ nova/api/openstack/limits.py 2011-03-25 13:43:55 +0000
3163@@ -0,0 +1,358 @@
3164+# Copyright 2011 OpenStack LLC.
3165+# All Rights Reserved.
3166+#
3167+# Licensed under the Apache License, Version 2.0 (the "License"); you may
3168+# not use this file except in compliance with the License. You may obtain
3169+# a copy of the License at
3170+#
3171+# http://www.apache.org/licenses/LICENSE-2.0
3172+#
3173+# Unless required by applicable law or agreed to in writing, software
3174+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3175+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3176+# License for the specific language governing permissions and limitations
3177+# under the License.import datetime
3178+
3179+"""
3180+Module dedicated functions/classes dealing with rate limiting requests.
3181+"""
3182+
3183+import copy
3184+import httplib
3185+import json
3186+import math
3187+import re
3188+import time
3189+import urllib
3190+import webob.exc
3191+
3192+from collections import defaultdict
3193+
3194+from webob.dec import wsgify
3195+
3196+from nova import wsgi
3197+from nova.api.openstack import faults
3198+from nova.wsgi import Controller
3199+from nova.wsgi import Middleware
3200+
3201+
3202+# Convenience constants for the limits dictionary passed to Limiter().
3203+PER_SECOND = 1
3204+PER_MINUTE = 60
3205+PER_HOUR = 60 * 60
3206+PER_DAY = 60 * 60 * 24
3207+
3208+
3209+class LimitsController(Controller):
3210+ """
3211+ Controller for accessing limits in the OpenStack API.
3212+ """
3213+
3214+ _serialization_metadata = {
3215+ "application/xml": {
3216+ "attributes": {
3217+ "limit": ["verb", "URI", "regex", "value", "unit",
3218+ "resetTime", "remaining", "name"],
3219+ },
3220+ "plurals": {
3221+ "rate": "limit",
3222+ },
3223+ },
3224+ }
3225+
3226+ def index(self, req):
3227+ """
3228+ Return all global and rate limit information.
3229+ """
3230+ abs_limits = {}
3231+ rate_limits = req.environ.get("nova.limits", [])
3232+
3233+ return {
3234+ "limits": {
3235+ "rate": rate_limits,
3236+ "absolute": abs_limits,
3237+ },
3238+ }
3239+
3240+
3241+class Limit(object):
3242+ """
3243+ Stores information about a limit for HTTP requets.
3244+ """
3245+
3246+ UNITS = {
3247+ 1: "SECOND",
3248+ 60: "MINUTE",
3249+ 60 * 60: "HOUR",
3250+ 60 * 60 * 24: "DAY",
3251+ }
3252+
3253+ def __init__(self, verb, uri, regex, value, unit):
3254+ """
3255+ Initialize a new `Limit`.
3256+
3257+ @param verb: HTTP verb (POST, PUT, etc.)
3258+ @param uri: Human-readable URI
3259+ @param regex: Regular expression format for this limit
3260+ @param value: Integer number of requests which can be made
3261+ @param unit: Unit of measure for the value parameter
3262+ """
3263+ self.verb = verb
3264+ self.uri = uri
3265+ self.regex = regex
3266+ self.value = int(value)
3267+ self.unit = unit
3268+ self.unit_string = self.display_unit().lower()
3269+ self.remaining = int(value)
3270+
3271+ if value <= 0:
3272+ raise ValueError("Limit value must be > 0")
3273+
3274+ self.last_request = None
3275+ self.next_request = None
3276+
3277+ self.water_level = 0
3278+ self.capacity = self.unit
3279+ self.request_value = float(self.capacity) / float(self.value)
3280+ self.error_message = _("Only %(value)s %(verb)s request(s) can be "\
3281+ "made to %(uri)s every %(unit_string)s." % self.__dict__)
3282+
3283+ def __call__(self, verb, url):
3284+ """
3285+ Represents a call to this limit from a relevant request.
3286+
3287+ @param verb: string http verb (POST, GET, etc.)
3288+ @param url: string URL
3289+ """
3290+ if self.verb != verb or not re.match(self.regex, url):
3291+ return
3292+
3293+ now = self._get_time()
3294+
3295+ if self.last_request is None:
3296+ self.last_request = now
3297+
3298+ leak_value = now - self.last_request
3299+
3300+ self.water_level -= leak_value
3301+ self.water_level = max(self.water_level, 0)
3302+ self.water_level += self.request_value
3303+
3304+ difference = self.water_level - self.capacity
3305+
3306+ self.last_request = now
3307+
3308+ if difference > 0:
3309+ self.water_level -= self.request_value
3310+ self.next_request = now + difference
3311+ return difference
3312+
3313+ cap = self.capacity
3314+ water = self.water_level
3315+ val = self.value
3316+
3317+ self.remaining = math.floor(((cap - water) / cap) * val)
3318+ self.next_request = now
3319+
3320+ def _get_time(self):
3321+ """Retrieve the current time. Broken out for testability."""
3322+ return time.time()
3323+
3324+ def display_unit(self):
3325+ """Display the string name of the unit."""
3326+ return self.UNITS.get(self.unit, "UNKNOWN")
3327+
3328+ def display(self):
3329+ """Return a useful representation of this class."""
3330+ return {
3331+ "verb": self.verb,
3332+ "URI": self.uri,
3333+ "regex": self.regex,
3334+ "value": self.value,
3335+ "remaining": int(self.remaining),
3336+ "unit": self.display_unit(),
3337+ "resetTime": int(self.next_request or self._get_time()),
3338+ }
3339+
3340+# "Limit" format is a dictionary with the HTTP verb, human-readable URI,
3341+# a regular-expression to match, value and unit of measure (PER_DAY, etc.)
3342+
3343+DEFAULT_LIMITS = [
3344+ Limit("POST", "*", ".*", 10, PER_MINUTE),
3345+ Limit("POST", "*/servers", "^/servers", 50, PER_DAY),
3346+ Limit("PUT", "*", ".*", 10, PER_MINUTE),
3347+ Limit("GET", "*changes-since*", ".*changes-since.*", 3, PER_MINUTE),
3348+ Limit("DELETE", "*", ".*", 100, PER_MINUTE),
3349+]
3350+
3351+
3352+class RateLimitingMiddleware(Middleware):
3353+ """
3354+ Rate-limits requests passing through this middleware. All limit information
3355+ is stored in memory for this implementation.
3356+ """
3357+
3358+ def __init__(self, application, limits=None):
3359+ """
3360+ Initialize new `RateLimitingMiddleware`, which wraps the given WSGI
3361+ application and sets up the given limits.
3362+
3363+ @param application: WSGI application to wrap
3364+ @param limits: List of dictionaries describing limits
3365+ """
3366+ Middleware.__init__(self, application)
3367+ self._limiter = Limiter(limits or DEFAULT_LIMITS)
3368+
3369+ @wsgify(RequestClass=wsgi.Request)
3370+ def __call__(self, req):
3371+ """
3372+ Represents a single call through this middleware. We should record the
3373+ request if we have a limit relevant to it. If no limit is relevant to
3374+ the request, ignore it.
3375+
3376+ If the request should be rate limited, return a fault telling the user
3377+ they are over the limit and need to retry later.
3378+ """
3379+ verb = req.method
3380+ url = req.url
3381+ context = req.environ.get("nova.context")
3382+
3383+ if context:
3384+ username = context.user_id
3385+ else:
3386+ username = None
3387+
3388+ delay, error = self._limiter.check_for_delay(verb, url, username)
3389+
3390+ if delay:
3391+ msg = _("This request was rate-limited.")
3392+ retry = time.time() + delay
3393+ return faults.OverLimitFault(msg, error, retry)
3394+
3395+ req.environ["nova.limits"] = self._limiter.get_limits(username)
3396+
3397+ return self.application
3398+
3399+
3400+class Limiter(object):
3401+ """
3402+ Rate-limit checking class which handles limits in memory.
3403+ """
3404+
3405+ def __init__(self, limits):
3406+ """
3407+ Initialize the new `Limiter`.
3408+
3409+ @param limits: List of `Limit` objects
3410+ """
3411+ self.limits = copy.deepcopy(limits)
3412+ self.levels = defaultdict(lambda: copy.deepcopy(limits))
3413+
3414+ def get_limits(self, username=None):
3415+ """
3416+ Return the limits for a given user.
3417+ """
3418+ return [limit.display() for limit in self.levels[username]]
3419+
3420+ def check_for_delay(self, verb, url, username=None):
3421+ """
3422+ Check the given verb/user/user triplet for limit.
3423+
3424+ @return: Tuple of delay (in seconds) and error message (or None, None)
3425+ """
3426+ delays = []
3427+
3428+ for limit in self.levels[username]:
3429+ delay = limit(verb, url)
3430+ if delay:
3431+ delays.append((delay, limit.error_message))
3432+
3433+ if delays:
3434+ delays.sort()
3435+ return delays[0]
3436+
3437+ return None, None
3438+
3439+
3440+class WsgiLimiter(object):
3441+ """
3442+ Rate-limit checking from a WSGI application. Uses an in-memory `Limiter`.
3443+
3444+ To use:
3445+ POST /<username> with JSON data such as:
3446+ {
3447+ "verb" : GET,
3448+ "path" : "/servers"
3449+ }
3450+
3451+ and receive a 204 No Content, or a 403 Forbidden with an X-Wait-Seconds
3452+ header containing the number of seconds to wait before the action would
3453+ succeed.
3454+ """
3455+
3456+ def __init__(self, limits=None):
3457+ """
3458+ Initialize the new `WsgiLimiter`.
3459+
3460+ @param limits: List of `Limit` objects
3461+ """
3462+ self._limiter = Limiter(limits or DEFAULT_LIMITS)
3463+
3464+ @wsgify(RequestClass=wsgi.Request)
3465+ def __call__(self, request):
3466+ """
3467+ Handles a call to this application. Returns 204 if the request is
3468+ acceptable to the limiter, else a 403 is returned with a relevant
3469+ header indicating when the request *will* succeed.
3470+ """
3471+ if request.method != "POST":
3472+ raise webob.exc.HTTPMethodNotAllowed()
3473+
3474+ try:
3475+ info = dict(json.loads(request.body))
3476+ except ValueError:
3477+ raise webob.exc.HTTPBadRequest()
3478+
3479+ username = request.path_info_pop()
3480+ verb = info.get("verb")
3481+ path = info.get("path")
3482+
3483+ delay, error = self._limiter.check_for_delay(verb, path, username)
3484+
3485+ if delay:
3486+ headers = {"X-Wait-Seconds": "%.2f" % delay}
3487+ return webob.exc.HTTPForbidden(headers=headers, explanation=error)
3488+ else:
3489+ return webob.exc.HTTPNoContent()
3490+
3491+
3492+class WsgiLimiterProxy(object):
3493+ """
3494+ Rate-limit requests based on answers from a remote source.
3495+ """
3496+
3497+ def __init__(self, limiter_address):
3498+ """
3499+ Initialize the new `WsgiLimiterProxy`.
3500+
3501+ @param limiter_address: IP/port combination of where to request limit
3502+ """
3503+ self.limiter_address = limiter_address
3504+
3505+ def check_for_delay(self, verb, path, username=None):
3506+ body = json.dumps({"verb": verb, "path": path})
3507+ headers = {"Content-Type": "application/json"}
3508+
3509+ conn = httplib.HTTPConnection(self.limiter_address)
3510+
3511+ if username:
3512+ conn.request("POST", "/%s" % (username), body, headers)
3513+ else:
3514+ conn.request("POST", "/", body, headers)
3515+
3516+ resp = conn.getresponse()
3517+
3518+ if 200 >= resp.status < 300:
3519+ return None, None
3520+
3521+ return resp.getheader("X-Wait-Seconds"), resp.read() or None
3522
3523=== added file 'nova/api/openstack/server_metadata.py'
3524--- nova/api/openstack/server_metadata.py 1970-01-01 00:00:00 +0000
3525+++ nova/api/openstack/server_metadata.py 2011-03-25 13:43:55 +0000
3526@@ -0,0 +1,78 @@
3527+# vim: tabstop=4 shiftwidth=4 softtabstop=4
3528+
3529+# Copyright 2011 OpenStack LLC.
3530+# All Rights Reserved.
3531+#
3532+# Licensed under the Apache License, Version 2.0 (the "License"); you may
3533+# not use this file except in compliance with the License. You may obtain
3534+# a copy of the License at
3535+#
3536+# http://www.apache.org/licenses/LICENSE-2.0
3537+#
3538+# Unless required by applicable law or agreed to in writing, software
3539+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3540+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3541+# License for the specific language governing permissions and limitations
3542+# under the License.
3543+
3544+from webob import exc
3545+
3546+from nova import compute
3547+from nova import wsgi
3548+from nova.api.openstack import faults
3549+
3550+
3551+class Controller(wsgi.Controller):
3552+ """ The server metadata API controller for the Openstack API """
3553+
3554+ def __init__(self):
3555+ self.compute_api = compute.API()
3556+ super(Controller, self).__init__()
3557+
3558+ def _get_metadata(self, context, server_id):
3559+ metadata = self.compute_api.get_instance_metadata(context, server_id)
3560+ meta_dict = {}
3561+ for key, value in metadata.iteritems():
3562+ meta_dict[key] = value
3563+ return dict(metadata=meta_dict)
3564+
3565+ def index(self, req, server_id):
3566+ """ Returns the list of metadata for a given instance """
3567+ context = req.environ['nova.context']
3568+ return self._get_metadata(context, server_id)
3569+
3570+ def create(self, req, server_id):
3571+ context = req.environ['nova.context']
3572+ body = self._deserialize(req.body, req.get_content_type())
3573+ self.compute_api.update_or_create_instance_metadata(context,
3574+ server_id,
3575+ body['metadata'])
3576+ return req.body
3577+
3578+ def update(self, req, server_id, id):
3579+ context = req.environ['nova.context']
3580+ body = self._deserialize(req.body, req.get_content_type())
3581+ if not id in body:
3582+ expl = _('Request body and URI mismatch')
3583+ raise exc.HTTPBadRequest(explanation=expl)
3584+ if len(body) > 1:
3585+ expl = _('Request body contains too many items')
3586+ raise exc.HTTPBadRequest(explanation=expl)
3587+ self.compute_api.update_or_create_instance_metadata(context,
3588+ server_id,
3589+ body)
3590+ return req.body
3591+
3592+ def show(self, req, server_id, id):
3593+ """ Return a single metadata item """
3594+ context = req.environ['nova.context']
3595+ data = self._get_metadata(context, server_id)
3596+ if id in data['metadata']:
3597+ return {id: data['metadata'][id]}
3598+ else:
3599+ return faults.Fault(exc.HTTPNotFound())
3600+
3601+ def delete(self, req, server_id, id):
3602+ """ Deletes an existing metadata """
3603+ context = req.environ['nova.context']
3604+ self.compute_api.delete_instance_metadata(context, server_id, id)
3605
3606=== modified file 'nova/api/openstack/servers.py'
3607--- nova/api/openstack/servers.py 2011-03-11 19:49:32 +0000
3608+++ nova/api/openstack/servers.py 2011-03-25 13:43:55 +0000
3609@@ -13,32 +13,48 @@
3610 # License for the specific language governing permissions and limitations
3611 # under the License.
3612
3613+<<<<<<< TREE
3614 import hashlib
3615 import json
3616+=======
3617+import base64
3618+import hashlib
3619+>>>>>>> MERGE-SOURCE
3620 import traceback
3621
3622 from webob import exc
3623+from xml.dom import minidom
3624
3625 from nova import compute
3626+from nova import context
3627 from nova import exception
3628 from nova import flags
3629 from nova import log as logging
3630+from nova import quota
3631+from nova import utils
3632 from nova import wsgi
3633-from nova import utils
3634 from nova.api.openstack import common
3635 from nova.api.openstack import faults
3636+import nova.api.openstack.views.addresses
3637+import nova.api.openstack.views.flavors
3638+import nova.api.openstack.views.servers
3639 from nova.auth import manager as auth_manager
3640 from nova.compute import instance_types
3641 from nova.compute import power_state
3642 import nova.api.openstack
3643+from nova.scheduler import api as scheduler_api
3644
3645
3646 LOG = logging.getLogger('server')
3647-
3648-
3649+<<<<<<< TREE
3650+
3651+
3652+=======
3653+>>>>>>> MERGE-SOURCE
3654 FLAGS = flags.FLAGS
3655
3656
3657+<<<<<<< TREE
3658 def _translate_detail_keys(inst):
3659 """ Coerces into dictionary format, mapping everything to Rackspace-like
3660 attributes for return"""
3661@@ -91,6 +107,8 @@
3662 return dict(server=dict(id=inst['id'], name=inst['display_name']))
3663
3664
3665+=======
3666+>>>>>>> MERGE-SOURCE
3667 class Controller(wsgi.Controller):
3668 """ The Server API controller for the OpenStack API """
3669
3670@@ -98,39 +116,59 @@
3671 'application/xml': {
3672 "attributes": {
3673 "server": ["id", "imageId", "name", "flavorId", "hostId",
3674+<<<<<<< TREE
3675 "status", "progress", "adminPass"]}}}
3676+=======
3677+ "status", "progress", "adminPass", "flavorRef",
3678+ "imageRef"]}}}
3679+>>>>>>> MERGE-SOURCE
3680
3681 def __init__(self):
3682 self.compute_api = compute.API()
3683 self._image_service = utils.import_object(FLAGS.image_service)
3684 super(Controller, self).__init__()
3685
3686+ def ips(self, req, id):
3687+ try:
3688+ instance = self.compute_api.get(req.environ['nova.context'], id)
3689+ except exception.NotFound:
3690+ return faults.Fault(exc.HTTPNotFound())
3691+
3692+ builder = self._get_addresses_view_builder(req)
3693+ return builder.build(instance)
3694+
3695 def index(self, req):
3696 """ Returns a list of server names and ids for a given user """
3697- return self._items(req, entity_maker=_translate_keys)
3698+ return self._items(req, is_detail=False)
3699
3700 def detail(self, req):
3701 """ Returns a list of server details for a given user """
3702- return self._items(req, entity_maker=_translate_detail_keys)
3703+ return self._items(req, is_detail=True)
3704
3705- def _items(self, req, entity_maker):
3706+ def _items(self, req, is_detail):
3707 """Returns a list of servers for a given user.
3708
3709- entity_maker - either _translate_detail_keys or _translate_keys
3710+ builder - the response model builder
3711 """
3712 instance_list = self.compute_api.get_all(req.environ['nova.context'])
3713- limited_list = common.limited(instance_list, req)
3714- res = [entity_maker(inst)['server'] for inst in limited_list]
3715- return dict(servers=res)
3716+ limited_list = self._limit_items(instance_list, req)
3717+ builder = self._get_view_builder(req)
3718+ servers = [builder.build(inst, is_detail)['server']
3719+ for inst in limited_list]
3720+ return dict(servers=servers)
3721
3722+ @scheduler_api.redirect_handler
3723 def show(self, req, id):
3724 """ Returns server details by server id """
3725 try:
3726- instance = self.compute_api.get(req.environ['nova.context'], id)
3727- return _translate_detail_keys(instance)
3728+ instance = self.compute_api.routing_get(
3729+ req.environ['nova.context'], id)
3730+ builder = self._get_view_builder(req)
3731+ return builder.build(instance, is_detail=True)
3732 except exception.NotFound:
3733 return faults.Fault(exc.HTTPNotFound())
3734
3735+ @scheduler_api.redirect_handler
3736 def delete(self, req, id):
3737 """ Destroys a server """
3738 try:
3739@@ -141,20 +179,43 @@
3740
3741 def create(self, req):
3742 """ Creates a new server for a given user """
3743+<<<<<<< TREE
3744 env = self._deserialize(req.body, req.get_content_type())
3745+=======
3746+ env = self._deserialize_create(req)
3747+>>>>>>> MERGE-SOURCE
3748 if not env:
3749 return faults.Fault(exc.HTTPUnprocessableEntity())
3750
3751+<<<<<<< TREE
3752 context = req.environ['nova.context']
3753 key_pairs = auth_manager.AuthManager.get_key_pairs(context)
3754 if not key_pairs:
3755 raise exception.NotFound(_("No keypairs defined"))
3756 key_pair = key_pairs[0]
3757
3758+=======
3759+ context = req.environ['nova.context']
3760+
3761+ key_name = None
3762+ key_data = None
3763+ key_pairs = auth_manager.AuthManager.get_key_pairs(context)
3764+ if key_pairs:
3765+ key_pair = key_pairs[0]
3766+ key_name = key_pair['name']
3767+ key_data = key_pair['public_key']
3768+
3769+ requested_image_id = self._image_id_from_req_data(env)
3770+>>>>>>> MERGE-SOURCE
3771 image_id = common.get_image_id_from_image_hash(self._image_service,
3772+<<<<<<< TREE
3773 context, env['server']['imageId'])
3774+=======
3775+ context, requested_image_id)
3776+>>>>>>> MERGE-SOURCE
3777 kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
3778 req, image_id)
3779+<<<<<<< TREE
3780
3781 # Metadata is a list, not a Dictionary, because we allow duplicate keys
3782 # (even though JSON can't encode this)
3783@@ -187,6 +248,110 @@
3784 password)
3785 return server
3786
3787+=======
3788+
3789+ # Metadata is a list, not a Dictionary, because we allow duplicate keys
3790+ # (even though JSON can't encode this)
3791+ # In future, we may not allow duplicate keys.
3792+ # However, the CloudServers API is not definitive on this front,
3793+ # and we want to be compatible.
3794+ metadata = []
3795+ if env['server'].get('metadata'):
3796+ for k, v in env['server']['metadata'].items():
3797+ metadata.append({'key': k, 'value': v})
3798+
3799+ personality = env['server'].get('personality')
3800+ injected_files = []
3801+ if personality:
3802+ injected_files = self._get_injected_files(personality)
3803+
3804+ flavor_id = self._flavor_id_from_req_data(env)
3805+ try:
3806+ (inst,) = self.compute_api.create(
3807+ context,
3808+ instance_types.get_by_flavor_id(flavor_id),
3809+ image_id,
3810+ kernel_id=kernel_id,
3811+ ramdisk_id=ramdisk_id,
3812+ display_name=env['server']['name'],
3813+ display_description=env['server']['name'],
3814+ key_name=key_name,
3815+ key_data=key_data,
3816+ metadata=metadata,
3817+ injected_files=injected_files)
3818+ except quota.QuotaError as error:
3819+ self._handle_quota_error(error)
3820+
3821+ inst['instance_type'] = flavor_id
3822+ inst['image_id'] = requested_image_id
3823+
3824+ builder = self._get_view_builder(req)
3825+ server = builder.build(inst, is_detail=True)
3826+ password = "%s%s" % (server['server']['name'][:4],
3827+ utils.generate_password(12))
3828+ server['server']['adminPass'] = password
3829+ self.compute_api.set_admin_password(context, server['server']['id'],
3830+ password)
3831+ return server
3832+
3833+ def _deserialize_create(self, request):
3834+ """
3835+ Deserialize a create request
3836+
3837+ Overrides normal behavior in the case of xml content
3838+ """
3839+ if request.content_type == "application/xml":
3840+ deserializer = ServerCreateRequestXMLDeserializer()
3841+ return deserializer.deserialize(request.body)
3842+ else:
3843+ return self._deserialize(request.body, request.get_content_type())
3844+
3845+ def _get_injected_files(self, personality):
3846+ """
3847+ Create a list of injected files from the personality attribute
3848+
3849+ At this time, injected_files must be formatted as a list of
3850+ (file_path, file_content) pairs for compatibility with the
3851+ underlying compute service.
3852+ """
3853+ injected_files = []
3854+
3855+ for item in personality:
3856+ try:
3857+ path = item['path']
3858+ contents = item['contents']
3859+ except KeyError as key:
3860+ expl = _('Bad personality format: missing %s') % key
3861+ raise exc.HTTPBadRequest(explanation=expl)
3862+ except TypeError:
3863+ expl = _('Bad personality format')
3864+ raise exc.HTTPBadRequest(explanation=expl)
3865+ try:
3866+ contents = base64.b64decode(contents)
3867+ except TypeError:
3868+ expl = _('Personality content for %s cannot be decoded') % path
3869+ raise exc.HTTPBadRequest(explanation=expl)
3870+ injected_files.append((path, contents))
3871+ return injected_files
3872+
3873+ def _handle_quota_error(self, error):
3874+ """
3875+ Reraise quota errors as api-specific http exceptions
3876+ """
3877+ if error.code == "OnsetFileLimitExceeded":
3878+ expl = _("Personality file limit exceeded")
3879+ raise exc.HTTPBadRequest(explanation=expl)
3880+ if error.code == "OnsetFilePathLimitExceeded":
3881+ expl = _("Personality file path too long")
3882+ raise exc.HTTPBadRequest(explanation=expl)
3883+ if error.code == "OnsetFileContentLimitExceeded":
3884+ expl = _("Personality file content too long")
3885+ raise exc.HTTPBadRequest(explanation=expl)
3886+ # if the original error is okay, just reraise it
3887+ raise error
3888+
3889+ @scheduler_api.redirect_handler
3890+>>>>>>> MERGE-SOURCE
3891 def update(self, req, id):
3892 """ Updates the server name or password """
3893 if len(req.body) == 0:
3894@@ -202,7 +367,7 @@
3895 update_dict['admin_pass'] = inst_dict['server']['adminPass']
3896 try:
3897 self.compute_api.set_admin_password(ctxt, id)
3898- except exception.TimeoutException, e:
3899+ except exception.TimeoutException:
3900 return exc.HTTPRequestTimeout()
3901 if 'name' in inst_dict['server']:
3902 update_dict['display_name'] = inst_dict['server']['name']
3903@@ -212,6 +377,7 @@
3904 return faults.Fault(exc.HTTPNotFound())
3905 return exc.HTTPNoContent()
3906
3907+ @scheduler_api.redirect_handler
3908 def action(self, req, id):
3909 """Multi-purpose method used to reboot, rebuild, or
3910 resize a server"""
3911@@ -277,6 +443,7 @@
3912 return faults.Fault(exc.HTTPUnprocessableEntity())
3913 return exc.HTTPAccepted()
3914
3915+ @scheduler_api.redirect_handler
3916 def lock(self, req, id):
3917 """
3918 lock the instance with id
3919@@ -292,6 +459,7 @@
3920 return faults.Fault(exc.HTTPUnprocessableEntity())
3921 return exc.HTTPAccepted()
3922
3923+ @scheduler_api.redirect_handler
3924 def unlock(self, req, id):
3925 """
3926 unlock the instance with id
3927@@ -307,6 +475,7 @@
3928 return faults.Fault(exc.HTTPUnprocessableEntity())
3929 return exc.HTTPAccepted()
3930
3931+ @scheduler_api.redirect_handler
3932 def get_lock(self, req, id):
3933 """
3934 return the boolean state of (instance with id)'s lock
3935@@ -321,34 +490,68 @@
3936 return faults.Fault(exc.HTTPUnprocessableEntity())
3937 return exc.HTTPAccepted()
3938
3939- def reset_network(self, req, id):
3940- """
3941- Reset networking on an instance (admin only).
3942-
3943- """
3944- context = req.environ['nova.context']
3945- try:
3946- self.compute_api.reset_network(context, id)
3947- except:
3948- readable = traceback.format_exc()
3949- LOG.exception(_("Compute.api::reset_network %s"), readable)
3950- return faults.Fault(exc.HTTPUnprocessableEntity())
3951- return exc.HTTPAccepted()
3952-
3953- def inject_network_info(self, req, id):
3954- """
3955- Inject network info for an instance (admin only).
3956-
3957- """
3958- context = req.environ['nova.context']
3959- try:
3960- self.compute_api.inject_network_info(context, id)
3961- except:
3962- readable = traceback.format_exc()
3963- LOG.exception(_("Compute.api::inject_network_info %s"), readable)
3964- return faults.Fault(exc.HTTPUnprocessableEntity())
3965- return exc.HTTPAccepted()
3966-
3967+<<<<<<< TREE
3968+ def reset_network(self, req, id):
3969+ """
3970+ Reset networking on an instance (admin only).
3971+
3972+ """
3973+ context = req.environ['nova.context']
3974+ try:
3975+ self.compute_api.reset_network(context, id)
3976+ except:
3977+ readable = traceback.format_exc()
3978+ LOG.exception(_("Compute.api::reset_network %s"), readable)
3979+ return faults.Fault(exc.HTTPUnprocessableEntity())
3980+ return exc.HTTPAccepted()
3981+
3982+ def inject_network_info(self, req, id):
3983+ """
3984+ Inject network info for an instance (admin only).
3985+
3986+ """
3987+ context = req.environ['nova.context']
3988+ try:
3989+ self.compute_api.inject_network_info(context, id)
3990+ except:
3991+ readable = traceback.format_exc()
3992+ LOG.exception(_("Compute.api::inject_network_info %s"), readable)
3993+ return faults.Fault(exc.HTTPUnprocessableEntity())
3994+ return exc.HTTPAccepted()
3995+
3996+=======
3997+ @scheduler_api.redirect_handler
3998+ def reset_network(self, req, id):
3999+ """
4000+ Reset networking on an instance (admin only).
4001+
4002+ """
4003+ context = req.environ['nova.context']
4004+ try:
4005+ self.compute_api.reset_network(context, id)
4006+ except:
4007+ readable = traceback.format_exc()
4008+ LOG.exception(_("Compute.api::reset_network %s"), readable)
4009+ return faults.Fault(exc.HTTPUnprocessableEntity())
4010+ return exc.HTTPAccepted()
4011+
4012+ @scheduler_api.redirect_handler
4013+ def inject_network_info(self, req, id):
4014+ """
4015+ Inject network info for an instance (admin only).
4016+
4017+ """
4018+ context = req.environ['nova.context']
4019+ try:
4020+ self.compute_api.inject_network_info(context, id)
4021+ except:
4022+ readable = traceback.format_exc()
4023+ LOG.exception(_("Compute.api::inject_network_info %s"), readable)
4024+ return faults.Fault(exc.HTTPUnprocessableEntity())
4025+ return exc.HTTPAccepted()
4026+
4027+ @scheduler_api.redirect_handler
4028+>>>>>>> MERGE-SOURCE
4029 def pause(self, req, id):
4030 """ Permit Admins to Pause the server. """
4031 ctxt = req.environ['nova.context']
4032@@ -360,6 +563,7 @@
4033 return faults.Fault(exc.HTTPUnprocessableEntity())
4034 return exc.HTTPAccepted()
4035
4036+ @scheduler_api.redirect_handler
4037 def unpause(self, req, id):
4038 """ Permit Admins to Unpause the server. """
4039 ctxt = req.environ['nova.context']
4040@@ -371,6 +575,7 @@
4041 return faults.Fault(exc.HTTPUnprocessableEntity())
4042 return exc.HTTPAccepted()
4043
4044+ @scheduler_api.redirect_handler
4045 def suspend(self, req, id):
4046 """permit admins to suspend the server"""
4047 context = req.environ['nova.context']
4048@@ -382,6 +587,7 @@
4049 return faults.Fault(exc.HTTPUnprocessableEntity())
4050 return exc.HTTPAccepted()
4051
4052+ @scheduler_api.redirect_handler
4053 def resume(self, req, id):
4054 """permit admins to resume the server from suspend"""
4055 context = req.environ['nova.context']
4056@@ -393,28 +599,56 @@
4057 return faults.Fault(exc.HTTPUnprocessableEntity())
4058 return exc.HTTPAccepted()
4059
4060- def rescue(self, req, id):
4061- """Permit users to rescue the server."""
4062- context = req.environ["nova.context"]
4063- try:
4064- self.compute_api.rescue(context, id)
4065- except:
4066- readable = traceback.format_exc()
4067- LOG.exception(_("compute.api::rescue %s"), readable)
4068- return faults.Fault(exc.HTTPUnprocessableEntity())
4069- return exc.HTTPAccepted()
4070-
4071- def unrescue(self, req, id):
4072- """Permit users to unrescue the server."""
4073- context = req.environ["nova.context"]
4074- try:
4075- self.compute_api.unrescue(context, id)
4076- except:
4077- readable = traceback.format_exc()
4078- LOG.exception(_("compute.api::unrescue %s"), readable)
4079- return faults.Fault(exc.HTTPUnprocessableEntity())
4080- return exc.HTTPAccepted()
4081-
4082+<<<<<<< TREE
4083+ def rescue(self, req, id):
4084+ """Permit users to rescue the server."""
4085+ context = req.environ["nova.context"]
4086+ try:
4087+ self.compute_api.rescue(context, id)
4088+ except:
4089+ readable = traceback.format_exc()
4090+ LOG.exception(_("compute.api::rescue %s"), readable)
4091+ return faults.Fault(exc.HTTPUnprocessableEntity())
4092+ return exc.HTTPAccepted()
4093+
4094+ def unrescue(self, req, id):
4095+ """Permit users to unrescue the server."""
4096+ context = req.environ["nova.context"]
4097+ try:
4098+ self.compute_api.unrescue(context, id)
4099+ except:
4100+ readable = traceback.format_exc()
4101+ LOG.exception(_("compute.api::unrescue %s"), readable)
4102+ return faults.Fault(exc.HTTPUnprocessableEntity())
4103+ return exc.HTTPAccepted()
4104+
4105+=======
4106+ @scheduler_api.redirect_handler
4107+ def rescue(self, req, id):
4108+ """Permit users to rescue the server."""
4109+ context = req.environ["nova.context"]
4110+ try:
4111+ self.compute_api.rescue(context, id)
4112+ except:
4113+ readable = traceback.format_exc()
4114+ LOG.exception(_("compute.api::rescue %s"), readable)
4115+ return faults.Fault(exc.HTTPUnprocessableEntity())
4116+ return exc.HTTPAccepted()
4117+
4118+ @scheduler_api.redirect_handler
4119+ def unrescue(self, req, id):
4120+ """Permit users to unrescue the server."""
4121+ context = req.environ["nova.context"]
4122+ try:
4123+ self.compute_api.unrescue(context, id)
4124+ except:
4125+ readable = traceback.format_exc()
4126+ LOG.exception(_("compute.api::unrescue %s"), readable)
4127+ return faults.Fault(exc.HTTPUnprocessableEntity())
4128+ return exc.HTTPAccepted()
4129+
4130+ @scheduler_api.redirect_handler
4131+>>>>>>> MERGE-SOURCE
4132 def get_ajax_console(self, req, id):
4133 """ Returns a url to an instance's ajaxterm console. """
4134 try:
4135@@ -424,6 +658,7 @@
4136 return faults.Fault(exc.HTTPNotFound())
4137 return exc.HTTPAccepted()
4138
4139+ @scheduler_api.redirect_handler
4140 def diagnostics(self, req, id):
4141 """Permit Admins to retrieve server diagnostics."""
4142 ctxt = req.environ["nova.context"]
4143@@ -442,37 +677,195 @@
4144 action=item.action,
4145 error=item.error))
4146 return dict(actions=actions)
4147-
4148- def _get_kernel_ramdisk_from_image(self, req, image_id):
4149- """Retrevies kernel and ramdisk IDs from Glance
4150-
4151- Only 'machine' (ami) type use kernel and ramdisk outside of the
4152- image.
4153- """
4154- # FIXME(sirp): Since we're retrieving the kernel_id from an
4155- # image_property, this means only Glance is supported.
4156- # The BaseImageService needs to expose a consistent way of accessing
4157- # kernel_id and ramdisk_id
4158- image = self._image_service.show(req.environ['nova.context'], image_id)
4159-
4160- if image['status'] != 'active':
4161- raise exception.Invalid(
4162- _("Cannot build from image %(image_id)s, status not active") %
4163- locals())
4164-
4165- if image['disk_format'] != 'ami':
4166- return None, None
4167-
4168- try:
4169- kernel_id = image['properties']['kernel_id']
4170- except KeyError:
4171- raise exception.NotFound(
4172- _("Kernel not found for image %(image_id)s") % locals())
4173-
4174- try:
4175- ramdisk_id = image['properties']['ramdisk_id']
4176- except KeyError:
4177- raise exception.NotFound(
4178- _("Ramdisk not found for image %(image_id)s") % locals())
4179-
4180- return kernel_id, ramdisk_id
4181+<<<<<<< TREE
4182+
4183+ def _get_kernel_ramdisk_from_image(self, req, image_id):
4184+ """Retrevies kernel and ramdisk IDs from Glance
4185+
4186+ Only 'machine' (ami) type use kernel and ramdisk outside of the
4187+ image.
4188+ """
4189+ # FIXME(sirp): Since we're retrieving the kernel_id from an
4190+ # image_property, this means only Glance is supported.
4191+ # The BaseImageService needs to expose a consistent way of accessing
4192+ # kernel_id and ramdisk_id
4193+ image = self._image_service.show(req.environ['nova.context'], image_id)
4194+
4195+ if image['status'] != 'active':
4196+ raise exception.Invalid(
4197+ _("Cannot build from image %(image_id)s, status not active") %
4198+ locals())
4199+
4200+ if image['disk_format'] != 'ami':
4201+ return None, None
4202+
4203+ try:
4204+ kernel_id = image['properties']['kernel_id']
4205+ except KeyError:
4206+ raise exception.NotFound(
4207+ _("Kernel not found for image %(image_id)s") % locals())
4208+
4209+ try:
4210+ ramdisk_id = image['properties']['ramdisk_id']
4211+ except KeyError:
4212+ raise exception.NotFound(
4213+ _("Ramdisk not found for image %(image_id)s") % locals())
4214+
4215+ return kernel_id, ramdisk_id
4216+=======
4217+
4218+ def _get_kernel_ramdisk_from_image(self, req, image_id):
4219+ """Retrevies kernel and ramdisk IDs from Glance
4220+
4221+ Only 'machine' (ami) type use kernel and ramdisk outside of the
4222+ image.
4223+ """
4224+ # FIXME(sirp): Since we're retrieving the kernel_id from an
4225+ # image_property, this means only Glance is supported.
4226+ # The BaseImageService needs to expose a consistent way of accessing
4227+ # kernel_id and ramdisk_id
4228+ image = self._image_service.show(req.environ['nova.context'], image_id)
4229+
4230+ if image['status'] != 'active':
4231+ raise exception.Invalid(
4232+ _("Cannot build from image %(image_id)s, status not active") %
4233+ locals())
4234+
4235+ if image['disk_format'] != 'ami':
4236+ return None, None
4237+
4238+ try:
4239+ kernel_id = image['properties']['kernel_id']
4240+ except KeyError:
4241+ raise exception.NotFound(
4242+ _("Kernel not found for image %(image_id)s") % locals())
4243+
4244+ try:
4245+ ramdisk_id = image['properties']['ramdisk_id']
4246+ except KeyError:
4247+ raise exception.NotFound(
4248+ _("Ramdisk not found for image %(image_id)s") % locals())
4249+
4250+ return kernel_id, ramdisk_id
4251+
4252+
4253+class ControllerV10(Controller):
4254+ def _image_id_from_req_data(self, data):
4255+ return data['server']['imageId']
4256+
4257+ def _flavor_id_from_req_data(self, data):
4258+ return data['server']['flavorId']
4259+
4260+ def _get_view_builder(self, req):
4261+ addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV10()
4262+ return nova.api.openstack.views.servers.ViewBuilderV10(
4263+ addresses_builder)
4264+
4265+ def _get_addresses_view_builder(self, req):
4266+ return nova.api.openstack.views.addresses.ViewBuilderV10(req)
4267+
4268+ def _limit_items(self, items, req):
4269+ return common.limited(items, req)
4270+
4271+
4272+class ControllerV11(Controller):
4273+ def _image_id_from_req_data(self, data):
4274+ href = data['server']['imageRef']
4275+ return common.get_id_from_href(href)
4276+
4277+ def _flavor_id_from_req_data(self, data):
4278+ href = data['server']['flavorRef']
4279+ return common.get_id_from_href(href)
4280+
4281+ def _get_view_builder(self, req):
4282+ base_url = req.application_url
4283+ flavor_builder = nova.api.openstack.views.flavors.ViewBuilderV11(
4284+ base_url)
4285+ image_builder = nova.api.openstack.views.images.ViewBuilderV11(
4286+ base_url)
4287+ addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV11()
4288+ return nova.api.openstack.views.servers.ViewBuilderV11(
4289+ addresses_builder, flavor_builder, image_builder)
4290+
4291+ def _get_addresses_view_builder(self, req):
4292+ return nova.api.openstack.views.addresses.ViewBuilderV11(req)
4293+
4294+ def _limit_items(self, items, req):
4295+ return common.limited_by_marker(items, req)
4296+
4297+
4298+class ServerCreateRequestXMLDeserializer(object):
4299+ """
4300+ Deserializer to handle xml-formatted server create requests.
4301+
4302+ Handles standard server attributes as well as optional metadata
4303+ and personality attributes
4304+ """
4305+
4306+ def deserialize(self, string):
4307+ """Deserialize an xml-formatted server create request"""
4308+ dom = minidom.parseString(string)
4309+ server = self._extract_server(dom)
4310+ return {'server': server}
4311+
4312+ def _extract_server(self, node):
4313+ """Marshal the server attribute of a parsed request"""
4314+ server = {}
4315+ server_node = self._find_first_child_named(node, 'server')
4316+ for attr in ["name", "imageId", "flavorId"]:
4317+ server[attr] = server_node.getAttribute(attr)
4318+ metadata = self._extract_metadata(server_node)
4319+ if metadata is not None:
4320+ server["metadata"] = metadata
4321+ personality = self._extract_personality(server_node)
4322+ if personality is not None:
4323+ server["personality"] = personality
4324+ return server
4325+
4326+ def _extract_metadata(self, server_node):
4327+ """Marshal the metadata attribute of a parsed request"""
4328+ metadata_node = self._find_first_child_named(server_node, "metadata")
4329+ if metadata_node is None:
4330+ return None
4331+ metadata = {}
4332+ for meta_node in self._find_children_named(metadata_node, "meta"):
4333+ key = meta_node.getAttribute("key")
4334+ metadata[key] = self._extract_text(meta_node)
4335+ return metadata
4336+
4337+ def _extract_personality(self, server_node):
4338+ """Marshal the personality attribute of a parsed request"""
4339+ personality_node = \
4340+ self._find_first_child_named(server_node, "personality")
4341+ if personality_node is None:
4342+ return None
4343+ personality = []
4344+ for file_node in self._find_children_named(personality_node, "file"):
4345+ item = {}
4346+ if file_node.hasAttribute("path"):
4347+ item["path"] = file_node.getAttribute("path")
4348+ item["contents"] = self._extract_text(file_node)
4349+ personality.append(item)
4350+ return personality
4351+
4352+ def _find_first_child_named(self, parent, name):
4353+ """Search a nodes children for the first child with a given name"""
4354+ for node in parent.childNodes:
4355+ if node.nodeName == name:
4356+ return node
4357+ return None
4358+
4359+ def _find_children_named(self, parent, name):
4360+ """Return all of a nodes children who have the given name"""
4361+ for node in parent.childNodes:
4362+ if node.nodeName == name:
4363+ yield node
4364+
4365+ def _extract_text(self, node):
4366+ """Get the text field contained by the given node"""
4367+ if len(node.childNodes) == 1:
4368+ child = node.childNodes[0]
4369+ if child.nodeType == child.TEXT_NODE:
4370+ return child.nodeValue
4371+ return ""
4372+>>>>>>> MERGE-SOURCE
4373
4374=== modified file 'nova/api/openstack/users.py'
4375--- nova/api/openstack/users.py 2011-03-11 19:49:32 +0000
4376+++ nova/api/openstack/users.py 2011-03-25 13:43:55 +0000
4377@@ -1,3 +1,4 @@
4378+<<<<<<< TREE
4379 # Copyright 2011 OpenStack LLC.
4380 # All Rights Reserved.
4381 #
4382@@ -91,3 +92,109 @@
4383 secret = env['user'].get('secret')
4384 self.manager.modify_user(id, access, secret, is_admin)
4385 return dict(user=_translate_keys(self.manager.get_user(id)))
4386+=======
4387+# Copyright 2011 OpenStack LLC.
4388+# All Rights Reserved.
4389+#
4390+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4391+# not use this file except in compliance with the License. You may obtain
4392+# a copy of the License at
4393+#
4394+# http://www.apache.org/licenses/LICENSE-2.0
4395+#
4396+# Unless required by applicable law or agreed to in writing, software
4397+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
4398+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4399+# License for the specific language governing permissions and limitations
4400+# under the License.
4401+
4402+from webob import exc
4403+
4404+from nova import exception
4405+from nova import flags
4406+from nova import log as logging
4407+from nova import wsgi
4408+from nova.api.openstack import common
4409+from nova.api.openstack import faults
4410+from nova.auth import manager
4411+
4412+FLAGS = flags.FLAGS
4413+LOG = logging.getLogger('nova.api.openstack')
4414+
4415+
4416+def _translate_keys(user):
4417+ return dict(id=user.id,
4418+ name=user.name,
4419+ access=user.access,
4420+ secret=user.secret,
4421+ admin=user.admin)
4422+
4423+
4424+class Controller(wsgi.Controller):
4425+
4426+ _serialization_metadata = {
4427+ 'application/xml': {
4428+ "attributes": {
4429+ "user": ["id", "name", "access", "secret", "admin"]}}}
4430+
4431+ def __init__(self):
4432+ self.manager = manager.AuthManager()
4433+
4434+ def _check_admin(self, context):
4435+ """We cannot depend on the db layer to check for admin access
4436+ for the auth manager, so we do it here"""
4437+ if not context.is_admin:
4438+ raise exception.NotAuthorized(_("Not admin user"))
4439+
4440+ def index(self, req):
4441+ """Return all users in brief"""
4442+ users = self.manager.get_users()
4443+ users = common.limited(users, req)
4444+ users = [_translate_keys(user) for user in users]
4445+ return dict(users=users)
4446+
4447+ def detail(self, req):
4448+ """Return all users in detail"""
4449+ return self.index(req)
4450+
4451+ def show(self, req, id):
4452+ """Return data about the given user id"""
4453+
4454+ #NOTE(justinsb): The drivers are a little inconsistent in how they
4455+ # deal with "NotFound" - some throw, some return None.
4456+ try:
4457+ user = self.manager.get_user(id)
4458+ except exception.NotFound:
4459+ user = None
4460+
4461+ if user is None:
4462+ raise faults.Fault(exc.HTTPNotFound())
4463+
4464+ return dict(user=_translate_keys(user))
4465+
4466+ def delete(self, req, id):
4467+ self._check_admin(req.environ['nova.context'])
4468+ self.manager.delete_user(id)
4469+ return {}
4470+
4471+ def create(self, req):
4472+ self._check_admin(req.environ['nova.context'])
4473+ env = self._deserialize(req.body, req.get_content_type())
4474+ is_admin = env['user'].get('admin') in ('T', 'True', True)
4475+ name = env['user'].get('name')
4476+ access = env['user'].get('access')
4477+ secret = env['user'].get('secret')
4478+ user = self.manager.create_user(name, access, secret, is_admin)
4479+ return dict(user=_translate_keys(user))
4480+
4481+ def update(self, req, id):
4482+ self._check_admin(req.environ['nova.context'])
4483+ env = self._deserialize(req.body, req.get_content_type())
4484+ is_admin = env['user'].get('admin')
4485+ if is_admin is not None:
4486+ is_admin = is_admin in ('T', 'True', True)
4487+ access = env['user'].get('access')
4488+ secret = env['user'].get('secret')
4489+ self.manager.modify_user(id, access, secret, is_admin)
4490+ return dict(user=_translate_keys(self.manager.get_user(id)))
4491+>>>>>>> MERGE-SOURCE
4492
4493=== added directory 'nova/api/openstack/views'
4494=== added file 'nova/api/openstack/views/__init__.py'
4495=== added file 'nova/api/openstack/views/addresses.py'
4496--- nova/api/openstack/views/addresses.py 1970-01-01 00:00:00 +0000
4497+++ nova/api/openstack/views/addresses.py 2011-03-25 13:43:55 +0000
4498@@ -0,0 +1,42 @@
4499+# vim: tabstop=4 shiftwidth=4 softtabstop=4
4500+
4501+# Copyright 2010-2011 OpenStack LLC.
4502+# All Rights Reserved.
4503+#
4504+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4505+# not use this file except in compliance with the License. You may obtain
4506+# a copy of the License at
4507+#
4508+# http://www.apache.org/licenses/LICENSE-2.0
4509+#
4510+# Unless required by applicable law or agreed to in writing, software
4511+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
4512+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4513+# License for the specific language governing permissions and limitations
4514+# under the License.
4515+
4516+from nova import utils
4517+from nova.api.openstack import common
4518+
4519+
4520+class ViewBuilder(object):
4521+ ''' Models a server addresses response as a python dictionary.'''
4522+
4523+ def build(self, inst):
4524+ raise NotImplementedError()
4525+
4526+
4527+class ViewBuilderV10(ViewBuilder):
4528+ def build(self, inst):
4529+ private_ips = utils.get_from_path(inst, 'fixed_ip/address')
4530+ public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
4531+ return dict(public=public_ips, private=private_ips)
4532+
4533+
4534+class ViewBuilderV11(ViewBuilder):
4535+ def build(self, inst):
4536+ private_ips = utils.get_from_path(inst, 'fixed_ip/address')
4537+ private_ips = [dict(version=4, addr=a) for a in private_ips]
4538+ public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
4539+ public_ips = [dict(version=4, addr=a) for a in public_ips]
4540+ return dict(public=public_ips, private=private_ips)
4541
4542=== added file 'nova/api/openstack/views/flavors.py'
4543--- nova/api/openstack/views/flavors.py 1970-01-01 00:00:00 +0000
4544+++ nova/api/openstack/views/flavors.py 2011-03-25 13:43:55 +0000
4545@@ -0,0 +1,34 @@
4546+# vim: tabstop=4 shiftwidth=4 softtabstop=4
4547+
4548+# Copyright 2010-2011 OpenStack LLC.
4549+# All Rights Reserved.
4550+#
4551+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4552+# not use this file except in compliance with the License. You may obtain
4553+# a copy of the License at
4554+#
4555+# http://www.apache.org/licenses/LICENSE-2.0
4556+#
4557+# Unless required by applicable law or agreed to in writing, software
4558+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
4559+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4560+# License for the specific language governing permissions and limitations
4561+# under the License.
4562+
4563+from nova.api.openstack import common
4564+
4565+
4566+class ViewBuilder(object):
4567+ def __init__(self):
4568+ pass
4569+
4570+ def build(self, flavor_obj):
4571+ raise NotImplementedError()
4572+
4573+
4574+class ViewBuilderV11(ViewBuilder):
4575+ def __init__(self, base_url):
4576+ self.base_url = base_url
4577+
4578+ def generate_href(self, flavor_id):
4579+ return "%s/flavors/%s" % (self.base_url, flavor_id)
4580
4581=== added file 'nova/api/openstack/views/images.py'
4582--- nova/api/openstack/views/images.py 1970-01-01 00:00:00 +0000
4583+++ nova/api/openstack/views/images.py 2011-03-25 13:43:55 +0000
4584@@ -0,0 +1,34 @@
4585+# vim: tabstop=4 shiftwidth=4 softtabstop=4
4586+
4587+# Copyright 2010-2011 OpenStack LLC.
4588+# All Rights Reserved.
4589+#
4590+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4591+# not use this file except in compliance with the License. You may obtain
4592+# a copy of the License at
4593+#
4594+# http://www.apache.org/licenses/LICENSE-2.0
4595+#
4596+# Unless required by applicable law or agreed to in writing, software
4597+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
4598+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4599+# License for the specific language governing permissions and limitations
4600+# under the License.
4601+
4602+from nova.api.openstack import common
4603+
4604+
4605+class ViewBuilder(object):
4606+ def __init__(self):
4607+ pass
4608+
4609+ def build(self, image_obj):
4610+ raise NotImplementedError()
4611+
4612+
4613+class ViewBuilderV11(ViewBuilder):
4614+ def __init__(self, base_url):
4615+ self.base_url = base_url
4616+
4617+ def generate_href(self, image_id):
4618+ return "%s/images/%s" % (self.base_url, image_id)
4619
4620=== added file 'nova/api/openstack/views/servers.py'
4621--- nova/api/openstack/views/servers.py 1970-01-01 00:00:00 +0000
4622+++ nova/api/openstack/views/servers.py 2011-03-25 13:43:55 +0000
4623@@ -0,0 +1,125 @@
4624+# vim: tabstop=4 shiftwidth=4 softtabstop=4
4625+
4626+# Copyright 2010-2011 OpenStack LLC.
4627+# All Rights Reserved.
4628+#
4629+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4630+# not use this file except in compliance with the License. You may obtain
4631+# a copy of the License at
4632+#
4633+# http://www.apache.org/licenses/LICENSE-2.0
4634+#
4635+# Unless required by applicable law or agreed to in writing, software
4636+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
4637+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4638+# License for the specific language governing permissions and limitations
4639+# under the License.
4640+
4641+import hashlib
4642+
4643+from nova.compute import power_state
4644+import nova.compute
4645+import nova.context
4646+from nova.api.openstack import common
4647+from nova.api.openstack.views import addresses as addresses_view
4648+from nova.api.openstack.views import flavors as flavors_view
4649+from nova.api.openstack.views import images as images_view
4650+from nova import utils
4651+
4652+
4653+class ViewBuilder(object):
4654+ """Model a server response as a python dictionary.
4655+
4656+ Public methods: build
4657+ Abstract methods: _build_image, _build_flavor
4658+
4659+ """
4660+
4661+ def __init__(self, addresses_builder):
4662+ self.addresses_builder = addresses_builder
4663+
4664+ def build(self, inst, is_detail):
4665+ """Return a dict that represenst a server."""
4666+ if is_detail:
4667+ return self._build_detail(inst)
4668+ else:
4669+ return self._build_simple(inst)
4670+
4671+ def _build_simple(self, inst):
4672+ """Return a simple model of a server."""
4673+ return dict(server=dict(id=inst['id'], name=inst['display_name']))
4674+
4675+ def _build_detail(self, inst):
4676+ """Returns a detailed model of a server."""
4677+ power_mapping = {
4678+ None: 'build',
4679+ power_state.NOSTATE: 'build',
4680+ power_state.RUNNING: 'active',
4681+ power_state.BLOCKED: 'active',
4682+ power_state.SUSPENDED: 'suspended',
4683+ power_state.PAUSED: 'paused',
4684+ power_state.SHUTDOWN: 'active',
4685+ power_state.SHUTOFF: 'active',
4686+ power_state.CRASHED: 'error',
4687+ power_state.FAILED: 'error'}
4688+
4689+ inst_dict = {
4690+ 'id': int(inst['id']),
4691+ 'name': inst['display_name'],
4692+ 'addresses': self.addresses_builder.build(inst),
4693+ 'status': power_mapping[inst.get('state')]}
4694+
4695+ ctxt = nova.context.get_admin_context()
4696+ compute_api = nova.compute.API()
4697+ if compute_api.has_finished_migration(ctxt, inst['id']):
4698+ inst_dict['status'] = 'resize-confirm'
4699+
4700+ # Return the metadata as a dictionary
4701+ metadata = {}
4702+ for item in inst.get('metadata', []):
4703+ metadata[item['key']] = item['value']
4704+ inst_dict['metadata'] = metadata
4705+
4706+ inst_dict['hostId'] = ''
4707+ if inst.get('host'):
4708+ inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest()
4709+
4710+ self._build_image(inst_dict, inst)
4711+ self._build_flavor(inst_dict, inst)
4712+
4713+ return dict(server=inst_dict)
4714+
4715+ def _build_image(self, response, inst):
4716+ """Return the image sub-resource of a server."""
4717+ raise NotImplementedError()
4718+
4719+ def _build_flavor(self, response, inst):
4720+ """Return the flavor sub-resource of a server."""
4721+ raise NotImplementedError()
4722+
4723+
4724+class ViewBuilderV10(ViewBuilder):
4725+ """Model an Openstack API V1.0 server response."""
4726+
4727+ def _build_image(self, response, inst):
4728+ response['imageId'] = inst['image_id']
4729+
4730+ def _build_flavor(self, response, inst):
4731+ response['flavorId'] = inst['instance_type']
4732+
4733+
4734+class ViewBuilderV11(ViewBuilder):
4735+ """Model an Openstack API V1.0 server response."""
4736+
4737+ def __init__(self, addresses_builder, flavor_builder, image_builder):
4738+ ViewBuilder.__init__(self, addresses_builder)
4739+ self.flavor_builder = flavor_builder
4740+ self.image_builder = image_builder
4741+
4742+ def _build_image(self, response, inst):
4743+ image_id = inst["image_id"]
4744+ response["imageRef"] = self.image_builder.generate_href(image_id)
4745+
4746+ def _build_flavor(self, response, inst):
4747+ flavor_id = inst["instance_type"]
4748+ response["flavorRef"] = self.flavor_builder.generate_href(flavor_id)
4749
4750=== modified file 'nova/api/openstack/zones.py'
4751--- nova/api/openstack/zones.py 2011-03-11 19:49:32 +0000
4752+++ nova/api/openstack/zones.py 2011-03-25 13:43:55 +0000
4753@@ -1,3 +1,4 @@
4754+<<<<<<< TREE
4755 # Copyright 2011 OpenStack LLC.
4756 # All Rights Reserved.
4757 #
4758@@ -93,3 +94,106 @@
4759 zone_id = int(id)
4760 zone = db.zone_update(context, zone_id, env["zone"])
4761 return dict(zone=_scrub_zone(zone))
4762+=======
4763+# Copyright 2011 OpenStack LLC.
4764+# All Rights Reserved.
4765+#
4766+# Licensed under the Apache License, Version 2.0 (the "License"); you may
4767+# not use this file except in compliance with the License. You may obtain
4768+# a copy of the License at
4769+#
4770+# http://www.apache.org/licenses/LICENSE-2.0
4771+#
4772+# Unless required by applicable law or agreed to in writing, software
4773+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
4774+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4775+# License for the specific language governing permissions and limitations
4776+# under the License.
4777+
4778+import common
4779+
4780+from nova import db
4781+from nova import flags
4782+from nova import log as logging
4783+from nova import wsgi
4784+from nova.scheduler import api
4785+
4786+
4787+FLAGS = flags.FLAGS
4788+
4789+
4790+def _filter_keys(item, keys):
4791+ """
4792+ Filters all model attributes except for keys
4793+ item is a dict
4794+
4795+ """
4796+ return dict((k, v) for k, v in item.iteritems() if k in keys)
4797+
4798+
4799+def _exclude_keys(item, keys):
4800+ return dict((k, v) for k, v in item.iteritems() if k not in keys)
4801+
4802+
4803+def _scrub_zone(zone):
4804+ return _exclude_keys(zone, ('username', 'password', 'created_at',
4805+ 'deleted', 'deleted_at', 'updated_at'))
4806+
4807+
4808+class Controller(wsgi.Controller):
4809+
4810+ _serialization_metadata = {
4811+ 'application/xml': {
4812+ "attributes": {
4813+ "zone": ["id", "api_url", "name", "capabilities"]}}}
4814+
4815+ def index(self, req):
4816+ """Return all zones in brief"""
4817+ # Ask the ZoneManager in the Scheduler for most recent data,
4818+ # or fall-back to the database ...
4819+ items = api.get_zone_list(req.environ['nova.context'])
4820+ items = common.limited(items, req)
4821+ items = [_scrub_zone(item) for item in items]
4822+ return dict(zones=items)
4823+
4824+ def detail(self, req):
4825+ """Return all zones in detail"""
4826+ return self.index(req)
4827+
4828+ def info(self, req):
4829+ """Return name and capabilities for this zone."""
4830+ items = api.get_zone_capabilities(req.environ['nova.context'])
4831+
4832+ zone = dict(name=FLAGS.zone_name)
4833+ caps = FLAGS.zone_capabilities
4834+ for cap in caps:
4835+ key, value = cap.split('=')
4836+ zone[key] = value
4837+ for item, (min_value, max_value) in items.iteritems():
4838+ zone[item] = "%s,%s" % (min_value, max_value)
4839+ return dict(zone=zone)
4840+
4841+ def show(self, req, id):
4842+ """Return data about the given zone id"""
4843+ zone_id = int(id)
4844+ zone = api.zone_get(req.environ['nova.context'], zone_id)
4845+ return dict(zone=_scrub_zone(zone))
4846+
4847+ def delete(self, req, id):
4848+ zone_id = int(id)
4849+ api.zone_delete(req.environ['nova.context'], zone_id)
4850+ return {}
4851+
4852+ def create(self, req):
4853+ context = req.environ['nova.context']
4854+ env = self._deserialize(req.body, req.get_content_type())
4855+ zone = api.zone_create(context, env["zone"])
4856+ return dict(zone=_scrub_zone(zone))
4857+
4858+ def update(self, req, id):
4859+ context = req.environ['nova.context']
4860+ env = self._deserialize(req.body, req.get_content_type())
4861+ zone_id = int(id)
4862+ zone = api.zone_update(context, zone_id, env["zone"])
4863+ return dict(zone=_scrub_zone(zone))
4864+>>>>>>> MERGE-SOURCE
4865
4866=== modified file 'nova/auth/dbdriver.py'
4867--- nova/auth/dbdriver.py 2011-01-20 17:52:02 +0000
4868+++ nova/auth/dbdriver.py 2011-03-25 13:43:55 +0000
4869@@ -162,6 +162,8 @@
4870 values['description'] = description
4871
4872 db.project_update(context.get_admin_context(), project_id, values)
4873+ if not self.is_in_project(manager_uid, project_id):
4874+ self.add_to_project(manager_uid, project_id)
4875
4876 def add_to_project(self, uid, project_id):
4877 """Add user to project"""
4878
4879=== modified file 'nova/auth/fakeldap.py'
4880--- nova/auth/fakeldap.py 2010-12-22 23:47:31 +0000
4881+++ nova/auth/fakeldap.py 2011-03-25 13:43:55 +0000
4882@@ -90,12 +90,12 @@
4883 MOD_REPLACE = 2
4884
4885
4886-class NO_SUCH_OBJECT(Exception): # pylint: disable-msg=C0103
4887+class NO_SUCH_OBJECT(Exception): # pylint: disable=C0103
4888 """Duplicate exception class from real LDAP module."""
4889 pass
4890
4891
4892-class OBJECT_CLASS_VIOLATION(Exception): # pylint: disable-msg=C0103
4893+class OBJECT_CLASS_VIOLATION(Exception): # pylint: disable=C0103
4894 """Duplicate exception class from real LDAP module."""
4895 pass
4896
4897@@ -268,7 +268,7 @@
4898 # get the attributes from the store
4899 attrs = store.hgetall(key)
4900 # turn the values from the store into lists
4901- # pylint: disable-msg=E1103
4902+ # pylint: disable=E1103
4903 attrs = dict([(k, _from_json(v))
4904 for k, v in attrs.iteritems()])
4905 # filter the objects by query
4906@@ -277,12 +277,12 @@
4907 attrs = dict([(k, v) for k, v in attrs.iteritems()
4908 if not fields or k in fields])
4909 objects.append((key[len(self.__prefix):], attrs))
4910- # pylint: enable-msg=E1103
4911+ # pylint: enable=E1103
4912 if objects == []:
4913 raise NO_SUCH_OBJECT()
4914 return objects
4915
4916 @property
4917- def __prefix(self): # pylint: disable-msg=R0201
4918+ def __prefix(self): # pylint: disable=R0201
4919 """Get the prefix to use for all keys."""
4920 return 'ldap:'
4921
4922=== modified file 'nova/auth/ldapdriver.py'
4923--- nova/auth/ldapdriver.py 2011-02-16 20:03:59 +0000
4924+++ nova/auth/ldapdriver.py 2011-03-25 13:43:55 +0000
4925@@ -275,6 +275,8 @@
4926 attr.append((self.ldap.MOD_REPLACE, 'description', description))
4927 dn = self.__project_to_dn(project_id)
4928 self.conn.modify_s(dn, attr)
4929+ if not self.is_in_project(manager_uid, project_id):
4930+ self.add_to_project(manager_uid, project_id)
4931
4932 @sanitize
4933 def add_to_project(self, uid, project_id):
4934@@ -632,6 +634,6 @@
4935 class FakeLdapDriver(LdapDriver):
4936 """Fake Ldap Auth driver"""
4937
4938- def __init__(self): # pylint: disable-msg=W0231
4939+ def __init__(self): # pylint: disable=W0231
4940 __import__('nova.auth.fakeldap')
4941 self.ldap = sys.modules['nova.auth.fakeldap']
4942
4943=== modified file 'nova/auth/manager.py'
4944--- nova/auth/manager.py 2011-01-19 02:00:28 +0000
4945+++ nova/auth/manager.py 2011-03-25 13:43:55 +0000
4946@@ -22,7 +22,7 @@
4947
4948 import os
4949 import shutil
4950-import string # pylint: disable-msg=W0402
4951+import string # pylint: disable=W0402
4952 import tempfile
4953 import uuid
4954 import zipfile
4955@@ -96,10 +96,19 @@
4956
4957
4958 class User(AuthBase):
4959- """Object representing a user"""
4960+ """Object representing a user
4961+
4962+ The following attributes are defined:
4963+ :id: A system identifier for the user. A string (for LDAP)
4964+ :name: The user name, potentially in some more friendly format
4965+ :access: The 'username' for EC2 authentication
4966+ :secret: The 'password' for EC2 authenticatoin
4967+ :admin: ???
4968+ """
4969
4970 def __init__(self, id, name, access, secret, admin):
4971 AuthBase.__init__(self)
4972+ assert isinstance(id, basestring)
4973 self.id = id
4974 self.name = name
4975 self.access = access
4976
4977=== modified file 'nova/compute/api.py'
4978--- nova/compute/api.py 2011-03-10 17:30:26 +0000
4979+++ nova/compute/api.py 2011-03-25 13:43:55 +0000
4980@@ -34,6 +34,7 @@
4981 from nova import utils
4982 from nova import volume
4983 from nova.compute import instance_types
4984+from nova.scheduler import api as scheduler_api
4985 from nova.db import base
4986
4987 FLAGS = flags.FLAGS
4988@@ -80,13 +81,37 @@
4989 topic,
4990 {"method": "get_network_topic", "args": {'fake': 1}})
4991
4992+ def _check_injected_file_quota(self, context, injected_files):
4993+ """
4994+ Enforce quota limits on injected files
4995+
4996+ Raises a QuotaError if any limit is exceeded
4997+ """
4998+ if injected_files is None:
4999+ return
5000+ limit = quota.allowed_injected_files(context)
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches