Merge lp:~tr3buchet/rackspace-nova/rs-nova into lp:rackspace-nova

Proposed by Trey Morris
Status: Merged
Approved by: Trey Morris
Approved revision: not available
Merge reported by: Trey Morris
Merged at revision: not available
Proposed branch: lp:~tr3buchet/rackspace-nova/rs-nova
Merge into: lp:rackspace-nova
Diff against target: 39242 lines (+20555/-7198)
245 files modified
.mailmap (+5/-0)
Authors (+16/-2)
MANIFEST.in (+1/-0)
bin/instance-usage-audit (+116/-0)
bin/nova-ajax-console-proxy (+3/-2)
bin/nova-api (+34/-31)
bin/nova-dhcpbridge (+9/-6)
bin/nova-direct-api (+5/-2)
bin/nova-manage (+206/-71)
bin/nova-objectstore (+5/-2)
bin/nova-vncproxy (+20/-2)
contrib/nova.sh (+13/-7)
doc/.autogenerated (+0/-283)
doc/build/html/.buildinfo (+0/-4)
doc/source/devref/distributed_scheduler.rst (+188/-0)
doc/source/devref/index.rst (+1/-0)
doc/source/devref/multinic.rst (+39/-0)
doc/source/devref/zone.rst (+5/-5)
doc/source/man/novamanage.rst (+2/-2)
doc/source/runnova/managing.users.rst (+3/-3)
nova/__init__.py (+5/-0)
nova/api/direct.py (+9/-5)
nova/api/ec2/__init__.py (+11/-0)
nova/api/ec2/admin.py (+64/-4)
nova/api/ec2/apirequest.py (+3/-75)
nova/api/ec2/cloud.py (+240/-78)
nova/api/ec2/ec2utils.py (+94/-0)
nova/api/ec2/metadatarequesthandler.py (+13/-2)
nova/api/openstack/__init__.py (+44/-49)
nova/api/openstack/accounts.py (+21/-12)
nova/api/openstack/auth.py (+12/-10)
nova/api/openstack/backup_schedules.py (+22/-12)
nova/api/openstack/common.py (+54/-51)
nova/api/openstack/consoles.py (+8/-11)
nova/api/openstack/contrib/__init__.py (+1/-1)
nova/api/openstack/contrib/flavorextraspecs.py (+126/-0)
nova/api/openstack/contrib/floating_ips.py (+173/-0)
nova/api/openstack/contrib/hosts.py (+114/-0)
nova/api/openstack/contrib/multinic.py (+125/-0)
nova/api/openstack/contrib/volumes.py (+14/-17)
nova/api/openstack/create_instance_helper.py (+354/-0)
nova/api/openstack/extensions.py (+67/-47)
nova/api/openstack/faults.py (+23/-16)
nova/api/openstack/flavors.py (+25/-15)
nova/api/openstack/image_metadata.py (+75/-13)
nova/api/openstack/images.py (+303/-60)
nova/api/openstack/ips.py (+34/-27)
nova/api/openstack/limits.py (+40/-17)
nova/api/openstack/notes.txt (+0/-3)
nova/api/openstack/ratelimiting/__init__.py (+1/-1)
nova/api/openstack/server_metadata.py (+58/-20)
nova/api/openstack/servers.py (+187/-348)
nova/api/openstack/shared_ip_groups.py (+14/-26)
nova/api/openstack/users.py (+28/-17)
nova/api/openstack/versions.py (+22/-26)
nova/api/openstack/views/addresses.py (+6/-4)
nova/api/openstack/views/flavors.py (+9/-7)
nova/api/openstack/views/images.py (+40/-27)
nova/api/openstack/views/limits.py (+23/-10)
nova/api/openstack/views/servers.py (+28/-17)
nova/api/openstack/wsgi.py (+473/-0)
nova/api/openstack/zones.py (+97/-29)
nova/auth/fakeldap.py (+24/-0)
nova/auth/ldapdriver.py (+113/-14)
nova/auth/manager.py (+19/-13)
nova/auth/novarc.template (+4/-1)
nova/compute/api.py (+502/-172)
nova/compute/instance_types.py (+1/-1)
nova/compute/manager.py (+431/-174)
nova/compute/monitor.py (+2/-1)
nova/compute/utils.py (+29/-0)
nova/console/manager.py (+2/-2)
nova/console/vmrc.py (+1/-1)
nova/console/vmrc_manager.py (+2/-2)
nova/context.py (+0/-1)
nova/crypto.py (+2/-1)
nova/db/api.py (+259/-45)
nova/db/sqlalchemy/api.py (+993/-294)
nova/db/sqlalchemy/migrate_repo/versions/016_make_quotas_key_and_value.py (+6/-6)
nova/db/sqlalchemy/migrate_repo/versions/018_rename_server_management_url.py (+5/-24)
nova/db/sqlalchemy/migrate_repo/versions/019_add_volume_snapshot_support.py (+70/-0)
nova/db/sqlalchemy/migrate_repo/versions/020_add_snapshot_id_to_volumes.py (+47/-0)
nova/db/sqlalchemy/migrate_repo/versions/021_rename_image_ids.py (+40/-0)
nova/db/sqlalchemy/migrate_repo/versions/022_set_engine_mysql_innodb.py (+65/-0)
nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py (+45/-0)
nova/db/sqlalchemy/migrate_repo/versions/024_add_block_device_mapping.py (+87/-0)
nova/db/sqlalchemy/migrate_repo/versions/025_add_uuid_to_instances.py (+43/-0)
nova/db/sqlalchemy/migrate_repo/versions/026_add_agent_table.py (+73/-0)
nova/db/sqlalchemy/migrate_repo/versions/027_add_provider_firewall_rules.py (+74/-0)
nova/db/sqlalchemy/migrate_repo/versions/028_add_instance_type_extra_specs.py (+67/-0)
nova/db/sqlalchemy/migrate_repo/versions/029_add_zone_weight_offsets.py (+38/-0)
nova/db/sqlalchemy/migrate_repo/versions/030_multi_nic.py (+125/-0)
nova/db/sqlalchemy/migrate_repo/versions/031_fk_fixed_ips_virtual_interface_id.py (+56/-0)
nova/db/sqlalchemy/migrate_repo/versions/031_sqlite_downgrade.sql (+48/-0)
nova/db/sqlalchemy/migrate_repo/versions/031_sqlite_upgrade.sql (+48/-0)
nova/db/sqlalchemy/models.py (+172/-45)
nova/exception.py (+155/-29)
nova/fakerabbit.py (+23/-8)
nova/flags.py (+14/-6)
nova/image/__init__.py (+98/-0)
nova/image/fake.py (+89/-24)
nova/image/glance.py (+41/-12)
nova/image/local.py (+0/-165)
nova/image/s3.py (+70/-29)
nova/image/service.py (+2/-2)
nova/log.py (+21/-0)
nova/network/api.py (+82/-14)
nova/network/linux_net.py (+15/-6)
nova/network/manager.py (+558/-286)
nova/network/vmwareapi_net.py (+5/-7)
nova/network/xenapi_net.py (+6/-4)
nova/notifier/api.py (+16/-5)
nova/notifier/test_notifier.py (+28/-0)
nova/objectstore/s3server.py (+1/-1)
nova/quota.py (+78/-52)
nova/rpc.py (+215/-71)
nova/scheduler/api.py (+131/-49)
nova/scheduler/driver.py (+11/-9)
nova/scheduler/host_filter.py (+98/-37)
nova/scheduler/least_cost.py (+174/-0)
nova/scheduler/manager.py (+21/-2)
nova/scheduler/simple.py (+12/-7)
nova/scheduler/zone_aware_scheduler.py (+314/-52)
nova/scheduler/zone_manager.py (+56/-8)
nova/service.py (+142/-99)
nova/test.py (+41/-30)
nova/tests/__init__.py (+17/-9)
nova/tests/api/__init__.py (+19/-0)
nova/tests/api/openstack/__init__.py (+3/-0)
nova/tests/api/openstack/contrib/__init__.py (+15/-0)
nova/tests/api/openstack/contrib/test_floating_ips.py (+189/-0)
nova/tests/api/openstack/contrib/test_multinic_xs.py (+115/-0)
nova/tests/api/openstack/extensions/foxinsocks.py (+1/-3)
nova/tests/api/openstack/extensions/test_flavors_extra_specs.py (+198/-0)
nova/tests/api/openstack/fakes.py (+57/-17)
nova/tests/api/openstack/test_api.py (+21/-0)
nova/tests/api/openstack/test_auth.py (+65/-0)
nova/tests/api/openstack/test_common.py (+137/-83)
nova/tests/api/openstack/test_extensions.py (+15/-2)
nova/tests/api/openstack/test_flavors.py (+31/-35)
nova/tests/api/openstack/test_image_metadata.py (+161/-10)
nova/tests/api/openstack/test_images.py (+1114/-152)
nova/tests/api/openstack/test_limits.py (+140/-5)
nova/tests/api/openstack/test_server_metadata.py (+78/-2)
nova/tests/api/openstack/test_servers.py (+398/-127)
nova/tests/api/openstack/test_wsgi.py (+346/-0)
nova/tests/api/openstack/test_zones.py (+10/-6)
nova/tests/api/test_wsgi.py (+0/-189)
nova/tests/db/fakes.py (+334/-35)
nova/tests/fake_flags.py (+14/-14)
nova/tests/glance/stubs.py (+10/-9)
nova/tests/image/__init__.py (+3/-0)
nova/tests/image/test_glance.py (+2/-4)
nova/tests/integrated/__init__.py (+2/-0)
nova/tests/integrated/api/client.py (+20/-15)
nova/tests/integrated/integrated_helpers.py (+21/-23)
nova/tests/integrated/test_servers.py (+106/-0)
nova/tests/integrated/test_xml.py (+2/-2)
nova/tests/network/__init__.py (+0/-67)
nova/tests/network/base.py (+0/-155)
nova/tests/real_flags.py (+0/-26)
nova/tests/scheduler/__init__.py (+19/-0)
nova/tests/scheduler/test_host_filter.py (+240/-0)
nova/tests/scheduler/test_least_cost_scheduler.py (+145/-0)
nova/tests/scheduler/test_scheduler.py (+43/-21)
nova/tests/scheduler/test_zone_aware_scheduler.py (+245/-35)
nova/tests/test_adminapi.py (+107/-0)
nova/tests/test_api.py (+24/-1)
nova/tests/test_auth.py (+11/-0)
nova/tests/test_cloud.py (+640/-62)
nova/tests/test_compute.py (+139/-20)
nova/tests/test_console.py (+0/-3)
nova/tests/test_crypto.py (+83/-0)
nova/tests/test_direct.py (+22/-21)
nova/tests/test_exception.py (+63/-0)
nova/tests/test_flags.py (+14/-0)
nova/tests/test_flat_network.py (+0/-161)
nova/tests/test_host_filter.py (+0/-208)
nova/tests/test_hosts.py (+102/-0)
nova/tests/test_instance_types_extra_specs.py (+165/-0)
nova/tests/test_iptables_network.py (+164/-0)
nova/tests/test_libvirt.py (+380/-69)
nova/tests/test_metadata.py (+76/-0)
nova/tests/test_middleware.py (+0/-1)
nova/tests/test_misc.py (+13/-0)
nova/tests/test_network.py (+267/-160)
nova/tests/test_notifier.py (+22/-3)
nova/tests/test_objectstore.py (+7/-2)
nova/tests/test_quota.py (+83/-29)
nova/tests/test_rpc.py (+107/-11)
nova/tests/test_service.py (+83/-6)
nova/tests/test_utils.py (+31/-0)
nova/tests/test_vlan_network.py (+0/-242)
nova/tests/test_vmwareapi.py (+276/-252)
nova/tests/test_volume.py (+49/-2)
nova/tests/test_wsgi.py (+95/-0)
nova/tests/test_xenapi.py (+260/-66)
nova/tests/test_zones.py (+175/-0)
nova/tests/vmwareapi/db_fakes.py (+2/-2)
nova/tests/xenapi/stubs.py (+67/-22)
nova/twistd.py (+3/-3)
nova/utils.py (+106/-21)
nova/virt/connection.py (+1/-1)
nova/virt/driver.py (+16/-4)
nova/virt/fake.py (+63/-1)
nova/virt/hyperv.py (+12/-3)
nova/virt/images.py (+4/-12)
nova/virt/libvirt.xml.template (+10/-1)
nova/virt/libvirt/connection.py (+132/-731)
nova/virt/libvirt/firewall.py (+820/-0)
nova/virt/libvirt/netutils.py (+102/-0)
nova/virt/vmwareapi/vm_util.py (+5/-1)
nova/virt/vmwareapi/vmops.py (+19/-13)
nova/virt/vmwareapi/vmware_images.py (+7/-15)
nova/virt/vmwareapi_conn.py (+5/-1)
nova/virt/xenapi/fake.py (+8/-8)
nova/virt/xenapi/vm_utils.py (+183/-85)
nova/virt/xenapi/vmops.py (+304/-113)
nova/virt/xenapi/volume_utils.py (+22/-21)
nova/virt/xenapi_conn.py (+10/-6)
nova/vnc/__init__.py (+2/-0)
nova/volume/api.py (+68/-4)
nova/volume/driver.py (+116/-28)
nova/volume/manager.py (+58/-5)
nova/wsgi.py (+144/-333)
plugins/xenserver/networking/etc/init.d/openvswitch-nova (+96/-0)
plugins/xenserver/networking/etc/sysconfig/openvswitch-nova (+1/-0)
plugins/xenserver/networking/etc/udev/rules.d/xen-openvswitch-nova.rules (+3/-0)
plugins/xenserver/networking/etc/xensource/scripts/novalib.py (+40/-0)
plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_base_flows.py (+62/-0)
plugins/xenserver/networking/etc/xensource/scripts/ovs_configure_vif_flows.py (+180/-0)
plugins/xenserver/networking/etc/xensource/scripts/vif_5.6-fp1.patch (+1/-5)
plugins/xenserver/networking/etc/xensource/scripts/vif_rules.py (+11/-19)
plugins/xenserver/xenapi/contrib/build-rpm.sh (+20/-0)
plugins/xenserver/xenapi/contrib/rpmbuild/SPECS/openstack-xen-plugins.spec (+37/-0)
plugins/xenserver/xenapi/etc/xapi.d/plugins/agent (+34/-2)
plugins/xenserver/xenapi/etc/xapi.d/plugins/glance (+74/-28)
plugins/xenserver/xenapi/etc/xapi.d/plugins/migration (+1/-1)
plugins/xenserver/xenapi/etc/xapi.d/plugins/xenhost (+44/-7)
run_tests.py (+79/-20)
run_tests.sh (+25/-8)
tools/clean-vlans (+3/-3)
tools/install_venv.py (+1/-1)
tools/nova-debug (+13/-8)
tools/pip-requires (+6/-7)
To merge this branch: bzr merge lp:~tr3buchet/rackspace-nova/rs-nova
Reviewer Review Type Date Requested Status
Ozone Pending
Review via email: mp+68016@code.launchpad.net

Description of the change

merged trunk

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.mailmap'
2--- .mailmap 2011-05-11 19:16:37 +0000
3+++ .mailmap 2011-07-14 20:24:25 +0000
4@@ -47,3 +47,8 @@
5 <vishvananda@gmail.com> <root@mirror.nasanebula.net>
6 <vishvananda@gmail.com> <root@ubuntu>
7 <vishvananda@gmail.com> <vishvananda@yahoo.com>
8+<ilyaalekseyev@acm.org> <ialekseev@griddynamics.com>
9+<ilyaalekseyev@acm.org> <ilya@oscloud.ru>
10+<reldan@oscloud.ru> <enugaev@griddynamics.com>
11+<kshileev@gmail.com> <kshileev@griddynamics.com>
12+<nsokolov@griddynamics.com> <nsokolov@griddynamics.net>
13
14=== modified file 'Authors'
15--- Authors 2011-05-20 06:51:29 +0000
16+++ Authors 2011-07-14 20:24:25 +0000
17@@ -1,4 +1,6 @@
18 Alex Meade <alex.meade@rackspace.com>
19+Alexander Sakhnov <asakhnov@mirantis.com>
20+Andrey Brindeyev <abrindeyev@griddynamics.com>
21 Andy Smith <code@term.ie>
22 Andy Southgate <andy.southgate@citrix.com>
23 Anne Gentle <anne@openstack.org>
24@@ -16,18 +18,22 @@
25 Chuck Short <zulcss@ubuntu.com>
26 Cory Wright <corywright@gmail.com>
27 Dan Prince <dan.prince@rackspace.com>
28+Dave Walker <DaveWalker@ubuntu.com>
29 David Pravec <David.Pravec@danix.org>
30 Dean Troyer <dtroyer@gmail.com>
31+Devendra Modium <dmodium@isi.edu>
32 Devin Carlen <devin.carlen@gmail.com>
33 Ed Leafe <ed@leafe.com>
34-Eldar Nugaev <enugaev@griddynamics.com>
35+Eldar Nugaev <reldan@oscloud.ru>
36 Eric Day <eday@oddments.org>
37 Eric Windisch <eric@cloudscaling.com>
38 Ewan Mellor <ewan.mellor@citrix.com>
39 Gabe Westmaas <gabe.westmaas@rackspace.com>
40 Hisaharu Ishii <ishii.hisaharu@lab.ntt.co.jp>
41 Hisaki Ohara <hisaki.ohara@intel.com>
42-Ilya Alekseyev <ialekseev@griddynamics.com>
43+Ilya Alekseyev <ilyaalekseyev@acm.org>
44+Isaku Yamahata <yamahata@valinux.co.jp>
45+Jason Cannavale <jason.cannavale@rackspace.com>
46 Jason Koelker <jason@koelker.net>
47 Jay Pipes <jaypipes@gmail.com>
48 Jesse Andrews <anotherjesse@gmail.com>
49@@ -39,6 +45,7 @@
50 John Tran <jtran@attinteractive.com>
51 Jonathan Bryce <jbryce@jbryce.com>
52 Jordan Rinke <jordan@openstack.org>
53+Joseph Suh <jsuh@isi.edu>
54 Josh Durgin <joshd@hq.newdream.net>
55 Josh Kearney <josh@jk0.org>
56 Josh Kleinpeter <josh@kleinpeter.org>
57@@ -49,6 +56,7 @@
58 Ken Pepple <ken.pepple@gmail.com>
59 Kevin Bringard <kbringard@attinteractive.com>
60 Kevin L. Mitchell <kevin.mitchell@rackspace.com>
61+Kirill Shileev <kshileev@gmail.com>
62 Koji Iida <iida.koji@lab.ntt.co.jp>
63 Lorin Hochstein <lorin@isi.edu>
64 Lvov Maxim <usrleon@gmail.com>
65@@ -56,14 +64,18 @@
66 Masanori Itoh <itoumsn@nttdata.co.jp>
67 Matt Dietz <matt.dietz@rackspace.com>
68 Michael Gundlach <michael.gundlach@rackspace.com>
69+Mike Scherbakov <mihgen@gmail.com>
70+Mohammed Naser <mnaser@vexxhost.com>
71 Monsyne Dragon <mdragon@rackspace.com>
72 Monty Taylor <mordred@inaugust.com>
73 MORITA Kazutaka <morita.kazutaka@gmail.com>
74 Muneyuki Noguchi <noguchimn@nttdata.co.jp>
75 Nachi Ueno <ueno.nachi@lab.ntt.co.jp>
76 Naveed Massjouni <naveedm9@gmail.com>
77+Nikolay Sokolov <nsokolov@griddynamics.com>
78 Nirmal Ranganathan <nirmal.ranganathan@rackspace.com>
79 Paul Voccio <paul@openstack.org>
80+Renuka Apte <renuka.apte@citrix.com>
81 Ricardo Carrillo Cruz <emaildericky@gmail.com>
82 Rick Clark <rick@openstack.org>
83 Rick Harris <rconradharris@gmail.com>
84@@ -73,6 +85,7 @@
85 Salvatore Orlando <salvatore.orlando@eu.citrix.com>
86 Sandy Walsh <sandy.walsh@rackspace.com>
87 Sateesh Chodapuneedi <sateesh.chodapuneedi@citrix.com>
88+Scott Moser <smoser@ubuntu.com>
89 Soren Hansen <soren.hansen@rackspace.com>
90 Thierry Carrez <thierry@openstack.org>
91 Todd Willey <todd@ansolabs.com>
92@@ -80,6 +93,7 @@
93 Tushar Patil <tushar.vitthal.patil@gmail.com>
94 Vasiliy Shlykov <vash@vasiliyshlykov.org>
95 Vishvananda Ishaya <vishvananda@gmail.com>
96+Vivek Y S <vivek.ys@gmail.com>
97 William Wolf <throughnothing@gmail.com>
98 Yoshiaki Tamura <yoshi@midokura.jp>
99 Youcef Laribi <Youcef.Laribi@eu.citrix.com>
100
101=== modified file 'MANIFEST.in'
102--- MANIFEST.in 2011-05-20 19:21:04 +0000
103+++ MANIFEST.in 2011-07-14 20:24:25 +0000
104@@ -23,6 +23,7 @@
105 include nova/console/xvp.conf.template
106 include nova/db/sqlalchemy/migrate_repo/migrate.cfg
107 include nova/db/sqlalchemy/migrate_repo/README
108+include nova/db/sqlalchemy/migrate_repo/versions/*.sql
109 include nova/virt/interfaces.template
110 include nova/virt/libvirt*.xml.template
111 include nova/virt/cpuinfo.xml.template
112
113=== added file 'bin/instance-usage-audit'
114--- bin/instance-usage-audit 1970-01-01 00:00:00 +0000
115+++ bin/instance-usage-audit 2011-07-14 20:24:25 +0000
116@@ -0,0 +1,116 @@
117+#!/usr/bin/env python
118+# vim: tabstop=4 shiftwidth=4 softtabstop=4
119+
120+# Copyright (c) 2011 Openstack, LLC.
121+# All Rights Reserved.
122+#
123+# Licensed under the Apache License, Version 2.0 (the "License"); you may
124+# not use this file except in compliance with the License. You may obtain
125+# a copy of the License at
126+#
127+# http://www.apache.org/licenses/LICENSE-2.0
128+#
129+# Unless required by applicable law or agreed to in writing, software
130+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
131+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
132+# License for the specific language governing permissions and limitations
133+# under the License.
134+
135+"""Cron script to generate usage notifications for instances neither created
136+ nor destroyed in a given time period.
137+
138+ Together with the notifications generated by compute on instance
139+ create/delete/resize, over that ime period, this allows an external
140+ system consuming usage notification feeds to calculate instance usage
141+ for each tenant.
142+
143+ Time periods are specified like so:
144+ <number>[mdy]
145+
146+ 1m = previous month. If the script is run April 1, it will generate usages
147+ for March 1 thry March 31.
148+ 3m = 3 previous months.
149+ 90d = previous 90 days.
150+ 1y = previous year. If run on Jan 1, it generates usages for
151+ Jan 1 thru Dec 31 of the previous year.
152+"""
153+
154+import datetime
155+import gettext
156+import os
157+import sys
158+import time
159+
160+# If ../nova/__init__.py exists, add ../ to Python search path, so that
161+# it will override what happens to be installed in /usr/(local/)lib/python...
162+POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
163+ os.pardir,
164+ os.pardir))
165+if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')):
166+ sys.path.insert(0, POSSIBLE_TOPDIR)
167+
168+gettext.install('nova', unicode=1)
169+
170+
171+from nova import context
172+from nova import db
173+from nova import exception
174+from nova import flags
175+from nova import log as logging
176+from nova import utils
177+
178+from nova.notifier import api as notifier_api
179+
180+FLAGS = flags.FLAGS
181+flags.DEFINE_string('instance_usage_audit_period', '1m',
182+ 'time period to generate instance usages for.')
183+
184+
185+def time_period(period):
186+ today = datetime.date.today()
187+ unit = period[-1]
188+ if unit not in 'mdy':
189+ raise ValueError('Time period must be m, d, or y')
190+ n = int(period[:-1])
191+ if unit == 'm':
192+ year = today.year - (n // 12)
193+ n = n % 12
194+ if n >= today.month:
195+ year -= 1
196+ month = 12 + (today.month - n)
197+ else:
198+ month = today.month - n
199+ begin = datetime.datetime(day=1, month=month, year=year)
200+ end = datetime.datetime(day=1, month=today.month, year=today.year)
201+
202+ elif unit == 'y':
203+ begin = datetime.datetime(day=1, month=1, year=today.year - n)
204+ end = datetime.datetime(day=1, month=1, year=today.year)
205+
206+ elif unit == 'd':
207+ b = today - datetime.timedelta(days=n)
208+ begin = datetime.datetime(day=b.day, month=b.month, year=b.year)
209+ end = datetime.datetime(day=today.day,
210+ month=today.month,
211+ year=today.year)
212+
213+ return (begin, end)
214+
215+if __name__ == '__main__':
216+ utils.default_flagfile()
217+ flags.FLAGS(sys.argv)
218+ logging.setup()
219+ begin, end = time_period(FLAGS.instance_usage_audit_period)
220+ print "Creating usages for %s until %s" % (str(begin), str(end))
221+ instances = db.instance_get_active_by_window(context.get_admin_context(),
222+ begin,
223+ end)
224+ print "%s instances" % len(instances)
225+ for instance_ref in instances:
226+ usage_info = utils.usage_from_instance(instance_ref,
227+ audit_period_begining=str(begin),
228+ audit_period_ending=str(end))
229+ notifier_api.notify('compute.%s' % FLAGS.host,
230+ 'compute.instance.exists',
231+ notifier_api.INFO,
232+ usage_info)
233
234=== modified file 'bin/nova-ajax-console-proxy'
235--- bin/nova-ajax-console-proxy 2011-03-29 23:13:09 +0000
236+++ bin/nova-ajax-console-proxy 2011-07-14 20:24:25 +0000
237@@ -137,8 +137,9 @@
238 utils.default_flagfile()
239 FLAGS(sys.argv)
240 logging.setup()
241- server = wsgi.Server()
242+ acp_port = FLAGS.ajax_console_proxy_port
243 acp = AjaxConsoleProxy()
244 acp.register_listeners()
245- server.start(acp, FLAGS.ajax_console_proxy_port, host='0.0.0.0')
246+ server = wsgi.Server("AJAX Console Proxy", acp, port=acp_port)
247+ server.start()
248 server.wait()
249
250=== modified file 'bin/nova-api'
251--- bin/nova-api 2011-03-18 13:56:05 +0000
252+++ bin/nova-api 2011-07-14 20:24:25 +0000
253@@ -1,5 +1,4 @@
254 #!/usr/bin/env python
255-# pylint: disable=C0103
256 # vim: tabstop=4 shiftwidth=4 softtabstop=4
257
258 # Copyright 2010 United States Government as represented by the
259@@ -18,44 +17,48 @@
260 # See the License for the specific language governing permissions and
261 # limitations under the License.
262
263-"""Starter script for Nova API."""
264-
265-import gettext
266+"""Starter script for Nova API.
267+
268+Starts both the EC2 and OpenStack APIs in separate processes.
269+
270+"""
271+
272 import os
273+import signal
274 import sys
275
276-# If ../nova/__init__.py exists, add ../ to Python search path, so that
277-# it will override what happens to be installed in /usr/(local/)lib/python...
278-possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
279- os.pardir,
280- os.pardir))
281-if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
282+
283+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(
284+ sys.argv[0]), os.pardir, os.pardir))
285+if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")):
286 sys.path.insert(0, possible_topdir)
287
288-gettext.install('nova', unicode=1)
289+import nova.service
290+import nova.utils
291
292 from nova import flags
293-from nova import log as logging
294-from nova import service
295-from nova import utils
296-from nova import version
297-from nova import wsgi
298-
299-
300-LOG = logging.getLogger('nova.api')
301+
302
303 FLAGS = flags.FLAGS
304
305+
306+def main():
307+ """Launch EC2 and OSAPI services."""
308+ nova.utils.Bootstrapper.bootstrap_binary(sys.argv)
309+
310+ launcher = nova.service.Launcher()
311+
312+ for api in FLAGS.enabled_apis:
313+ service = nova.service.WSGIService(api)
314+ launcher.launch_service(service)
315+
316+ signal.signal(signal.SIGTERM, lambda *_: launcher.stop())
317+
318+ try:
319+ launcher.wait()
320+ except KeyboardInterrupt:
321+ launcher.stop()
322+
323+
324 if __name__ == '__main__':
325- utils.default_flagfile()
326- FLAGS(sys.argv)
327- logging.setup()
328- LOG.audit(_("Starting nova-api node (version %s)"),
329- version.version_string_with_vcs())
330- LOG.debug(_("Full set of FLAGS:"))
331- for flag in FLAGS:
332- flag_get = FLAGS.get(flag, None)
333- LOG.debug("%(flag)s : %(flag_get)s" % locals())
334-
335- service = service.serve_wsgi(service.ApiService)
336- service.wait()
337+ sys.exit(main())
338
339=== modified file 'bin/nova-dhcpbridge'
340--- bin/nova-dhcpbridge 2011-03-29 20:32:44 +0000
341+++ bin/nova-dhcpbridge 2011-07-14 20:24:25 +0000
342@@ -59,14 +59,12 @@
343 LOG.debug(_("leasing ip"))
344 network_manager = utils.import_object(FLAGS.network_manager)
345 network_manager.lease_fixed_ip(context.get_admin_context(),
346- mac,
347 ip_address)
348 else:
349 rpc.cast(context.get_admin_context(),
350 "%s.%s" % (FLAGS.network_topic, FLAGS.host),
351 {"method": "lease_fixed_ip",
352- "args": {"mac": mac,
353- "address": ip_address}})
354+ "args": {"address": ip_address}})
355
356
357 def old_lease(mac, ip_address, hostname, interface):
358@@ -81,14 +79,12 @@
359 LOG.debug(_("releasing ip"))
360 network_manager = utils.import_object(FLAGS.network_manager)
361 network_manager.release_fixed_ip(context.get_admin_context(),
362- mac,
363 ip_address)
364 else:
365 rpc.cast(context.get_admin_context(),
366 "%s.%s" % (FLAGS.network_topic, FLAGS.host),
367 {"method": "release_fixed_ip",
368- "args": {"mac": mac,
369- "address": ip_address}})
370+ "args": {"address": ip_address}})
371
372
373 def init_leases(interface):
374@@ -108,6 +104,13 @@
375 interface = os.environ.get('DNSMASQ_INTERFACE', FLAGS.dnsmasq_interface)
376 if int(os.environ.get('TESTING', '0')):
377 from nova.tests import fake_flags
378+
379+ #if FLAGS.fake_rabbit:
380+ # LOG.debug(_("leasing ip"))
381+ # network_manager = utils.import_object(FLAGS.network_manager)
382+ ## reload(fake_flags)
383+ # from nova.tests import fake_flags
384+
385 action = argv[1]
386 if action in ['add', 'del', 'old']:
387 mac = argv[2]
388
389=== modified file 'bin/nova-direct-api'
390--- bin/nova-direct-api 2011-03-24 20:20:15 +0000
391+++ bin/nova-direct-api 2011-07-14 20:24:25 +0000
392@@ -93,6 +93,9 @@
393 with_req = direct.PostParamsMiddleware(with_json)
394 with_auth = direct.DelegatedAuthMiddleware(with_req)
395
396- server = wsgi.Server()
397- server.start(with_auth, FLAGS.direct_port, host=FLAGS.direct_host)
398+ server = wsgi.Server("Direct API",
399+ with_auth,
400+ host=FLAGS.direct_host,
401+ port=FLAGS.direct_port)
402+ server.start()
403 server.wait()
404
405=== modified file 'bin/nova-manage'
406--- bin/nova-manage 2011-05-20 09:29:54 +0000
407+++ bin/nova-manage 2011-07-14 20:24:25 +0000
408@@ -53,15 +53,14 @@
409 CLI interface for nova management.
410 """
411
412-import datetime
413 import gettext
414 import glob
415 import json
416+import netaddr
417 import os
418 import sys
419 import time
420
421-import IPy
422
423 # If ../nova/__init__.py exists, add ../ to Python search path, so that
424 # it will override what happens to be installed in /usr/(local/)lib/python...
425@@ -78,6 +77,7 @@
426 from nova import db
427 from nova import exception
428 from nova import flags
429+from nova import image
430 from nova import log as logging
431 from nova import quota
432 from nova import rpc
433@@ -96,8 +96,8 @@
434 flags.DECLARE('vlan_start', 'nova.network.manager')
435 flags.DECLARE('vpn_start', 'nova.network.manager')
436 flags.DECLARE('fixed_range_v6', 'nova.network.manager')
437-flags.DECLARE('images_path', 'nova.image.local')
438-flags.DECLARE('libvirt_type', 'nova.virt.libvirt_conn')
439+flags.DECLARE('gateway_v6', 'nova.network.manager')
440+flags.DECLARE('libvirt_type', 'nova.virt.libvirt.connection')
441 flags.DEFINE_flag(flags.HelpFlag())
442 flags.DEFINE_flag(flags.HelpshortFlag())
443 flags.DEFINE_flag(flags.HelpXMLFlag())
444@@ -172,17 +172,23 @@
445 def change(self, project_id, ip, port):
446 """Change the ip and port for a vpn.
447
448+ this will update all networks associated with a project
449+ not sure if that's the desired behavior or not, patches accepted
450+
451 args: project, ip, port"""
452+ # TODO(tr3buchet): perhaps this shouldn't update all networks
453+ # associated with a project in the future
454 project = self.manager.get_project(project_id)
455 if not project:
456 print 'No project %s' % (project_id)
457 return
458- admin = context.get_admin_context()
459- network_ref = db.project_get_network(admin, project_id)
460- db.network_update(admin,
461- network_ref['id'],
462- {'vpn_public_address': ip,
463- 'vpn_public_port': int(port)})
464+ admin_context = context.get_admin_context()
465+ networks = db.project_get_networks(admin_context, project_id)
466+ for network in networks:
467+ db.network_update(admin_context,
468+ network['id'],
469+ {'vpn_public_address': ip,
470+ 'vpn_public_port': int(port)})
471
472
473 class ShellCommands(object):
474@@ -257,6 +263,11 @@
475 """adds role to user
476 if project is specified, adds project specific role
477 arguments: user, role [project]"""
478+ if project:
479+ projobj = self.manager.get_project(project)
480+ if not projobj.has_member(user):
481+ print "%s not a member of %s" % (user, project)
482+ return
483 self.manager.add_role(user, role, project)
484
485 def has(self, user, role, project=None):
486@@ -403,8 +414,11 @@
487 except (exception.UserNotFound, exception.ProjectNotFound) as ex:
488 print ex
489 raise
490- with open(filename, 'w') as f:
491- f.write(rc)
492+ if filename == "-":
493+ sys.stdout.write(rc)
494+ else:
495+ with open(filename, 'w') as f:
496+ f.write(rc)
497
498 def list(self, username=None):
499 """Lists all projects
500@@ -417,12 +431,16 @@
501 arguments: project_id [key] [value]"""
502 ctxt = context.get_admin_context()
503 if key:
504+ if value.lower() == 'unlimited':
505+ value = None
506 try:
507 db.quota_update(ctxt, project_id, key, value)
508 except exception.ProjectQuotaNotFound:
509 db.quota_create(ctxt, project_id, key, value)
510- project_quota = quota.get_quota(ctxt, project_id)
511+ project_quota = quota.get_project_quotas(ctxt, project_id)
512 for key, value in project_quota.iteritems():
513+ if value is None:
514+ value = 'unlimited'
515 print '%s: %s' % (key, value)
516
517 def remove(self, project_id, user_id):
518@@ -437,20 +455,24 @@
519 def scrub(self, project_id):
520 """Deletes data associated with project
521 arguments: project_id"""
522- ctxt = context.get_admin_context()
523- network_ref = db.project_get_network(ctxt, project_id)
524- db.network_disassociate(ctxt, network_ref['id'])
525- groups = db.security_group_get_by_project(ctxt, project_id)
526+ admin_context = context.get_admin_context()
527+ networks = db.project_get_networks(admin_context, project_id)
528+ for network in networks:
529+ db.network_disassociate(admin_context, network['id'])
530+ groups = db.security_group_get_by_project(admin_context, project_id)
531 for group in groups:
532- db.security_group_destroy(ctxt, group['id'])
533+ db.security_group_destroy(admin_context, group['id'])
534
535 def zipfile(self, project_id, user_id, filename='nova.zip'):
536 """Exports credentials for project to a zip file
537 arguments: project_id user_id [filename='nova.zip]"""
538 try:
539 zip_file = self.manager.get_credentials(user_id, project_id)
540- with open(filename, 'w') as f:
541- f.write(zip_file)
542+ if filename == "-":
543+ sys.stdout.write(zip_file)
544+ else:
545+ with open(filename, 'w') as f:
546+ f.write(zip_file)
547 except (exception.UserNotFound, exception.ProjectNotFound) as ex:
548 print ex
549 raise
550@@ -496,7 +518,7 @@
551 instance = fixed_ip['instance']
552 hostname = instance['hostname']
553 host = instance['host']
554- mac_address = instance['mac_address']
555+ mac_address = fixed_ip['mac_address']['address']
556 print "%-18s\t%-15s\t%-17s\t%-15s\t%s" % (
557 fixed_ip['network']['cidr'],
558 fixed_ip['address'],
559@@ -506,24 +528,24 @@
560 class FloatingIpCommands(object):
561 """Class for managing floating ip."""
562
563- def create(self, host, range):
564- """Creates floating ips for host by range
565- arguments: host ip_range"""
566- for address in IPy.IP(range):
567+ def create(self, range):
568+ """Creates floating ips for zone by range
569+ arguments: ip_range"""
570+ for address in netaddr.IPNetwork(range):
571 db.floating_ip_create(context.get_admin_context(),
572- {'address': str(address),
573- 'host': host})
574+ {'address': str(address)})
575
576 def delete(self, ip_range):
577 """Deletes floating ips by range
578 arguments: range"""
579- for address in IPy.IP(ip_range):
580+ for address in netaddr.IPNetwork(ip_range):
581 db.floating_ip_destroy(context.get_admin_context(),
582 str(address))
583
584 def list(self, host=None):
585 """Lists all floating ips (optionally by host)
586- arguments: [host]"""
587+ arguments: [host]
588+ Note: if host is given, only active floating IPs are returned"""
589 ctxt = context.get_admin_context()
590 if host is None:
591 floating_ips = db.floating_ip_get_all(ctxt)
592@@ -532,7 +554,7 @@
593 for floating_ip in floating_ips:
594 instance = None
595 if floating_ip['fixed_ip']:
596- instance = floating_ip['fixed_ip']['instance']['ec2_id']
597+ instance = floating_ip['fixed_ip']['instance']['hostname']
598 print "%s\t%s\t%s" % (floating_ip['host'],
599 floating_ip['address'],
600 instance)
601@@ -541,13 +563,23 @@
602 class NetworkCommands(object):
603 """Class for managing networks."""
604
605- def create(self, fixed_range=None, num_networks=None,
606+ def create(self, label=None, fixed_range=None, num_networks=None,
607 network_size=None, vlan_start=None,
608- vpn_start=None, fixed_range_v6=None, label='public'):
609+ vpn_start=None, fixed_range_v6=None, gateway_v6=None,
610+ flat_network_bridge=None, bridge_interface=None):
611 """Creates fixed ips for host by range
612- arguments: fixed_range=FLAG, [num_networks=FLAG],
613+ arguments: label, fixed_range, [num_networks=FLAG],
614 [network_size=FLAG], [vlan_start=FLAG],
615- [vpn_start=FLAG], [fixed_range_v6=FLAG]"""
616+ [vpn_start=FLAG], [fixed_range_v6=FLAG], [gateway_v6=FLAG],
617+ [flat_network_bridge=FLAG], [bridge_interface=FLAG]
618+ If you wish to use a later argument fill in the gaps with 0s
619+ Ex: network create private 10.0.0.0/8 1 15 0 0 0 0 xenbr1 eth1
620+ network create private 10.0.0.0/8 1 15
621+ """
622+ if not label:
623+ msg = _('a label (ex: public) is required to create networks.')
624+ print msg
625+ raise TypeError(msg)
626 if not fixed_range:
627 msg = _('Fixed range in the form of 10.0.0.0/8 is '
628 'required to create networks.')
629@@ -563,31 +595,45 @@
630 vpn_start = FLAGS.vpn_start
631 if not fixed_range_v6:
632 fixed_range_v6 = FLAGS.fixed_range_v6
633+ if not flat_network_bridge:
634+ flat_network_bridge = FLAGS.flat_network_bridge
635+ if not bridge_interface:
636+ bridge_interface = FLAGS.flat_interface or FLAGS.vlan_interface
637+ if not gateway_v6:
638+ gateway_v6 = FLAGS.gateway_v6
639 net_manager = utils.import_object(FLAGS.network_manager)
640+
641 try:
642 net_manager.create_networks(context.get_admin_context(),
643+ label=label,
644 cidr=fixed_range,
645 num_networks=int(num_networks),
646 network_size=int(network_size),
647 vlan_start=int(vlan_start),
648 vpn_start=int(vpn_start),
649 cidr_v6=fixed_range_v6,
650- label=label)
651+ gateway_v6=gateway_v6,
652+ bridge=flat_network_bridge,
653+ bridge_interface=bridge_interface)
654 except ValueError, e:
655 print e
656 raise e
657
658 def list(self):
659 """List all created networks"""
660- print "%-18s\t%-15s\t%-15s\t%-15s" % (_('network'),
661- _('netmask'),
662- _('start address'),
663- 'DNS')
664+ print "%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % (_('network'),
665+ _('netmask'),
666+ _('start address'),
667+ _('DNS'),
668+ _('VlanID'),
669+ 'project')
670 for network in db.network_get_all(context.get_admin_context()):
671- print "%-18s\t%-15s\t%-15s\t%-15s" % (network.cidr,
672- network.netmask,
673- network.dhcp_start,
674- network.dns)
675+ print "%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % (network.cidr,
676+ network.netmask,
677+ network.dhcp_start,
678+ network.dns,
679+ network.vlan,
680+ network.project_id)
681
682 def delete(self, fixed_range):
683 """Deletes a network"""
684@@ -608,7 +654,7 @@
685 :param host: show all instance on specified host.
686 :param instance: show specificed instance.
687 """
688- print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
689+ print "%-10s %-15s %-10s %-10s %-26s %-9s %-9s %-9s" \
690 " %-10s %-10s %-10s %-5s" % (
691 _('instance'),
692 _('node'),
693@@ -630,14 +676,14 @@
694 context.get_admin_context(), host)
695
696 for instance in instances:
697- print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
698+ print "%-10s %-15s %-10s %-10s %-26s %-9s %-9s %-9s" \
699 " %-10s %-10s %-10s %-5d" % (
700 instance['hostname'],
701 instance['host'],
702- instance['instance_type'],
703+ instance['instance_type'].name,
704 instance['state_description'],
705 instance['launched_at'],
706- instance['image_id'],
707+ instance['image_ref'],
708 instance['kernel_id'],
709 instance['ramdisk_id'],
710 instance['project_id'],
711@@ -685,7 +731,7 @@
712 """Show a list of all running services. Filter by host & service name.
713 args: [host] [service]"""
714 ctxt = context.get_admin_context()
715- now = datetime.datetime.utcnow()
716+ now = utils.utcnow()
717 services = db.service_get_all(ctxt)
718 if host:
719 services = [s for s in services if s['host'] == host]
720@@ -776,6 +822,28 @@
721 {"method": "update_available_resource"})
722
723
724+class HostCommands(object):
725+ """List hosts"""
726+
727+ def list(self, zone=None):
728+ """Show a list of all physical hosts. Filter by zone.
729+ args: [zone]"""
730+ print "%-25s\t%-15s" % (_('host'),
731+ _('zone'))
732+ ctxt = context.get_admin_context()
733+ now = utils.utcnow()
734+ services = db.service_get_all(ctxt)
735+ if zone:
736+ services = [s for s in services if s['availability_zone'] == zone]
737+ hosts = []
738+ for srv in services:
739+ if not [h for h in hosts if h['host'] == srv['host']]:
740+ hosts.append(srv)
741+
742+ for h in hosts:
743+ print "%-25s\t%-15s" % (h['host'], h['availability_zone'])
744+
745+
746 class DbCommands(object):
747 """Class for managing the database."""
748
749@@ -869,7 +937,7 @@
750 try:
751 instance_types.create(name, memory, vcpus, local_gb,
752 flavorid, swap, rxtx_quota, rxtx_cap)
753- except exception.InvalidInputException:
754+ except exception.InvalidInput, e:
755 print "Must supply valid parameters to create instance_type"
756 print e
757 sys.exit(1)
758@@ -932,7 +1000,7 @@
759 """Methods for dealing with a cloud in an odd state"""
760
761 def __init__(self, *args, **kwargs):
762- self.image_service = utils.import_object(FLAGS.image_service)
763+ self.image_service = image.get_default_image_service()
764
765 def _register(self, container_format, disk_format,
766 path, owner, name=None, is_public='T',
767@@ -1051,16 +1119,6 @@
768 machine_images = {}
769 other_images = {}
770 directory = os.path.abspath(directory)
771- # NOTE(vish): If we're importing from the images path dir, attempt
772- # to move the files out of the way before importing
773- # so we aren't writing to the same directory. This
774- # may fail if the dir was a mointpoint.
775- if (FLAGS.image_service == 'nova.image.local.LocalImageService'
776- and directory == os.path.abspath(FLAGS.images_path)):
777- new_dir = "%s_bak" % directory
778- os.rename(directory, new_dir)
779- os.mkdir(directory)
780- directory = new_dir
781 for fn in glob.glob("%s/*/info.json" % directory):
782 try:
783 image_path = os.path.join(fn.rpartition('/')[0], 'image')
784@@ -1077,24 +1135,101 @@
785 self._convert_images(machine_images)
786
787
788+class AgentBuildCommands(object):
789+ """Class for managing agent builds."""
790+
791+ def create(self, os, architecture, version, url, md5hash,
792+ hypervisor='xen'):
793+ """Creates a new agent build.
794+ arguments: os architecture version url md5hash [hypervisor='xen']"""
795+ ctxt = context.get_admin_context()
796+ agent_build = db.agent_build_create(ctxt,
797+ {'hypervisor': hypervisor,
798+ 'os': os,
799+ 'architecture': architecture,
800+ 'version': version,
801+ 'url': url,
802+ 'md5hash': md5hash})
803+
804+ def delete(self, os, architecture, hypervisor='xen'):
805+ """Deletes an existing agent build.
806+ arguments: os architecture [hypervisor='xen']"""
807+ ctxt = context.get_admin_context()
808+ agent_build_ref = db.agent_build_get_by_triple(ctxt,
809+ hypervisor, os, architecture)
810+ db.agent_build_destroy(ctxt, agent_build_ref['id'])
811+
812+ def list(self, hypervisor=None):
813+ """Lists all agent builds.
814+ arguments: <none>"""
815+ fmt = "%-10s %-8s %12s %s"
816+ ctxt = context.get_admin_context()
817+ by_hypervisor = {}
818+ for agent_build in db.agent_build_get_all(ctxt):
819+ buildlist = by_hypervisor.get(agent_build.hypervisor)
820+ if not buildlist:
821+ buildlist = by_hypervisor[agent_build.hypervisor] = []
822+
823+ buildlist.append(agent_build)
824+
825+ for key, buildlist in by_hypervisor.iteritems():
826+ if hypervisor and key != hypervisor:
827+ continue
828+
829+ print "Hypervisor: %s" % key
830+ print fmt % ('-' * 10, '-' * 8, '-' * 12, '-' * 32)
831+ for agent_build in buildlist:
832+ print fmt % (agent_build.os, agent_build.architecture,
833+ agent_build.version, agent_build.md5hash)
834+ print ' %s' % agent_build.url
835+
836+ print
837+
838+ def modify(self, os, architecture, version, url, md5hash,
839+ hypervisor='xen'):
840+ """Update an existing agent build.
841+ arguments: os architecture version url md5hash [hypervisor='xen']
842+ """
843+ ctxt = context.get_admin_context()
844+ agent_build_ref = db.agent_build_get_by_triple(ctxt,
845+ hypervisor, os, architecture)
846+ db.agent_build_update(ctxt, agent_build_ref['id'],
847+ {'version': version,
848+ 'url': url,
849+ 'md5hash': md5hash})
850+
851+
852+class ConfigCommands(object):
853+ """Class for exposing the flags defined by flag_file(s)."""
854+
855+ def __init__(self):
856+ pass
857+
858+ def list(self):
859+ print FLAGS.FlagsIntoString()
860+
861+
862 CATEGORIES = [
863- ('user', UserCommands),
864 ('account', AccountCommands),
865+ ('agent', AgentBuildCommands),
866+ ('config', ConfigCommands),
867+ ('db', DbCommands),
868+ ('fixed', FixedIpCommands),
869+ ('flavor', InstanceTypeCommands),
870+ ('floating', FloatingIpCommands),
871+ ('host', HostCommands),
872+ ('instance_type', InstanceTypeCommands),
873+ ('image', ImageCommands),
874+ ('network', NetworkCommands),
875 ('project', ProjectCommands),
876 ('role', RoleCommands),
877+ ('service', ServiceCommands),
878 ('shell', ShellCommands),
879- ('vpn', VpnCommands),
880- ('fixed', FixedIpCommands),
881- ('floating', FloatingIpCommands),
882- ('network', NetworkCommands),
883+ ('user', UserCommands),
884+ ('version', VersionCommands),
885 ('vm', VmCommands),
886- ('service', ServiceCommands),
887- ('db', DbCommands),
888 ('volume', VolumeCommands),
889- ('instance_type', InstanceTypeCommands),
890- ('image', ImageCommands),
891- ('flavor', InstanceTypeCommands),
892- ('version', VersionCommands)]
893+ ('vpn', VpnCommands)]
894
895
896 def lazy_match(name, key_value_tuples):
897
898=== modified file 'bin/nova-objectstore'
899--- bin/nova-objectstore 2011-03-24 23:37:35 +0000
900+++ bin/nova-objectstore 2011-07-14 20:24:25 +0000
901@@ -50,6 +50,9 @@
902 FLAGS(sys.argv)
903 logging.setup()
904 router = s3server.S3Application(FLAGS.buckets_path)
905- server = wsgi.Server()
906- server.start(router, FLAGS.s3_port, host=FLAGS.s3_host)
907+ server = wsgi.Server("S3 Objectstore",
908+ router,
909+ port=FLAGS.s3_port,
910+ host=FLAGS.s3_host)
911+ server.start()
912 server.wait()
913
914=== modified file 'bin/nova-vncproxy'
915--- bin/nova-vncproxy 2011-03-29 21:53:38 +0000
916+++ bin/nova-vncproxy 2011-07-14 20:24:25 +0000
917@@ -63,6 +63,19 @@
918 flags.DEFINE_flag(flags.HelpXMLFlag())
919
920
921+def handle_flash_socket_policy(socket):
922+ LOG.info(_("Received connection on flash socket policy port"))
923+
924+ fd = socket.makefile('rw')
925+ expected_command = "<policy-file-request/>"
926+ if expected_command in fd.read(len(expected_command) + 1):
927+ LOG.info(_("Received valid flash socket policy request"))
928+ fd.write('<?xml version="1.0"?><cross-domain-policy><allow-'
929+ 'access-from domain="*" to-ports="%d" /></cross-'
930+ 'domain-policy>' % (FLAGS.vncproxy_port))
931+ fd.flush()
932+ socket.close()
933+
934 if __name__ == "__main__":
935 utils.default_flagfile()
936 FLAGS(sys.argv)
937@@ -96,6 +109,11 @@
938
939 service.serve()
940
941- server = wsgi.Server()
942- server.start(with_auth, FLAGS.vncproxy_port, host=FLAGS.vncproxy_host)
943+ server = wsgi.Server("VNC Proxy",
944+ with_auth,
945+ host=FLAGS.vncproxy_host,
946+ port=FLAGS.vncproxy_port)
947+ server.start()
948+ server.start_tcp(handle_flash_socket_policy, 843, host=FLAGS.vncproxy_host)
949+
950 server.wait()
951
952=== modified file 'contrib/nova.sh'
953--- contrib/nova.sh 2011-03-25 16:40:59 +0000
954+++ contrib/nova.sh 2011-07-14 20:24:25 +0000
955@@ -17,7 +17,7 @@
956 HOST_IP=`LC_ALL=C ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'`
957 fi
958
959-USE_MYSQL=${USE_MYSQL:-0}
960+USE_MYSQL=${USE_MYSQL:-1}
961 INTERFACE=${INTERFACE:-eth0}
962 FLOATING_RANGE=${FLOATING_RANGE:-10.6.0.0/27}
963 FIXED_RANGE=${FIXED_RANGE:-10.0.0.0/24}
964@@ -159,10 +159,6 @@
965 mkdir -p $NOVA_DIR/instances
966 rm -rf $NOVA_DIR/networks
967 mkdir -p $NOVA_DIR/networks
968- if [ ! -d "$NOVA_DIR/images" ]; then
969- ln -s $DIR/images $NOVA_DIR/images
970- fi
971-
972 if [ "$TEST" == 1 ]; then
973 cd $NOVA_DIR
974 python $NOVA_DIR/run_tests.py
975@@ -181,8 +177,18 @@
976 # create some floating ips
977 $NOVA_DIR/bin/nova-manage floating create `hostname` $FLOATING_RANGE
978
979- # convert old images
980- $NOVA_DIR/bin/nova-manage image convert $DIR/images
981+ if [ ! -d "$NOVA_DIR/images" ]; then
982+ if [ ! -d "$DIR/converted-images" ]; then
983+ # convert old images
984+ mkdir $DIR/converted-images
985+ ln -s $DIR/converted-images $NOVA_DIR/images
986+ $NOVA_DIR/bin/nova-manage image convert $DIR/images
987+ else
988+ ln -s $DIR/converted-images $NOVA_DIR/images
989+ fi
990+
991+ fi
992+
993
994 # nova api crashes if we start it with a regular screen command,
995 # so send the start command by forcing text into the window.
996
997=== removed file 'doc/.autogenerated'
998--- doc/.autogenerated 2011-03-03 00:57:56 +0000
999+++ doc/.autogenerated 1970-01-01 00:00:00 +0000
1000@@ -1,283 +0,0 @@
1001-source/api/nova..adminclient.rst
1002-source/api/nova..api.direct.rst
1003-source/api/nova..api.ec2.admin.rst
1004-source/api/nova..api.ec2.apirequest.rst
1005-source/api/nova..api.ec2.cloud.rst
1006-source/api/nova..api.ec2.metadatarequesthandler.rst
1007-source/api/nova..api.openstack.auth.rst
1008-source/api/nova..api.openstack.backup_schedules.rst
1009-source/api/nova..api.openstack.common.rst
1010-source/api/nova..api.openstack.consoles.rst
1011-source/api/nova..api.openstack.faults.rst
1012-source/api/nova..api.openstack.flavors.rst
1013-source/api/nova..api.openstack.images.rst
1014-source/api/nova..api.openstack.servers.rst
1015-source/api/nova..api.openstack.shared_ip_groups.rst
1016-source/api/nova..api.openstack.zones.rst
1017-source/api/nova..auth.dbdriver.rst
1018-source/api/nova..auth.fakeldap.rst
1019-source/api/nova..auth.ldapdriver.rst
1020-source/api/nova..auth.manager.rst
1021-source/api/nova..auth.signer.rst
1022-source/api/nova..cloudpipe.pipelib.rst
1023-source/api/nova..compute.api.rst
1024-source/api/nova..compute.instance_types.rst
1025-source/api/nova..compute.manager.rst
1026-source/api/nova..compute.monitor.rst
1027-source/api/nova..compute.power_state.rst
1028-source/api/nova..console.api.rst
1029-source/api/nova..console.fake.rst
1030-source/api/nova..console.manager.rst
1031-source/api/nova..console.xvp.rst
1032-source/api/nova..context.rst
1033-source/api/nova..crypto.rst
1034-source/api/nova..db.api.rst
1035-source/api/nova..db.base.rst
1036-source/api/nova..db.migration.rst
1037-source/api/nova..db.sqlalchemy.api.rst
1038-source/api/nova..db.sqlalchemy.migrate_repo.manage.rst
1039-source/api/nova..db.sqlalchemy.migrate_repo.versions.001_austin.rst
1040-source/api/nova..db.sqlalchemy.migrate_repo.versions.002_bexar.rst
1041-source/api/nova..db.sqlalchemy.migrate_repo.versions.003_add_label_to_networks.rst
1042-source/api/nova..db.sqlalchemy.migrate_repo.versions.004_add_zone_tables.rst
1043-source/api/nova..db.sqlalchemy.migrate_repo.versions.005_add_instance_metadata.rst
1044-source/api/nova..db.sqlalchemy.migrate_repo.versions.006_add_provider_data_to_volumes.rst
1045-source/api/nova..db.sqlalchemy.migrate_repo.versions.007_add_instance_types.rst
1046-source/api/nova..db.sqlalchemy.migration.rst
1047-source/api/nova..db.sqlalchemy.models.rst
1048-source/api/nova..db.sqlalchemy.session.rst
1049-source/api/nova..exception.rst
1050-source/api/nova..fakememcache.rst
1051-source/api/nova..fakerabbit.rst
1052-source/api/nova..flags.rst
1053-source/api/nova..image.glance.rst
1054-source/api/nova..image.local.rst
1055-source/api/nova..image.s3.rst
1056-source/api/nova..image.service.rst
1057-source/api/nova..log.rst
1058-source/api/nova..manager.rst
1059-source/api/nova..network.api.rst
1060-source/api/nova..network.linux_net.rst
1061-source/api/nova..network.manager.rst
1062-source/api/nova..objectstore.bucket.rst
1063-source/api/nova..objectstore.handler.rst
1064-source/api/nova..objectstore.image.rst
1065-source/api/nova..objectstore.stored.rst
1066-source/api/nova..quota.rst
1067-source/api/nova..rpc.rst
1068-source/api/nova..scheduler.chance.rst
1069-source/api/nova..scheduler.driver.rst
1070-source/api/nova..scheduler.manager.rst
1071-source/api/nova..scheduler.simple.rst
1072-source/api/nova..scheduler.zone.rst
1073-source/api/nova..service.rst
1074-source/api/nova..test.rst
1075-source/api/nova..tests.api.openstack.fakes.rst
1076-source/api/nova..tests.api.openstack.test_adminapi.rst
1077-source/api/nova..tests.api.openstack.test_api.rst
1078-source/api/nova..tests.api.openstack.test_auth.rst
1079-source/api/nova..tests.api.openstack.test_common.rst
1080-source/api/nova..tests.api.openstack.test_faults.rst
1081-source/api/nova..tests.api.openstack.test_flavors.rst
1082-source/api/nova..tests.api.openstack.test_images.rst
1083-source/api/nova..tests.api.openstack.test_ratelimiting.rst
1084-source/api/nova..tests.api.openstack.test_servers.rst
1085-source/api/nova..tests.api.openstack.test_shared_ip_groups.rst
1086-source/api/nova..tests.api.openstack.test_zones.rst
1087-source/api/nova..tests.api.test_wsgi.rst
1088-source/api/nova..tests.db.fakes.rst
1089-source/api/nova..tests.declare_flags.rst
1090-source/api/nova..tests.fake_flags.rst
1091-source/api/nova..tests.glance.stubs.rst
1092-source/api/nova..tests.hyperv_unittest.rst
1093-source/api/nova..tests.objectstore_unittest.rst
1094-source/api/nova..tests.real_flags.rst
1095-source/api/nova..tests.runtime_flags.rst
1096-source/api/nova..tests.test_access.rst
1097-source/api/nova..tests.test_api.rst
1098-source/api/nova..tests.test_auth.rst
1099-source/api/nova..tests.test_cloud.rst
1100-source/api/nova..tests.test_compute.rst
1101-source/api/nova..tests.test_console.rst
1102-source/api/nova..tests.test_direct.rst
1103-source/api/nova..tests.test_flags.rst
1104-source/api/nova..tests.test_instance_types.rst
1105-source/api/nova..tests.test_localization.rst
1106-source/api/nova..tests.test_log.rst
1107-source/api/nova..tests.test_middleware.rst
1108-source/api/nova..tests.test_misc.rst
1109-source/api/nova..tests.test_network.rst
1110-source/api/nova..tests.test_quota.rst
1111-source/api/nova..tests.test_rpc.rst
1112-source/api/nova..tests.test_scheduler.rst
1113-source/api/nova..tests.test_service.rst
1114-source/api/nova..tests.test_test.rst
1115-source/api/nova..tests.test_twistd.rst
1116-source/api/nova..tests.test_utils.rst
1117-source/api/nova..tests.test_virt.rst
1118-source/api/nova..tests.test_volume.rst
1119-source/api/nova..tests.test_xenapi.rst
1120-source/api/nova..tests.xenapi.stubs.rst
1121-source/api/nova..twistd.rst
1122-source/api/nova..utils.rst
1123-source/api/nova..version.rst
1124-source/api/nova..virt.connection.rst
1125-source/api/nova..virt.disk.rst
1126-source/api/nova..virt.fake.rst
1127-source/api/nova..virt.hyperv.rst
1128-source/api/nova..virt.images.rst
1129-source/api/nova..virt.libvirt_conn.rst
1130-source/api/nova..virt.xenapi.fake.rst
1131-source/api/nova..virt.xenapi.network_utils.rst
1132-source/api/nova..virt.xenapi.vm_utils.rst
1133-source/api/nova..virt.xenapi.vmops.rst
1134-source/api/nova..virt.xenapi.volume_utils.rst
1135-source/api/nova..virt.xenapi.volumeops.rst
1136-source/api/nova..virt.xenapi_conn.rst
1137-source/api/nova..volume.api.rst
1138-source/api/nova..volume.driver.rst
1139-source/api/nova..volume.manager.rst
1140-source/api/nova..volume.san.rst
1141-source/api/nova..wsgi.rst
1142-source/api/autoindex.rst
1143-source/api/nova..adminclient.rst
1144-source/api/nova..api.direct.rst
1145-source/api/nova..api.ec2.admin.rst
1146-source/api/nova..api.ec2.apirequest.rst
1147-source/api/nova..api.ec2.cloud.rst
1148-source/api/nova..api.ec2.metadatarequesthandler.rst
1149-source/api/nova..api.openstack.auth.rst
1150-source/api/nova..api.openstack.backup_schedules.rst
1151-source/api/nova..api.openstack.common.rst
1152-source/api/nova..api.openstack.consoles.rst
1153-source/api/nova..api.openstack.faults.rst
1154-source/api/nova..api.openstack.flavors.rst
1155-source/api/nova..api.openstack.images.rst
1156-source/api/nova..api.openstack.servers.rst
1157-source/api/nova..api.openstack.shared_ip_groups.rst
1158-source/api/nova..api.openstack.zones.rst
1159-source/api/nova..auth.dbdriver.rst
1160-source/api/nova..auth.fakeldap.rst
1161-source/api/nova..auth.ldapdriver.rst
1162-source/api/nova..auth.manager.rst
1163-source/api/nova..auth.signer.rst
1164-source/api/nova..cloudpipe.pipelib.rst
1165-source/api/nova..compute.api.rst
1166-source/api/nova..compute.instance_types.rst
1167-source/api/nova..compute.manager.rst
1168-source/api/nova..compute.monitor.rst
1169-source/api/nova..compute.power_state.rst
1170-source/api/nova..console.api.rst
1171-source/api/nova..console.fake.rst
1172-source/api/nova..console.manager.rst
1173-source/api/nova..console.xvp.rst
1174-source/api/nova..context.rst
1175-source/api/nova..crypto.rst
1176-source/api/nova..db.api.rst
1177-source/api/nova..db.base.rst
1178-source/api/nova..db.migration.rst
1179-source/api/nova..db.sqlalchemy.api.rst
1180-source/api/nova..db.sqlalchemy.migrate_repo.manage.rst
1181-source/api/nova..db.sqlalchemy.migrate_repo.versions.001_austin.rst
1182-source/api/nova..db.sqlalchemy.migrate_repo.versions.002_bexar.rst
1183-source/api/nova..db.sqlalchemy.migrate_repo.versions.003_add_label_to_networks.rst
1184-source/api/nova..db.sqlalchemy.migrate_repo.versions.004_add_zone_tables.rst
1185-source/api/nova..db.sqlalchemy.migrate_repo.versions.005_add_instance_metadata.rst
1186-source/api/nova..db.sqlalchemy.migrate_repo.versions.006_add_provider_data_to_volumes.rst
1187-source/api/nova..db.sqlalchemy.migrate_repo.versions.007_add_instance_types.rst
1188-source/api/nova..db.sqlalchemy.migration.rst
1189-source/api/nova..db.sqlalchemy.models.rst
1190-source/api/nova..db.sqlalchemy.session.rst
1191-source/api/nova..exception.rst
1192-source/api/nova..fakememcache.rst
1193-source/api/nova..fakerabbit.rst
1194-source/api/nova..flags.rst
1195-source/api/nova..image.glance.rst
1196-source/api/nova..image.local.rst
1197-source/api/nova..image.s3.rst
1198-source/api/nova..image.service.rst
1199-source/api/nova..log.rst
1200-source/api/nova..manager.rst
1201-source/api/nova..network.api.rst
1202-source/api/nova..network.linux_net.rst
1203-source/api/nova..network.manager.rst
1204-source/api/nova..objectstore.bucket.rst
1205-source/api/nova..objectstore.handler.rst
1206-source/api/nova..objectstore.image.rst
1207-source/api/nova..objectstore.stored.rst
1208-source/api/nova..quota.rst
1209-source/api/nova..rpc.rst
1210-source/api/nova..scheduler.chance.rst
1211-source/api/nova..scheduler.driver.rst
1212-source/api/nova..scheduler.manager.rst
1213-source/api/nova..scheduler.simple.rst
1214-source/api/nova..scheduler.zone.rst
1215-source/api/nova..service.rst
1216-source/api/nova..test.rst
1217-source/api/nova..tests.api.openstack.fakes.rst
1218-source/api/nova..tests.api.openstack.test_adminapi.rst
1219-source/api/nova..tests.api.openstack.test_api.rst
1220-source/api/nova..tests.api.openstack.test_auth.rst
1221-source/api/nova..tests.api.openstack.test_common.rst
1222-source/api/nova..tests.api.openstack.test_faults.rst
1223-source/api/nova..tests.api.openstack.test_flavors.rst
1224-source/api/nova..tests.api.openstack.test_images.rst
1225-source/api/nova..tests.api.openstack.test_ratelimiting.rst
1226-source/api/nova..tests.api.openstack.test_servers.rst
1227-source/api/nova..tests.api.openstack.test_shared_ip_groups.rst
1228-source/api/nova..tests.api.openstack.test_zones.rst
1229-source/api/nova..tests.api.test_wsgi.rst
1230-source/api/nova..tests.db.fakes.rst
1231-source/api/nova..tests.declare_flags.rst
1232-source/api/nova..tests.fake_flags.rst
1233-source/api/nova..tests.glance.stubs.rst
1234-source/api/nova..tests.hyperv_unittest.rst
1235-source/api/nova..tests.objectstore_unittest.rst
1236-source/api/nova..tests.real_flags.rst
1237-source/api/nova..tests.runtime_flags.rst
1238-source/api/nova..tests.test_access.rst
1239-source/api/nova..tests.test_api.rst
1240-source/api/nova..tests.test_auth.rst
1241-source/api/nova..tests.test_cloud.rst
1242-source/api/nova..tests.test_compute.rst
1243-source/api/nova..tests.test_console.rst
1244-source/api/nova..tests.test_direct.rst
1245-source/api/nova..tests.test_flags.rst
1246-source/api/nova..tests.test_instance_types.rst
1247-source/api/nova..tests.test_localization.rst
1248-source/api/nova..tests.test_log.rst
1249-source/api/nova..tests.test_middleware.rst
1250-source/api/nova..tests.test_misc.rst
1251-source/api/nova..tests.test_network.rst
1252-source/api/nova..tests.test_quota.rst
1253-source/api/nova..tests.test_rpc.rst
1254-source/api/nova..tests.test_scheduler.rst
1255-source/api/nova..tests.test_service.rst
1256-source/api/nova..tests.test_test.rst
1257-source/api/nova..tests.test_twistd.rst
1258-source/api/nova..tests.test_utils.rst
1259-source/api/nova..tests.test_virt.rst
1260-source/api/nova..tests.test_volume.rst
1261-source/api/nova..tests.test_xenapi.rst
1262-source/api/nova..tests.xenapi.stubs.rst
1263-source/api/nova..twistd.rst
1264-source/api/nova..utils.rst
1265-source/api/nova..version.rst
1266-source/api/nova..virt.connection.rst
1267-source/api/nova..virt.disk.rst
1268-source/api/nova..virt.fake.rst
1269-source/api/nova..virt.hyperv.rst
1270-source/api/nova..virt.images.rst
1271-source/api/nova..virt.libvirt_conn.rst
1272-source/api/nova..virt.xenapi.fake.rst
1273-source/api/nova..virt.xenapi.network_utils.rst
1274-source/api/nova..virt.xenapi.vm_utils.rst
1275-source/api/nova..virt.xenapi.vmops.rst
1276-source/api/nova..virt.xenapi.volume_utils.rst
1277-source/api/nova..virt.xenapi.volumeops.rst
1278-source/api/nova..virt.xenapi_conn.rst
1279-source/api/nova..volume.api.rst
1280-source/api/nova..volume.driver.rst
1281-source/api/nova..volume.manager.rst
1282-source/api/nova..volume.san.rst
1283-source/api/nova..wsgi.rst
1284
1285=== removed directory 'doc/build/html'
1286=== removed file 'doc/build/html/.buildinfo'
1287--- doc/build/html/.buildinfo 2011-02-21 20:30:20 +0000
1288+++ doc/build/html/.buildinfo 1970-01-01 00:00:00 +0000
1289@@ -1,4 +0,0 @@
1290-# Sphinx build info version 1
1291-# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
1292-config: 2a2fe6198f4be4a4d6f289b09d16d74a
1293-tags: fbb0d17656682115ca4d033fb2f83ba1
1294
1295=== added file 'doc/source/devref/distributed_scheduler.rst'
1296--- doc/source/devref/distributed_scheduler.rst 1970-01-01 00:00:00 +0000
1297+++ doc/source/devref/distributed_scheduler.rst 2011-07-14 20:24:25 +0000
1298@@ -0,0 +1,188 @@
1299+..
1300+ Copyright 2011 OpenStack LLC
1301+ All Rights Reserved.
1302+
1303+ Licensed under the Apache License, Version 2.0 (the "License"); you may
1304+ not use this file except in compliance with the License. You may obtain
1305+ a copy of the License at
1306+
1307+ http://www.apache.org/licenses/LICENSE-2.0
1308+
1309+ Unless required by applicable law or agreed to in writing, software
1310+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1311+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1312+ License for the specific language governing permissions and limitations
1313+ under the License.
1314+
1315+ Source for illustrations in doc/source/image_src/zone_distsched_illustrations.odp
1316+ (OpenOffice Impress format) Illustrations are "exported" to png and then scaled
1317+ to 400x300 or 640x480 as needed and placed in the doc/source/images directory.
1318+
1319+Distributed Scheduler
1320+=====================
1321+
1322+The Scheduler is akin to a Dating Service. Requests for the creation of new instances come in and the most applicable Compute nodes are selected from a large pool of potential candidates. In a small deployment we may be happy with the currently available Chance Scheduler which randomly selects a Host from the available pool. Or if you need something a little more fancy you may want to use the Availability Zone Scheduler, which selects Compute hosts from a logical partitioning of available hosts (within a single Zone).
1323+
1324+ .. image:: /images/dating_service.png
1325+
1326+But for larger deployments a more complex scheduling algorithm is required. Additionally, if you are using Zones in your Nova setup, you'll need a scheduler that understand how to pass instance requests from Zone to Zone.
1327+
1328+This is the purpose of the Distributed Scheduler (DS). The DS utilizes the Capabilities of a Zone and its component services to make informed decisions on where a new instance should be created. When making this decision it consults not only all the Compute nodes in the current Zone, but the Compute nodes in each Child Zone. This continues recursively until the ideal host is found.
1329+
1330+So, how does this all work?
1331+
1332+This document will explain the strategy employed by the `ZoneAwareScheduler` and its derivations. You should read the :doc:`devguide/zones` documentation before reading this.
1333+
1334+ .. image:: /images/zone_aware_scheduler.png
1335+
1336+Costs & Weights
1337+---------------
1338+When deciding where to place an Instance, we compare a Weighted Cost for each Host. The Weighting, currently, is just the sum of each Cost. Costs are nothing more than integers from `0 - max_int`. Costs are computed by looking at the various Capabilities of the Host relative to the specs of the Instance being asked for. Trying to put a plain vanilla instance on a high performance host should have a very high cost. But putting a vanilla instance on a vanilla Host should have a low cost.
1339+
1340+Some Costs are more esoteric. Consider a rule that says we should prefer Hosts that don't already have an instance on it that is owned by the user requesting it (to mitigate against machine failures). Here we have to look at all the other Instances on the host to compute our cost.
1341+
1342+An example of some other costs might include selecting:
1343+ * a GPU-based host over a standard CPU
1344+ * a host with fast ethernet over a 10mbps line
1345+ * a host that can run Windows instances
1346+ * a host in the EU vs North America
1347+ * etc
1348+
1349+This Weight is computed for each Instance requested. If the customer asked for 1000 instances, the consumed resources on each Host are "virtually" depleted so the Cost can change accordingly.
1350+
1351+ .. image:: /images/costs_weights.png
1352+
1353+nova.scheduler.zone_aware_scheduler.ZoneAwareScheduler
1354+------------------------------------------------------
1355+As we explained in the Zones documentation, each Scheduler has a `ZoneManager` object that collects "Capabilities" about child Zones and each of the services running in the current Zone. The `ZoneAwareScheduler` uses this information to make its decisions.
1356+
1357+Here is how it works:
1358+
1359+ 1. The compute nodes are filtered and the nodes remaining are weighed.
1360+ 2. Filtering the hosts is a simple matter of ensuring the compute node has ample resources (CPU, RAM, Disk, etc) to fulfil the request.
1361+ 3. Weighing of the remaining compute nodes assigns a number based on their suitability for the request.
1362+ 4. The same request is sent to each child Zone and step #1 is done there too. The resulting weighted list is returned to the parent.
1363+ 5. The parent Zone sorts and aggregates all the weights and a final build plan is constructed.
1364+ 6. The build plan is executed upon. Concurrently, instance create requests are sent to each of the selected hosts, be they local or in a child zone. Child Zones may forward the requests to their child Zones as needed.
1365+
1366+ .. image:: /images/zone_aware_overview.png
1367+
1368+`ZoneAwareScheduler` by itself is not capable of handling all the provisioning itself. Derived classes are used to select which host filtering and weighing strategy will be used.
1369+
1370+Filtering and Weighing
1371+----------------------
1372+The filtering (excluding compute nodes incapable of fulfilling the request) and weighing (computing the relative "fitness" of a compute node to fulfill the request) rules used are very subjective operations ... Service Providers will probably have a very different set of filtering and weighing rules than private cloud administrators. The filtering and weighing aspects of the `ZoneAwareScheduler` are flexible and extensible.
1373+
1374+ .. image:: /images/filtering.png
1375+
1376+Requesting a new instance
1377+-------------------------
1378+Prior to the `ZoneAwareScheduler`, to request a new instance, a call was made to `nova.compute.api.create()`. The type of instance created depended on the value of the `InstanceType` record being passed in. The `InstanceType` determined the amount of disk, CPU, RAM and network required for the instance. Administrators can add new `InstanceType` records to suit their needs. For more complicated instance requests we need to go beyond the default fields in the `InstanceType` table.
1379+
1380+`nova.compute.api.create()` performed the following actions:
1381+ 1. it validated all the fields passed into it.
1382+ 2. it created an entry in the `Instance` table for each instance requested
1383+ 3. it put one `run_instance` message in the scheduler queue for each instance requested
1384+ 4. the schedulers picked off the messages and decided which compute node should handle the request.
1385+ 5. the `run_instance` message was forwarded to the compute node for processing and the instance is created.
1386+ 6. it returned a list of dicts representing each of the `Instance` records (even if the instance has not been activated yet). At least the `instance_ids` are valid.
1387+
1388+ .. image:: /images/nova.compute.api.create.png
1389+
1390+Generally, the standard schedulers (like `ChanceScheduler` and `AvailabilityZoneScheduler`) only operate in the current Zone. They have no concept of child Zones.
1391+
1392+The problem with this approach is each request is scattered amongst each of the schedulers. If we are asking for 1000 instances, each scheduler gets the requests one-at-a-time. There is no possability of optimizing the requests to take into account all 1000 instances as a group. We call this Single-Shot vs. All-at-Once.
1393+
1394+For the `ZoneAwareScheduler` we need to use the All-at-Once approach. We need to consider all the hosts across all the Zones before deciding where they should reside. In order to handle this we have a new method `nova.compute.api.create_all_at_once()`. This method does things a little differently:
1395+ 1. it validates all the fields passed into it.
1396+ 2. it creates a single `reservation_id` for all of instances created. This is a UUID.
1397+ 3. it creates a single `run_instance` request in the scheduler queue
1398+ 4. a scheduler picks the message off the queue and works on it.
1399+ 5. the scheduler sends off an OS API `POST /zones/select` command to each child Zone. The `BODY` payload of the call contains the `request_spec`.
1400+ 6. the child Zones use the `request_spec` to compute a weighted list for each instance requested. No attempt to actually create an instance is done at this point. We're only estimating the suitability of the Zones.
1401+ 7. if the child Zone has its own child Zones, the `/zones/select` call will be sent down to them as well.
1402+ 8. Finally, when all the estimates have bubbled back to the Zone that initiated the call, all the results are merged, sorted and processed.
1403+ 9. Now the instances can be created. The initiating Zone either forwards the `run_instance` message to the local Compute node to do the work, or it issues a `POST /servers` call to the relevant child Zone. The parameters to the child Zone call are the same as what was passed in by the user.
1404+ 10. The `reservation_id` is passed back to the caller. Later we explain how the user can check on the status of the command with this `reservation_id`.
1405+
1406+ .. image:: /images/nova.compute.api.create_all_at_once.png
1407+
1408+The Catch
1409+---------
1410+This all seems pretty straightforward but, like most things, there's a catch. Zones are expected to operate in complete isolation from each other. Each Zone has its own AMQP service, database and set of Nova services. But, for security reasons Zones should never leak information about the architectural layout internally. That means Zones cannot leak information about hostnames or service IP addresses outside of its world.
1411+
1412+When `POST /zones/select` is called to estimate which compute node to use, time passes until the `POST /servers` call is issued. If we only passed the weight back from the `select` we would have to re-compute the appropriate compute node for the create command ... and we could end up with a different host. Somehow we need to remember the results of our computations and pass them outside of the Zone. Now, we could store this information in the local database and return a reference to it, but remember that the vast majority of weights are going to be ignored. Storing them in the database would result in a flood of disk access and then we have to clean up all these entries periodically. Recall that there are going to be many many `select` calls issued to child Zones asking for estimates.
1413+
1414+Instead, we take a rather innovative approach to the problem. We encrypt all the child zone internal details and pass them back the to parent Zone. If the parent zone decides to use a child Zone for the instance it simply passes the encrypted data back to the child during the `POST /servers` call as an extra parameter. The child Zone can then decrypt the hint and go directly to the Compute node previously selected. If the estimate isn't used, it is simply discarded by the parent. It's for this reason that it is so important that each Zone defines a unique encryption key via `--build_plan_encryption_key`
1415+
1416+In the case of nested child Zones, each Zone re-encrypts the weighted list results and passes those values to the parent.
1417+
1418+Throughout the `nova.api.openstack.servers`, `nova.api.openstack.zones`, `nova.compute.api.create*` and `nova.scheduler.zone_aware_scheduler` code you'll see references to `blob` and `child_blob`. These are the encrypted hints about which Compute node to use.
1419+
1420+Reservation IDs
1421+---------------
1422+
1423+NOTE: The features described in this section are related to the up-coming 'merge-4' branch.
1424+
1425+The OpenStack API allows a user to list all the instances they own via the `GET /servers/` command or the details on a particular instance via `GET /servers/###`. This mechanism is usually sufficient since OS API only allows for creating one instance at a time, unlike the EC2 API which allows you to specify a quantity of instances to be created.
1426+
1427+NOTE: currently the `GET /servers` command is not Zone-aware since all operations done in child Zones are done via a single administrative account. Therefore, asking a child Zone to `GET /servers` would return all the active instances ... and that would not be what the user intended. Later, when the Keystone Auth system is integrated with Nova, this functionality will be enabled.
1428+
1429+We could use the OS API 1.1 Extensions mechanism to accept a `num_instances` parameter, but this would result in a different return code. Instead of getting back an `Instance` record, we would be getting back a `reservation_id`. So, instead, we've implemented a new command `POST /zones/boot` command which is nearly identical to `POST /servers` except that it takes a `num_instances` parameter and returns a `reservation_id`. Perhaps in OS API 2.x we can unify these approaches.
1430+
1431+Finally, we need to give the user a way to get information on each of the instances created under this `reservation_id`. Fortunately, this is still possible with the existing `GET /servers` command, so long as we add a new optional `reservation_id` parameter.
1432+
1433+`python-novaclient` will be extended to support both of these changes.
1434+
1435+Host Filter
1436+-----------
1437+
1438+As we mentioned earlier, filtering hosts is a very deployment-specific process. Service Providers may have a different set of criteria for filtering Compute nodes than a University. To faciliate this the `nova.scheduler.host_filter` module supports a variety of filtering strategies as well as an easy means for plugging in your own algorithms.
1439+
1440+The filter used is determined by the `--default_host_filter` flag, which points to a Python Class. By default this flag is set to `nova.scheduler.host_filter.AllHostsFilter` which simply returns all available hosts. But there are others:
1441+
1442+ * `nova.scheduler.host_filter.InstanceTypeFilter` provides host filtering based on the memory and disk size specified in the `InstanceType` record passed into `run_instance`.
1443+
1444+ * `nova.scheduler.host_filter.JSONFilter` filters hosts based on simple JSON expression grammar. Using a LISP-like JSON structure the caller can request instances based on criteria well beyond what `InstanceType` specifies. See `nova.tests.test_host_filter` for examples.
1445+
1446+To create your own `HostFilter` the user simply has to derive from `nova.scheduler.host_filter.HostFilter` and implement two methods: `instance_type_to_filter` and `filter_hosts`. Since Nova is currently dependent on the `InstanceType` structure, the `instance_type_to_filter` method should take an `InstanceType` and turn it into an internal data structure usable by your filter. This is for backward compatibility with existing OpenStack and EC2 API calls. If you decide to create your own call for creating instances not based on `Flavors` or `InstanceTypes` you can ignore this method. The real work is done in `filter_hosts` which must return a list of host tuples for each appropriate host. The set of all available hosts is in the `ZoneManager` object passed into the call as well as the filter query. The host tuple contains (`<hostname>`, `<additional data>`) where `<additional data>` is whatever you want it to be.
1447+
1448+Cost Scheduler Weighing
1449+-----------------------
1450+Every `ZoneAwareScheduler` derivation must also override the `weigh_hosts` method. This takes the list of filtered hosts (generated by the `filter_hosts` method) and returns a list of weight dicts. The weight dicts must contain two keys: `weight` and `hostname` where `weight` is simply an integer (lower is better) and `hostname` is the name of the host. The list does not need to be sorted, this will be done by the `ZoneAwareScheduler` base class when all the results have been assembled.
1451+
1452+Simple Zone Aware Scheduling
1453+----------------------------
1454+The easiest way to get started with the `ZoneAwareScheduler` is to use the `nova.scheduler.host_filter.HostFilterScheduler`. This scheduler uses the default Host Filter and the `weight_hosts` method simply returns a weight of 1 for all hosts. But, from this, you can see calls being routed from Zone to Zone and follow the flow of things.
1455+
1456+The `--scheduler_driver` flag is how you specify the scheduler class name.
1457+
1458+Flags
1459+-----
1460+
1461+All this Zone and Distributed Scheduler stuff can seem a little daunting to configure, but it's actually not too bad. Here are some of the main flags you should set in your `nova.conf` file:
1462+
1463+::
1464+
1465+ --allow_admin_api=true
1466+ --enable_zone_routing=true
1467+ --zone_name=zone1
1468+ --build_plan_encryption_key=c286696d887c9aa0611bbb3e2025a45b
1469+ --scheduler_driver=nova.scheduler.host_filter.HostFilterScheduler
1470+ --default_host_filter=nova.scheduler.host_filter.AllHostsFilter
1471+
1472+`--allow_admin_api` must be set for OS API to enable the new `/zones/*` commands.
1473+`--enable_zone_routing` must be set for OS API commands such as `create()`, `pause()` and `delete()` to get routed from Zone to Zone when looking for instances.
1474+`--zone_name` is only required in child Zones. The default Zone name is `nova`, but you may want to name your child Zones something useful. Duplicate Zone names are not an issue.
1475+`build_plan_encryption_key` is the SHA-256 key for encrypting/decrypting the Host information when it leaves a Zone. Be sure to change this key for each Zone you create. Do not duplicate keys.
1476+`scheduler_driver` is the real workhorse of the operation. For Distributed Scheduler, you need to specify a class derived from `nova.scheduler.zone_aware_scheduler.ZoneAwareScheduler`.
1477+`default_host_filter` is the host filter to be used for filtering candidate Compute nodes.
1478+
1479+Some optional flags which are handy for debugging are:
1480+
1481+::
1482+
1483+ --connection_type=fake
1484+ --verbose
1485+
1486+Using the `Fake` virtualization driver is handy when you're setting this stuff up so you're not dealing with a million possible issues at once. When things seem to working correctly, switch back to whatever hypervisor your deployment uses.
1487
1488=== modified file 'doc/source/devref/index.rst'
1489--- doc/source/devref/index.rst 2011-01-04 22:58:08 +0000
1490+++ doc/source/devref/index.rst 2011-07-14 20:24:25 +0000
1491@@ -35,6 +35,7 @@
1492 .. toctree::
1493 :maxdepth: 3
1494
1495+ zone
1496 rabbit
1497
1498 API Reference
1499
1500=== added file 'doc/source/devref/multinic.rst'
1501--- doc/source/devref/multinic.rst 1970-01-01 00:00:00 +0000
1502+++ doc/source/devref/multinic.rst 2011-07-14 20:24:25 +0000
1503@@ -0,0 +1,39 @@
1504+MultiNic
1505+========
1506+
1507+What is it
1508+----------
1509+
1510+Multinic allows an instance to have more than one vif connected to it. Each vif is representative of a separate network with its own IP block.
1511+
1512+Managers
1513+--------
1514+
1515+Each of the network managers are designed to run independently of the compute manager. They expose a common API for the compute manager to call to determine and configure the network(s) for an instance. Direct calls to either the network api or especially the DB should be avoided by the virt layers.
1516+
1517+On startup a manager looks in the networks table for networks it is assigned and configures itself to support that network. Using the periodic task, they will claim new networks that have no host set. Only one network per network-host will be claimed at a time. This allows for psuedo-loadbalancing if there are multiple network-hosts running.
1518+
1519+Flat Manager
1520+------------
1521+
1522+ .. image:: /images/multinic_flat.png
1523+
1524+The Flat manager is most similar to a traditional switched network environment. It assumes that the IP routing, DNS, DHCP (possibly) and bridge creation is handled by something else. That is it makes no attempt to configure any of this. It does keep track of a range of IPs for the instances that are connected to the network to be allocated.
1525+
1526+Each instance will get a fixed IP from each network's pool. The guest operating system may be configured to gather this information through an agent or by the hypervisor injecting the files, or it may ignore it completely and come up with only a layer 2 connection.
1527+
1528+Flat manager requires at least one nova-network process running that will listen to the API queue and respond to queries. It does not need to sit on any of the networks but it does keep track of the IPs it hands out to instances.
1529+
1530+FlatDHCP Manager
1531+----------------
1532+
1533+ .. image:: /images/multinic_dhcp.png
1534+
1535+FlatDHCP manager builds on the the Flat manager adding dnsmask (DNS and DHCP) and radvd (Router Advertisement) servers on the bridge for that network. The services run on the host that is assigned to that nework. The FlatDHCP manager will create its bridge as specified when the network was created on the network-host when the network host starts up or when a new network gets allocated to that host. Compute nodes will also create the bridges as necessary and connect instance VIFs to them.
1536+
1537+VLAN Manager
1538+------------
1539+
1540+ .. image:: /images/multinic_vlan.png
1541+
1542+The VLAN manager sets up forwarding to/from a cloudpipe instance in addition to providing dnsmask (DNS and DHCP) and radvd (Router Advertisement) services for each network. The manager will create its bridge as specified when the network was created on the network-host when the network host starts up or when a new network gets allocated to that host. Compute nodes will also create the bridges as necessary and conenct instance VIFs to them.
1543
1544=== modified file 'doc/source/devref/zone.rst'
1545--- doc/source/devref/zone.rst 2011-04-08 18:45:42 +0000
1546+++ doc/source/devref/zone.rst 2011-07-14 20:24:25 +0000
1547@@ -17,11 +17,11 @@
1548 Zones
1549 =====
1550
1551-A Nova deployment is called a Zone. At the very least a Zone requires an API node, a Scheduler node, a database and RabbitMQ. Pushed further a Zone may contain many API nodes, many Scheduler, Volume, Network and Compute nodes as well as a cluster of databases and RabbitMQ servers. A Zone allows you to partition your deployments into logical groups for load balancing and instance distribution.
1552+A Nova deployment is called a Zone. A Zone allows you to partition your deployments into logical groups for load balancing and instance distribution. At the very least a Zone requires an API node, a Scheduler node, a database and RabbitMQ. Pushed further a Zone may contain many API nodes, many Scheduler, Volume, Network and Compute nodes as well as a cluster of databases and RabbitMQ servers.
1553
1554 The idea behind Zones is, if a particular deployment is not capable of servicing a particular request, the request may be forwarded to (child) Zones for possible processing. Zones may be nested in a tree fashion.
1555
1556-Zones only know about their immediate children, they do not know about their parent Zones and may in fact have more than one parent. Likewise, a Zone's children may themselves have child Zones.
1557+Zones only know about their immediate children, they do not know about their parent Zones and may in fact have more than one parent. Likewise, a Zone's children may themselves have child Zones and, in those cases, the grandchild's internal structure would not be known to the grand-parent.
1558
1559 Zones share nothing. They communicate via the public OpenStack API only. No database, queue, user or project definition is shared between Zones.
1560
1561@@ -34,7 +34,7 @@
1562
1563 key=value;value;value, key=value;value;value
1564
1565-Zones have Capabilities which are general to the Zone and are set via `--zone-capabilities` flag. Zones also have dynamic per-service Capabilities. Services derived from `nova.manager.SchedulerDependentManager` (such as Compute, Volume and Network) can set these capabilities by calling the `update_service_capabilities()` method on their `Manager` base class. These capabilities will be periodically sent to the Scheduler service automatically. The rate at which these updates are sent is controlled by the `--periodic_interval` flag.
1566+Zones have Capabilities which are general to the Zone and are set via `--zone_capabilities` flag. Zones also have dynamic per-service Capabilities. Services derived from `nova.manager.SchedulerDependentManager` (such as Compute, Volume and Network) can set these capabilities by calling the `update_service_capabilities()` method on their `Manager` base class. These capabilities will be periodically sent to the Scheduler service automatically. The rate at which these updates are sent is controlled by the `--periodic_interval` flag.
1567
1568 Flow within a Zone
1569 ------------------
1570@@ -47,7 +47,7 @@
1571
1572 These capability messages are received by the Scheduler services and stored in the `ZoneManager` object. The SchedulerManager object has a reference to the `ZoneManager` it can use for load balancing.
1573
1574-The `ZoneManager` also polls the child Zones periodically to gather their capabilities to aid in decision making. This is done via the OpenStack API `/v1.0/zones/info` REST call. This also captures the name of each child Zone. The Zone name is set via the `--zone-name` flag (and defaults to "nova").
1575+The `ZoneManager` also polls the child Zones periodically to gather their capabilities to aid in decision making. This is done via the OpenStack API `/v1.0/zones/info` REST call. This also captures the name of each child Zone. The Zone name is set via the `--zone_name` flag (and defaults to "nova").
1576
1577 Zone administrative functions
1578 -----------------------------
1579@@ -99,7 +99,7 @@
1580 export NOVA_URL="http://192.168.2.120:8774/v1.0/"
1581
1582
1583-This equates to a POST operation to `.../zones/` to add a new zone. No connection attempt to the child zone is done when this command. It only puts an entry in the db at this point. After about 30 seconds the `ZoneManager` in the Scheduler services will attempt to talk to the child zone and get its information.
1584+This equates to a POST operation to `.../zones/` to add a new zone. No connection attempt to the child zone is done with this command. It only puts an entry in the db at this point. After about 30 seconds the `ZoneManager` in the Scheduler services will attempt to talk to the child zone and get its information.
1585
1586 Getting a list of child Zones
1587 -----------------------------
1588
1589=== added directory 'doc/source/image_src'
1590=== added file 'doc/source/image_src/multinic_1.odg'
1591Binary files doc/source/image_src/multinic_1.odg 1970-01-01 00:00:00 +0000 and doc/source/image_src/multinic_1.odg 2011-07-14 20:24:25 +0000 differ
1592=== added file 'doc/source/image_src/multinic_2.odg'
1593Binary files doc/source/image_src/multinic_2.odg 1970-01-01 00:00:00 +0000 and doc/source/image_src/multinic_2.odg 2011-07-14 20:24:25 +0000 differ
1594=== added file 'doc/source/image_src/multinic_3.odg'
1595Binary files doc/source/image_src/multinic_3.odg 1970-01-01 00:00:00 +0000 and doc/source/image_src/multinic_3.odg 2011-07-14 20:24:25 +0000 differ
1596=== added file 'doc/source/image_src/zones_distsched_illustrations.odp'
1597Binary files doc/source/image_src/zones_distsched_illustrations.odp 1970-01-01 00:00:00 +0000 and doc/source/image_src/zones_distsched_illustrations.odp 2011-07-14 20:24:25 +0000 differ
1598=== added file 'doc/source/images/costs_weights.png'
1599Binary files doc/source/images/costs_weights.png 1970-01-01 00:00:00 +0000 and doc/source/images/costs_weights.png 2011-07-14 20:24:25 +0000 differ
1600=== added file 'doc/source/images/dating_service.png'
1601Binary files doc/source/images/dating_service.png 1970-01-01 00:00:00 +0000 and doc/source/images/dating_service.png 2011-07-14 20:24:25 +0000 differ
1602=== added file 'doc/source/images/filtering.png'
1603Binary files doc/source/images/filtering.png 1970-01-01 00:00:00 +0000 and doc/source/images/filtering.png 2011-07-14 20:24:25 +0000 differ
1604=== added file 'doc/source/images/multinic_dhcp.png'
1605Binary files doc/source/images/multinic_dhcp.png 1970-01-01 00:00:00 +0000 and doc/source/images/multinic_dhcp.png 2011-07-14 20:24:25 +0000 differ
1606=== added file 'doc/source/images/multinic_flat.png'
1607Binary files doc/source/images/multinic_flat.png 1970-01-01 00:00:00 +0000 and doc/source/images/multinic_flat.png 2011-07-14 20:24:25 +0000 differ
1608=== added file 'doc/source/images/multinic_vlan.png'
1609Binary files doc/source/images/multinic_vlan.png 1970-01-01 00:00:00 +0000 and doc/source/images/multinic_vlan.png 2011-07-14 20:24:25 +0000 differ
1610=== added file 'doc/source/images/nova.compute.api.create.png'
1611Binary files doc/source/images/nova.compute.api.create.png 1970-01-01 00:00:00 +0000 and doc/source/images/nova.compute.api.create.png 2011-07-14 20:24:25 +0000 differ
1612=== added file 'doc/source/images/nova.compute.api.create_all_at_once.png'
1613Binary files doc/source/images/nova.compute.api.create_all_at_once.png 1970-01-01 00:00:00 +0000 and doc/source/images/nova.compute.api.create_all_at_once.png 2011-07-14 20:24:25 +0000 differ
1614=== added file 'doc/source/images/zone_aware_overview.png'
1615Binary files doc/source/images/zone_aware_overview.png 1970-01-01 00:00:00 +0000 and doc/source/images/zone_aware_overview.png 2011-07-14 20:24:25 +0000 differ
1616=== added file 'doc/source/images/zone_aware_scheduler.png'
1617Binary files doc/source/images/zone_aware_scheduler.png 1970-01-01 00:00:00 +0000 and doc/source/images/zone_aware_scheduler.png 2011-07-14 20:24:25 +0000 differ
1618=== modified file 'doc/source/man/novamanage.rst'
1619--- doc/source/man/novamanage.rst 2011-04-07 18:25:44 +0000
1620+++ doc/source/man/novamanage.rst 2011-07-14 20:24:25 +0000
1621@@ -6,7 +6,7 @@
1622 control and manage cloud computer instances and images
1623 ------------------------------------------------------
1624
1625-:Author: nova@lists.launchpad.net
1626+:Author: openstack@lists.launchpad.net
1627 :Date: 2010-11-16
1628 :Copyright: OpenStack LLC
1629 :Version: 0.1
1630@@ -121,7 +121,7 @@
1631 nova-manage role <action> [<argument>]
1632 ``nova-manage role add <username> <rolename> <(optional) projectname>``
1633
1634- Add a user to either a global or project-based role with the indicated <rolename> assigned to the named user. Role names can be one of the following five roles: admin, itsec, projectmanager, netadmin, developer. If you add the project name as the last argument then the role is assigned just for that project, otherwise the user is assigned the named role for all projects.
1635+ Add a user to either a global or project-based role with the indicated <rolename> assigned to the named user. Role names can be one of the following five roles: cloudadmin, itsec, sysadmin, netadmin, developer. If you add the project name as the last argument then the role is assigned just for that project, otherwise the user is assigned the named role for all projects.
1636
1637 ``nova-manage role has <username> <projectname>``
1638 Checks the user or project and responds with True if the user has a global role with a particular project.
1639
1640=== modified file 'doc/source/runnova/managing.users.rst'
1641--- doc/source/runnova/managing.users.rst 2011-02-21 20:30:20 +0000
1642+++ doc/source/runnova/managing.users.rst 2011-07-14 20:24:25 +0000
1643@@ -38,11 +38,11 @@
1644
1645 Nova’s rights management system employs the RBAC model and currently supports the following five roles:
1646
1647-* **Cloud Administrator.** (admin) Users of this class enjoy complete system access.
1648+* **Cloud Administrator.** (cloudadmin) Users of this class enjoy complete system access.
1649 * **IT Security.** (itsec) This role is limited to IT security personnel. It permits role holders to quarantine instances.
1650-* **Project Manager.** (projectmanager)The default for project owners, this role affords users the ability to add other users to a project, interact with project images, and launch and terminate instances.
1651+* **System Administrator.** (sysadmin) The default for project owners, this role affords users the ability to add other users to a project, interact with project images, and launch and terminate instances.
1652 * **Network Administrator.** (netadmin) Users with this role are permitted to allocate and assign publicly accessible IP addresses as well as create and modify firewall rules.
1653-* **Developer.** This is a general purpose role that is assigned to users by default.
1654+* **Developer.** (developer) This is a general purpose role that is assigned to users by default.
1655
1656 RBAC management is exposed through the dashboard for simplified user management.
1657
1658
1659=== modified file 'nova/__init__.py'
1660--- nova/__init__.py 2011-01-20 08:14:42 +0000
1661+++ nova/__init__.py 2011-07-14 20:24:25 +0000
1662@@ -30,3 +30,8 @@
1663 .. moduleauthor:: Manish Singh <yosh@gimp.org>
1664 .. moduleauthor:: Andy Smith <andy@anarkystic.com>
1665 """
1666+
1667+import gettext
1668+
1669+
1670+gettext.install("nova", unicode=1)
1671
1672=== modified file 'nova/api/direct.py'
1673--- nova/api/direct.py 2011-04-11 16:34:19 +0000
1674+++ nova/api/direct.py 2011-07-14 20:24:25 +0000
1675@@ -42,6 +42,7 @@
1676 from nova import flags
1677 from nova import utils
1678 from nova import wsgi
1679+import nova.api.openstack.wsgi
1680
1681
1682 # Global storage for registering modules.
1683@@ -251,7 +252,7 @@
1684 return self._methods[method]
1685
1686
1687-class ServiceWrapper(wsgi.Controller):
1688+class ServiceWrapper(object):
1689 """Wrapper to dynamically povide a WSGI controller for arbitrary objects.
1690
1691 With lightweight introspection allows public methods on the object to
1692@@ -265,7 +266,7 @@
1693 def __init__(self, service_handle):
1694 self.service_handle = service_handle
1695
1696- @webob.dec.wsgify(RequestClass=wsgi.Request)
1697+ @webob.dec.wsgify(RequestClass=nova.api.openstack.wsgi.Request)
1698 def __call__(self, req):
1699 arg_dict = req.environ['wsgiorg.routing_args'][1]
1700 action = arg_dict['action']
1701@@ -289,8 +290,11 @@
1702
1703 try:
1704 content_type = req.best_match_content_type()
1705- default_xmlns = self.get_default_xmlns(req)
1706- return self._serialize(result, content_type, default_xmlns)
1707+ serializer = {
1708+ 'application/xml': nova.api.openstack.wsgi.XMLDictSerializer(),
1709+ 'application/json': nova.api.openstack.wsgi.JSONDictSerializer(),
1710+ }[content_type]
1711+ return serializer.serialize(result)
1712 except:
1713 raise exception.Error("returned non-serializable type: %s"
1714 % result)
1715@@ -320,7 +324,7 @@
1716
1717 def __init__(self, proxy):
1718 self._proxy = proxy
1719- if not self.__doc__:
1720+ if not self.__doc__: # pylint: disable=E0203
1721 self.__doc__ = proxy.__doc__
1722 if not self._allowed:
1723 self._allowed = []
1724
1725=== modified file 'nova/api/ec2/__init__.py'
1726--- nova/api/ec2/__init__.py 2011-04-29 22:06:18 +0000
1727+++ nova/api/ec2/__init__.py 2011-07-14 20:24:25 +0000
1728@@ -242,6 +242,7 @@
1729 'CreateKeyPair': ['all'],
1730 'DeleteKeyPair': ['all'],
1731 'DescribeSecurityGroups': ['all'],
1732+ 'ImportPublicKey': ['all'],
1733 'AuthorizeSecurityGroupIngress': ['netadmin'],
1734 'RevokeSecurityGroupIngress': ['netadmin'],
1735 'CreateSecurityGroup': ['netadmin'],
1736@@ -327,6 +328,12 @@
1737 ec2_id = ec2utils.id_to_ec2_id(ex.volume_id, 'vol-%08x')
1738 message = _('Volume %s not found') % ec2_id
1739 return self._error(req, context, type(ex).__name__, message)
1740+ except exception.SnapshotNotFound as ex:
1741+ LOG.info(_('SnapshotNotFound raised: %s'), unicode(ex),
1742+ context=context)
1743+ ec2_id = ec2utils.id_to_ec2_id(ex.snapshot_id, 'snap-%08x')
1744+ message = _('Snapshot %s not found') % ec2_id
1745+ return self._error(req, context, type(ex).__name__, message)
1746 except exception.NotFound as ex:
1747 LOG.info(_('NotFound raised: %s'), unicode(ex), context=context)
1748 return self._error(req, context, type(ex).__name__, unicode(ex))
1749@@ -338,6 +345,10 @@
1750 else:
1751 return self._error(req, context, type(ex).__name__,
1752 unicode(ex))
1753+ except exception.KeyPairExists as ex:
1754+ LOG.debug(_('KeyPairExists raised: %s'), unicode(ex),
1755+ context=context)
1756+ return self._error(req, context, type(ex).__name__, unicode(ex))
1757 except Exception as ex:
1758 extra = {'environment': req.environ}
1759 LOG.exception(_('Unexpected error raised: %s'), unicode(ex),
1760
1761=== modified file 'nova/api/ec2/admin.py'
1762--- nova/api/ec2/admin.py 2011-04-19 16:19:52 +0000
1763+++ nova/api/ec2/admin.py 2011-07-14 20:24:25 +0000
1764@@ -22,7 +22,10 @@
1765
1766 import base64
1767 import datetime
1768+import netaddr
1769+import urllib
1770
1771+from nova import compute
1772 from nova import db
1773 from nova import exception
1774 from nova import flags
1775@@ -118,6 +121,9 @@
1776 def __str__(self):
1777 return 'AdminController'
1778
1779+ def __init__(self):
1780+ self.compute_api = compute.API()
1781+
1782 def describe_instance_types(self, context, **_kwargs):
1783 """Returns all active instance types data (vcpus, memory, etc.)"""
1784 return {'instanceTypeSet': [instance_dict(v) for v in
1785@@ -305,7 +311,7 @@
1786 * Volume Count
1787 """
1788 services = db.service_get_all(context, False)
1789- now = datetime.datetime.utcnow()
1790+ now = utils.utcnow()
1791 hosts = []
1792 rv = []
1793 for host in [service['host'] for service in services]:
1794@@ -326,6 +332,60 @@
1795 now))
1796 return {'hosts': rv}
1797
1798- def describe_host(self, _context, name, **_kwargs):
1799- """Returns status info for single node."""
1800- return host_dict(db.host_get(name))
1801+ def _provider_fw_rule_exists(self, context, rule):
1802+ # TODO(todd): we call this repeatedly, can we filter by protocol?
1803+ for old_rule in db.provider_fw_rule_get_all(context):
1804+ if all([rule[k] == old_rule[k] for k in ('cidr', 'from_port',
1805+ 'to_port', 'protocol')]):
1806+ return True
1807+ return False
1808+
1809+ def block_external_addresses(self, context, cidr):
1810+ """Add provider-level firewall rules to block incoming traffic."""
1811+ LOG.audit(_('Blocking traffic to all projects incoming from %s'),
1812+ cidr, context=context)
1813+ cidr = urllib.unquote(cidr).decode()
1814+ # raise if invalid
1815+ netaddr.IPNetwork(cidr)
1816+ rule = {'cidr': cidr}
1817+ tcp_rule = rule.copy()
1818+ tcp_rule.update({'protocol': 'tcp', 'from_port': 1, 'to_port': 65535})
1819+ udp_rule = rule.copy()
1820+ udp_rule.update({'protocol': 'udp', 'from_port': 1, 'to_port': 65535})
1821+ icmp_rule = rule.copy()
1822+ icmp_rule.update({'protocol': 'icmp', 'from_port': -1,
1823+ 'to_port': None})
1824+ rules_added = 0
1825+ if not self._provider_fw_rule_exists(context, tcp_rule):
1826+ db.provider_fw_rule_create(context, tcp_rule)
1827+ rules_added += 1
1828+ if not self._provider_fw_rule_exists(context, udp_rule):
1829+ db.provider_fw_rule_create(context, udp_rule)
1830+ rules_added += 1
1831+ if not self._provider_fw_rule_exists(context, icmp_rule):
1832+ db.provider_fw_rule_create(context, icmp_rule)
1833+ rules_added += 1
1834+ if not rules_added:
1835+ raise exception.ApiError(_('Duplicate rule'))
1836+ self.compute_api.trigger_provider_fw_rules_refresh(context)
1837+ return {'status': 'OK', 'message': 'Added %s rules' % rules_added}
1838+
1839+ def describe_external_address_blocks(self, context):
1840+ blocks = db.provider_fw_rule_get_all(context)
1841+ # NOTE(todd): use a set since we have icmp/udp/tcp rules with same cidr
1842+ blocks = set([b.cidr for b in blocks])
1843+ blocks = [{'cidr': b} for b in blocks]
1844+ return {'externalIpBlockInfo':
1845+ list(sorted(blocks, key=lambda k: k['cidr']))}
1846+
1847+ def remove_external_address_block(self, context, cidr):
1848+ LOG.audit(_('Removing ip block from %s'), cidr, context=context)
1849+ cidr = urllib.unquote(cidr).decode()
1850+ # raise if invalid
1851+ netaddr.IPNetwork(cidr)
1852+ rules = db.provider_fw_rule_get_all_by_cidr(context, cidr)
1853+ for rule in rules:
1854+ db.provider_fw_rule_destroy(context, rule['id'])
1855+ if rules:
1856+ self.compute_api.trigger_provider_fw_rules_refresh(context)
1857+ return {'status': 'OK', 'message': 'Deleted %s rules' % len(rules)}
1858
1859=== modified file 'nova/api/ec2/apirequest.py'
1860--- nova/api/ec2/apirequest.py 2011-04-18 20:53:09 +0000
1861+++ nova/api/ec2/apirequest.py 2011-07-14 20:24:25 +0000
1862@@ -21,22 +21,15 @@
1863 """
1864
1865 import datetime
1866-import re
1867 # TODO(termie): replace minidom with etree
1868 from xml.dom import minidom
1869
1870 from nova import log as logging
1871+from nova.api.ec2 import ec2utils
1872
1873 LOG = logging.getLogger("nova.api.request")
1874
1875
1876-_c2u = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
1877-
1878-
1879-def _camelcase_to_underscore(str):
1880- return _c2u.sub(r'_\1', str).lower().strip('_')
1881-
1882-
1883 def _underscore_to_camelcase(str):
1884 return ''.join([x[:1].upper() + x[1:] for x in str.split('_')])
1885
1886@@ -51,59 +44,6 @@
1887 return datetimeobj.strftime("%Y-%m-%dT%H:%M:%SZ")
1888
1889
1890-def _try_convert(value):
1891- """Return a non-string from a string or unicode, if possible.
1892-
1893- ============= =====================================================
1894- When value is returns
1895- ============= =====================================================
1896- zero-length ''
1897- 'None' None
1898- 'True' True
1899- 'False' False
1900- '0', '-0' 0
1901- 0xN, -0xN int from hex (postitive) (N is any number)
1902- 0bN, -0bN int from binary (positive) (N is any number)
1903- * try conversion to int, float, complex, fallback value
1904-
1905- """
1906- if len(value) == 0:
1907- return ''
1908- if value == 'None':
1909- return None
1910- if value == 'True':
1911- return True
1912- if value == 'False':
1913- return False
1914- valueneg = value[1:] if value[0] == '-' else value
1915- if valueneg == '0':
1916- return 0
1917- if valueneg == '':
1918- return value
1919- if valueneg[0] == '0':
1920- if valueneg[1] in 'xX':
1921- return int(value, 16)
1922- elif valueneg[1] in 'bB':
1923- return int(value, 2)
1924- else:
1925- try:
1926- return int(value, 8)
1927- except ValueError:
1928- pass
1929- try:
1930- return int(value)
1931- except ValueError:
1932- pass
1933- try:
1934- return float(value)
1935- except ValueError:
1936- pass
1937- try:
1938- return complex(value)
1939- except ValueError:
1940- return value
1941-
1942-
1943 class APIRequest(object):
1944 def __init__(self, controller, action, version, args):
1945 self.controller = controller
1946@@ -114,7 +54,7 @@
1947 def invoke(self, context):
1948 try:
1949 method = getattr(self.controller,
1950- _camelcase_to_underscore(self.action))
1951+ ec2utils.camelcase_to_underscore(self.action))
1952 except AttributeError:
1953 controller = self.controller
1954 action = self.action
1955@@ -125,19 +65,7 @@
1956 # and reraise as 400 error.
1957 raise Exception(_error)
1958
1959- args = {}
1960- for key, value in self.args.items():
1961- parts = key.split(".")
1962- key = _camelcase_to_underscore(parts[0])
1963- if isinstance(value, str) or isinstance(value, unicode):
1964- # NOTE(vish): Automatically convert strings back
1965- # into their respective values
1966- value = _try_convert(value)
1967- if len(parts) > 1:
1968- d = args.get(key, {})
1969- d[parts[1]] = value
1970- value = d
1971- args[key] = value
1972+ args = ec2utils.dict_from_dotted_str(self.args.items())
1973
1974 for key in args.keys():
1975 # NOTE(vish): Turn numeric dict keys into lists
1976
1977=== modified file 'nova/api/ec2/cloud.py'
1978--- nova/api/ec2/cloud.py 2011-05-20 06:51:29 +0000
1979+++ nova/api/ec2/cloud.py 2011-07-14 20:24:25 +0000
1980@@ -23,8 +23,7 @@
1981 """
1982
1983 import base64
1984-import datetime
1985-import IPy
1986+import netaddr
1987 import os
1988 import urllib
1989 import tempfile
1990@@ -40,6 +39,7 @@
1991 from nova import ipv6
1992 from nova import log as logging
1993 from nova import network
1994+from nova import rpc
1995 from nova import utils
1996 from nova import volume
1997 from nova.api.ec2 import ec2utils
1998@@ -86,8 +86,7 @@
1999 self.volume_api = volume.API()
2000 self.compute_api = compute.API(
2001 network_api=self.network_api,
2002- volume_api=self.volume_api,
2003- hostname_factory=ec2utils.id_to_ec2_id)
2004+ volume_api=self.volume_api)
2005 self.setup()
2006
2007 def __str__(self):
2008@@ -121,8 +120,8 @@
2009 result = {}
2010 for instance in self.compute_api.get_all(context,
2011 project_id=project_id):
2012- if instance['fixed_ip']:
2013- line = '%s slots=%d' % (instance['fixed_ip']['address'],
2014+ if instance['fixed_ips']:
2015+ line = '%s slots=%d' % (instance['fixed_ips'][0]['address'],
2016 instance['vcpus'])
2017 key = str(instance['key_name'])
2018 if key in result:
2019@@ -137,6 +136,13 @@
2020 return services[0]['availability_zone']
2021 return 'unknown zone'
2022
2023+ def _get_image_state(self, image):
2024+ # NOTE(vish): fallback status if image_state isn't set
2025+ state = image.get('status')
2026+ if state == 'active':
2027+ state = 'available'
2028+ return image['properties'].get('image_state', state)
2029+
2030 def get_metadata(self, address):
2031 ctxt = context.get_admin_context()
2032 instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address)
2033@@ -145,7 +151,7 @@
2034
2035 # This ensures that all attributes of the instance
2036 # are populated.
2037- instance_ref = db.instance_get(ctxt, instance_ref['id'])
2038+ instance_ref = db.instance_get(ctxt, instance_ref[0]['id'])
2039
2040 mpi = self._get_mpi_data(ctxt, instance_ref['project_id'])
2041 if instance_ref['key_name']:
2042@@ -159,7 +165,10 @@
2043 floating_ip = db.instance_get_floating_address(ctxt,
2044 instance_ref['id'])
2045 ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])
2046- image_ec2_id = self.image_ec2_id(instance_ref['image_id'])
2047+ image_ec2_id = self.image_ec2_id(instance_ref['image_ref'])
2048+ security_groups = db.security_group_get_by_instance(ctxt,
2049+ instance_ref['id'])
2050+ security_groups = [x['name'] for x in security_groups]
2051 data = {
2052 'user-data': base64.b64decode(instance_ref['user_data']),
2053 'meta-data': {
2054@@ -183,7 +192,7 @@
2055 'public-ipv4': floating_ip or '',
2056 'public-keys': keys,
2057 'reservation-id': instance_ref['reservation_id'],
2058- 'security-groups': '',
2059+ 'security-groups': security_groups,
2060 'mpi': mpi}}
2061
2062 for image_type in ['kernel', 'ramdisk']:
2063@@ -235,7 +244,7 @@
2064 'zoneState': 'available'}]}
2065
2066 services = db.service_get_all(context, False)
2067- now = datetime.datetime.utcnow()
2068+ now = utils.utcnow()
2069 hosts = []
2070 for host in [service['host'] for service in services]:
2071 if not host in hosts:
2072@@ -283,14 +292,50 @@
2073 owner=None,
2074 restorable_by=None,
2075 **kwargs):
2076- return {'snapshotSet': [{'snapshotId': 'fixme',
2077- 'volumeId': 'fixme',
2078- 'status': 'fixme',
2079- 'startTime': 'fixme',
2080- 'progress': 'fixme',
2081- 'ownerId': 'fixme',
2082- 'volumeSize': 0,
2083- 'description': 'fixme'}]}
2084+ if snapshot_id:
2085+ snapshots = []
2086+ for ec2_id in snapshot_id:
2087+ internal_id = ec2utils.ec2_id_to_id(ec2_id)
2088+ snapshot = self.volume_api.get_snapshot(
2089+ context,
2090+ snapshot_id=internal_id)
2091+ snapshots.append(snapshot)
2092+ else:
2093+ snapshots = self.volume_api.get_all_snapshots(context)
2094+ snapshots = [self._format_snapshot(context, s) for s in snapshots]
2095+ return {'snapshotSet': snapshots}
2096+
2097+ def _format_snapshot(self, context, snapshot):
2098+ s = {}
2099+ s['snapshotId'] = ec2utils.id_to_ec2_id(snapshot['id'], 'snap-%08x')
2100+ s['volumeId'] = ec2utils.id_to_ec2_id(snapshot['volume_id'],
2101+ 'vol-%08x')
2102+ s['status'] = snapshot['status']
2103+ s['startTime'] = snapshot['created_at']
2104+ s['progress'] = snapshot['progress']
2105+ s['ownerId'] = snapshot['project_id']
2106+ s['volumeSize'] = snapshot['volume_size']
2107+ s['description'] = snapshot['display_description']
2108+
2109+ s['display_name'] = snapshot['display_name']
2110+ s['display_description'] = snapshot['display_description']
2111+ return s
2112+
2113+ def create_snapshot(self, context, volume_id, **kwargs):
2114+ LOG.audit(_("Create snapshot of volume %s"), volume_id,
2115+ context=context)
2116+ volume_id = ec2utils.ec2_id_to_id(volume_id)
2117+ snapshot = self.volume_api.create_snapshot(
2118+ context,
2119+ volume_id=volume_id,
2120+ name=kwargs.get('display_name'),
2121+ description=kwargs.get('display_description'))
2122+ return self._format_snapshot(context, snapshot)
2123+
2124+ def delete_snapshot(self, context, snapshot_id, **kwargs):
2125+ snapshot_id = ec2utils.ec2_id_to_id(snapshot_id)
2126+ self.volume_api.delete_snapshot(context, snapshot_id=snapshot_id)
2127+ return True
2128
2129 def describe_key_pairs(self, context, key_name=None, **kwargs):
2130 key_pairs = db.key_pair_get_all_by_user(context, context.user_id)
2131@@ -348,15 +393,21 @@
2132 pass
2133 return True
2134
2135- def describe_security_groups(self, context, group_name=None, **kwargs):
2136+ def describe_security_groups(self, context, group_name=None, group_id=None,
2137+ **kwargs):
2138 self.compute_api.ensure_default_security_group(context)
2139- if group_name:
2140+ if group_name or group_id:
2141 groups = []
2142- for name in group_name:
2143- group = db.security_group_get_by_name(context,
2144- context.project_id,
2145- name)
2146- groups.append(group)
2147+ if group_name:
2148+ for name in group_name:
2149+ group = db.security_group_get_by_name(context,
2150+ context.project_id,
2151+ name)
2152+ groups.append(group)
2153+ if group_id:
2154+ for gid in group_id:
2155+ group = db.security_group_get(context, gid)
2156+ groups.append(group)
2157 elif context.is_admin:
2158 groups = db.security_group_get_all(context)
2159 else:
2160@@ -409,7 +460,7 @@
2161 elif cidr_ip:
2162 # If this fails, it throws an exception. This is what we want.
2163 cidr_ip = urllib.unquote(cidr_ip).decode()
2164- IPy.IP(cidr_ip)
2165+ netaddr.IPNetwork(cidr_ip)
2166 values['cidr'] = cidr_ip
2167 else:
2168 values['cidr'] = '0.0.0.0/0'
2169@@ -454,13 +505,26 @@
2170 return True
2171 return False
2172
2173- def revoke_security_group_ingress(self, context, group_name, **kwargs):
2174- LOG.audit(_("Revoke security group ingress %s"), group_name,
2175- context=context)
2176+ def revoke_security_group_ingress(self, context, group_name=None,
2177+ group_id=None, **kwargs):
2178+ if not group_name and not group_id:
2179+ err = "Not enough parameters, need group_name or group_id"
2180+ raise exception.ApiError(_(err))
2181 self.compute_api.ensure_default_security_group(context)
2182- security_group = db.security_group_get_by_name(context,
2183- context.project_id,
2184- group_name)
2185+ notfound = exception.SecurityGroupNotFound
2186+ if group_name:
2187+ security_group = db.security_group_get_by_name(context,
2188+ context.project_id,
2189+ group_name)
2190+ if not security_group:
2191+ raise notfound(security_group_id=group_name)
2192+ if group_id:
2193+ security_group = db.security_group_get(context, group_id)
2194+ if not security_group:
2195+ raise notfound(security_group_id=group_id)
2196+
2197+ msg = "Revoke security group ingress %s"
2198+ LOG.audit(_(msg), security_group['name'], context=context)
2199
2200 criteria = self._revoke_rule_args_to_dict(context, **kwargs)
2201 if criteria is None:
2202@@ -475,7 +539,7 @@
2203 if match:
2204 db.security_group_rule_destroy(context, rule['id'])
2205 self.compute_api.trigger_security_group_rules_refresh(context,
2206- security_group['id'])
2207+ security_group_id=security_group['id'])
2208 return True
2209 raise exception.ApiError(_("No rule for the specified parameters."))
2210
2211@@ -483,14 +547,26 @@
2212 # Unfortunately, it seems Boto is using an old API
2213 # for these operations, so support for newer API versions
2214 # is sketchy.
2215- def authorize_security_group_ingress(self, context, group_name, **kwargs):
2216- LOG.audit(_("Authorize security group ingress %s"), group_name,
2217- context=context)
2218+ def authorize_security_group_ingress(self, context, group_name=None,
2219+ group_id=None, **kwargs):
2220+ if not group_name and not group_id:
2221+ err = "Not enough parameters, need group_name or group_id"
2222+ raise exception.ApiError(_(err))
2223 self.compute_api.ensure_default_security_group(context)
2224- security_group = db.security_group_get_by_name(context,
2225- context.project_id,
2226- group_name)
2227+ notfound = exception.SecurityGroupNotFound
2228+ if group_name:
2229+ security_group = db.security_group_get_by_name(context,
2230+ context.project_id,
2231+ group_name)
2232+ if not security_group:
2233+ raise notfound(security_group_id=group_name)
2234+ if group_id:
2235+ security_group = db.security_group_get(context, group_id)
2236+ if not security_group:
2237+ raise notfound(security_group_id=group_id)
2238
2239+ msg = "Authorize security group ingress %s"
2240+ LOG.audit(_(msg), security_group['name'], context=context)
2241 values = self._revoke_rule_args_to_dict(context, **kwargs)
2242 if values is None:
2243 raise exception.ApiError(_("Not enough parameters to build a "
2244@@ -504,7 +580,7 @@
2245 security_group_rule = db.security_group_rule_create(context, values)
2246
2247 self.compute_api.trigger_security_group_rules_refresh(context,
2248- security_group['id'])
2249+ security_group_id=security_group['id'])
2250
2251 return True
2252
2253@@ -540,11 +616,23 @@
2254 return {'securityGroupSet': [self._format_security_group(context,
2255 group_ref)]}
2256
2257- def delete_security_group(self, context, group_name, **kwargs):
2258+ def delete_security_group(self, context, group_name=None, group_id=None,
2259+ **kwargs):
2260+ if not group_name and not group_id:
2261+ err = "Not enough parameters, need group_name or group_id"
2262+ raise exception.ApiError(_(err))
2263+ notfound = exception.SecurityGroupNotFound
2264+ if group_name:
2265+ security_group = db.security_group_get_by_name(context,
2266+ context.project_id,
2267+ group_name)
2268+ if not security_group:
2269+ raise notfound(security_group_id=group_name)
2270+ elif group_id:
2271+ security_group = db.security_group_get(context, group_id)
2272+ if not security_group:
2273+ raise notfound(security_group_id=group_id)
2274 LOG.audit(_("Delete security group %s"), group_name, context=context)
2275- security_group = db.security_group_get_by_name(context,
2276- context.project_id,
2277- group_name)
2278 db.security_group_destroy(context, security_group.id)
2279 return True
2280
2281@@ -559,7 +647,7 @@
2282 instance_id = ec2utils.ec2_id_to_id(ec2_id)
2283 output = self.compute_api.get_console_output(
2284 context, instance_id=instance_id)
2285- now = datetime.datetime.utcnow()
2286+ now = utils.utcnow()
2287 return {"InstanceId": ec2_id,
2288 "Timestamp": now,
2289 "output": base64.b64encode(output)}
2290@@ -619,16 +707,30 @@
2291 'volumeId': v['volumeId']}]
2292 else:
2293 v['attachmentSet'] = [{}]
2294+ if volume.get('snapshot_id') != None:
2295+ v['snapshotId'] = ec2utils.id_to_ec2_id(volume['snapshot_id'],
2296+ 'snap-%08x')
2297+ else:
2298+ v['snapshotId'] = None
2299
2300 v['display_name'] = volume['display_name']
2301 v['display_description'] = volume['display_description']
2302 return v
2303
2304- def create_volume(self, context, size, **kwargs):
2305- LOG.audit(_("Create volume of %s GB"), size, context=context)
2306+ def create_volume(self, context, **kwargs):
2307+ size = kwargs.get('size')
2308+ if kwargs.get('snapshot_id') != None:
2309+ snapshot_id = ec2utils.ec2_id_to_id(kwargs['snapshot_id'])
2310+ LOG.audit(_("Create volume from snapshot %s"), snapshot_id,
2311+ context=context)
2312+ else:
2313+ snapshot_id = None
2314+ LOG.audit(_("Create volume of %s GB"), size, context=context)
2315+
2316 volume = self.volume_api.create(
2317 context,
2318 size=size,
2319+ snapshot_id=snapshot_id,
2320 name=kwargs.get('display_name'),
2321 description=kwargs.get('display_description'))
2322 # TODO(vish): Instance should be None at db layer instead of
2323@@ -724,27 +826,27 @@
2324 instances = self.compute_api.get_all(context, **kwargs)
2325 for instance in instances:
2326 if not context.is_admin:
2327- if instance['image_id'] == str(FLAGS.vpn_image_id):
2328+ if instance['image_ref'] == str(FLAGS.vpn_image_id):
2329 continue
2330 i = {}
2331 instance_id = instance['id']
2332 ec2_id = ec2utils.id_to_ec2_id(instance_id)
2333 i['instanceId'] = ec2_id
2334- i['imageId'] = self.image_ec2_id(instance['image_id'])
2335+ i['imageId'] = self.image_ec2_id(instance['image_ref'])
2336 i['instanceState'] = {
2337 'code': instance['state'],
2338 'name': instance['state_description']}
2339 fixed_addr = None
2340 floating_addr = None
2341- if instance['fixed_ip']:
2342- fixed_addr = instance['fixed_ip']['address']
2343- if instance['fixed_ip']['floating_ips']:
2344- fixed = instance['fixed_ip']
2345+ if instance['fixed_ips']:
2346+ fixed = instance['fixed_ips'][0]
2347+ fixed_addr = fixed['address']
2348+ if fixed['floating_ips']:
2349 floating_addr = fixed['floating_ips'][0]['address']
2350- if instance['fixed_ip']['network'] and 'use_v6' in kwargs:
2351+ if fixed['network'] and 'use_v6' in kwargs:
2352 i['dnsNameV6'] = ipv6.to_global(
2353- instance['fixed_ip']['network']['cidr_v6'],
2354- instance['mac_address'],
2355+ fixed['network']['cidr_v6'],
2356+ fixed['virtual_interface']['address'],
2357 instance['project_id'])
2358
2359 i['privateDnsName'] = fixed_addr
2360@@ -816,8 +918,15 @@
2361
2362 def allocate_address(self, context, **kwargs):
2363 LOG.audit(_("Allocate address"), context=context)
2364- public_ip = self.network_api.allocate_floating_ip(context)
2365- return {'publicIp': public_ip}
2366+ try:
2367+ public_ip = self.network_api.allocate_floating_ip(context)
2368+ return {'publicIp': public_ip}
2369+ except rpc.RemoteError as ex:
2370+ # NOTE(tr3buchet) - why does this block exist?
2371+ if ex.exc_type == 'NoMoreFloatingIps':
2372+ raise exception.NoMoreFloatingIps()
2373+ else:
2374+ raise
2375
2376 def release_address(self, context, public_ip, **kwargs):
2377 LOG.audit(_("Release address %s"), public_ip, context=context)
2378@@ -846,10 +955,39 @@
2379 if kwargs.get('ramdisk_id'):
2380 ramdisk = self._get_image(context, kwargs['ramdisk_id'])
2381 kwargs['ramdisk_id'] = ramdisk['id']
2382+ for bdm in kwargs.get('block_device_mapping', []):
2383+ # NOTE(yamahata)
2384+ # BlockDevicedMapping.<N>.DeviceName
2385+ # BlockDevicedMapping.<N>.Ebs.SnapshotId
2386+ # BlockDevicedMapping.<N>.Ebs.VolumeSize
2387+ # BlockDevicedMapping.<N>.Ebs.DeleteOnTermination
2388+ # BlockDevicedMapping.<N>.VirtualName
2389+ # => remove .Ebs and allow volume id in SnapshotId
2390+ ebs = bdm.pop('ebs', None)
2391+ if ebs:
2392+ ec2_id = ebs.pop('snapshot_id')
2393+ id = ec2utils.ec2_id_to_id(ec2_id)
2394+ if ec2_id.startswith('snap-'):
2395+ bdm['snapshot_id'] = id
2396+ elif ec2_id.startswith('vol-'):
2397+ bdm['volume_id'] = id
2398+ ebs.setdefault('delete_on_termination', True)
2399+ bdm.update(ebs)
2400+
2401+ image = self._get_image(context, kwargs['image_id'])
2402+
2403+ if image:
2404+ image_state = self._get_image_state(image)
2405+ else:
2406+ raise exception.ImageNotFound(image_id=kwargs['image_id'])
2407+
2408+ if image_state != 'available':
2409+ raise exception.ApiError(_('Image must be available'))
2410+
2411 instances = self.compute_api.create(context,
2412 instance_type=instance_types.get_instance_type_by_name(
2413 kwargs.get('instance_type', None)),
2414- image_id=self._get_image(context, kwargs['image_id'])['id'],
2415+ image_href=self._get_image(context, kwargs['image_id'])['id'],
2416 min_count=int(kwargs.get('min_count', max_count)),
2417 max_count=max_count,
2418 kernel_id=kwargs.get('kernel_id'),
2419@@ -860,37 +998,54 @@
2420 user_data=kwargs.get('user_data'),
2421 security_group=kwargs.get('security_group'),
2422 availability_zone=kwargs.get('placement', {}).get(
2423- 'AvailabilityZone'))
2424+ 'AvailabilityZone'),
2425+ block_device_mapping=kwargs.get('block_device_mapping', {}))
2426 return self._format_run_instances(context,
2427 instances[0]['reservation_id'])
2428
2429+ def _do_instance(self, action, context, ec2_id):
2430+ instance_id = ec2utils.ec2_id_to_id(ec2_id)
2431+ action(context, instance_id=instance_id)
2432+
2433+ def _do_instances(self, action, context, instance_id):
2434+ for ec2_id in instance_id:
2435+ self._do_instance(action, context, ec2_id)
2436+
2437 def terminate_instances(self, context, instance_id, **kwargs):
2438 """Terminate each instance in instance_id, which is a list of ec2 ids.
2439 instance_id is a kwarg so its name cannot be modified."""
2440 LOG.debug(_("Going to start terminating instances"))
2441- for ec2_id in instance_id:
2442- instance_id = ec2utils.ec2_id_to_id(ec2_id)
2443- self.compute_api.delete(context, instance_id=instance_id)
2444+ self._do_instances(self.compute_api.delete, context, instance_id)
2445 return True
2446
2447 def reboot_instances(self, context, instance_id, **kwargs):
2448 """instance_id is a list of instance ids"""
2449 LOG.audit(_("Reboot instance %r"), instance_id, context=context)
2450- for ec2_id in instance_id:
2451- instance_id = ec2utils.ec2_id_to_id(ec2_id)
2452- self.compute_api.reboot(context, instance_id=instance_id)
2453+ self._do_instances(self.compute_api.reboot, context, instance_id)
2454+ return True
2455+
2456+ def stop_instances(self, context, instance_id, **kwargs):
2457+ """Stop each instances in instance_id.
2458+ Here instance_id is a list of instance ids"""
2459+ LOG.debug(_("Going to stop instances"))
2460+ self._do_instances(self.compute_api.stop, context, instance_id)
2461+ return True
2462+
2463+ def start_instances(self, context, instance_id, **kwargs):
2464+ """Start each instances in instance_id.
2465+ Here instance_id is a list of instance ids"""
2466+ LOG.debug(_("Going to start instances"))
2467+ self._do_instances(self.compute_api.start, context, instance_id)
2468 return True
2469
2470 def rescue_instance(self, context, instance_id, **kwargs):
2471 """This is an extension to the normal ec2_api"""
2472- instance_id = ec2utils.ec2_id_to_id(instance_id)
2473- self.compute_api.rescue(context, instance_id=instance_id)
2474+ self._do_instance(self.compute_api.rescue, contect, instnace_id)
2475 return True
2476
2477 def unrescue_instance(self, context, instance_id, **kwargs):
2478 """This is an extension to the normal ec2_api"""
2479- instance_id = ec2utils.ec2_id_to_id(instance_id)
2480- self.compute_api.unrescue(context, instance_id=instance_id)
2481+ self._do_instance(self.compute_api.unrescue, context, instance_id)
2482 return True
2483
2484 def update_instance(self, context, instance_id, **kwargs):
2485@@ -901,7 +1056,8 @@
2486 changes[field] = kwargs[field]
2487 if changes:
2488 instance_id = ec2utils.ec2_id_to_id(instance_id)
2489- self.compute_api.update(context, instance_id=instance_id, **kwargs)
2490+ self.compute_api.update(context, instance_id=instance_id,
2491+ **changes)
2492 return True
2493
2494 @staticmethod
2495@@ -925,17 +1081,26 @@
2496 def image_ec2_id(image_id, image_type='ami'):
2497 """Returns image ec2_id using id and three letter type."""
2498 template = image_type + '-%08x'
2499- return ec2utils.id_to_ec2_id(int(image_id), template=template)
2500+ try:
2501+ return ec2utils.id_to_ec2_id(int(image_id), template=template)
2502+ except ValueError:
2503+ #TODO(wwolf): once we have ec2_id -> glance_id mapping
2504+ # in place, this wont be necessary
2505+ return "ami-00000000"
2506
2507 def _get_image(self, context, ec2_id):
2508 try:
2509 internal_id = ec2utils.ec2_id_to_id(ec2_id)
2510- return self.image_service.show(context, internal_id)
2511+ image = self.image_service.show(context, internal_id)
2512 except (exception.InvalidEc2Id, exception.ImageNotFound):
2513 try:
2514 return self.image_service.show_by_name(context, ec2_id)
2515 except exception.NotFound:
2516 raise exception.ImageNotFound(image_id=ec2_id)
2517+ image_type = ec2_id.split('-')[0]
2518+ if self._image_type(image.get('container_format')) != image_type:
2519+ raise exception.ImageNotFound(image_id=ec2_id)
2520+ return image
2521
2522 def _format_image(self, image):
2523 """Convert from format defined by BaseImageService to S3 format."""
2524@@ -956,11 +1121,8 @@
2525 get('image_location'), name)
2526 else:
2527 i['imageLocation'] = image['properties'].get('image_location')
2528- # NOTE(vish): fallback status if image_state isn't set
2529- state = image.get('status')
2530- if state == 'active':
2531- state = 'available'
2532- i['imageState'] = image['properties'].get('image_state', state)
2533+
2534+ i['imageState'] = self._get_image_state(image)
2535 i['displayName'] = name
2536 i['description'] = image.get('description')
2537 display_mapping = {'aki': 'kernel',
2538
2539=== modified file 'nova/api/ec2/ec2utils.py'
2540--- nova/api/ec2/ec2utils.py 2011-05-11 18:02:01 +0000
2541+++ nova/api/ec2/ec2utils.py 2011-07-14 20:24:25 +0000
2542@@ -16,6 +16,8 @@
2543 # License for the specific language governing permissions and limitations
2544 # under the License.
2545
2546+import re
2547+
2548 from nova import exception
2549
2550
2551@@ -30,3 +32,95 @@
2552 def id_to_ec2_id(instance_id, template='i-%08x'):
2553 """Convert an instance ID (int) to an ec2 ID (i-[base 16 number])"""
2554 return template % instance_id
2555+
2556+
2557+_c2u = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
2558+
2559+
2560+def camelcase_to_underscore(str):
2561+ return _c2u.sub(r'_\1', str).lower().strip('_')
2562+
2563+
2564+def _try_convert(value):
2565+ """Return a non-string from a string or unicode, if possible.
2566+
2567+ ============= =====================================================
2568+ When value is returns
2569+ ============= =====================================================
2570+ zero-length ''
2571+ 'None' None
2572+ 'True' True case insensitive
2573+ 'False' False case insensitive
2574+ '0', '-0' 0
2575+ 0xN, -0xN int from hex (postitive) (N is any number)
2576+ 0bN, -0bN int from binary (positive) (N is any number)
2577+ * try conversion to int, float, complex, fallback value
2578+
2579+ """
2580+ if len(value) == 0:
2581+ return ''
2582+ if value == 'None':
2583+ return None
2584+ lowered_value = value.lower()
2585+ if lowered_value == 'true':
2586+ return True
2587+ if lowered_value == 'false':
2588+ return False
2589+ valueneg = value[1:] if value[0] == '-' else value
2590+ if valueneg == '0':
2591+ return 0
2592+ if valueneg == '':
2593+ return value
2594+ if valueneg[0] == '0':
2595+ if valueneg[1] in 'xX':
2596+ return int(value, 16)
2597+ elif valueneg[1] in 'bB':
2598+ return int(value, 2)
2599+ else:
2600+ try:
2601+ return int(value, 8)
2602+ except ValueError:
2603+ pass
2604+ try:
2605+ return int(value)
2606+ except ValueError:
2607+ pass
2608+ try:
2609+ return float(value)
2610+ except ValueError:
2611+ pass
2612+ try:
2613+ return complex(value)
2614+ except ValueError:
2615+ return value
2616+
2617+
2618+def dict_from_dotted_str(items):
2619+ """parse multi dot-separated argument into dict.
2620+ EBS boot uses multi dot-separeted arguments like
2621+ BlockDeviceMapping.1.DeviceName=snap-id
2622+ Convert the above into
2623+ {'block_device_mapping': {'1': {'device_name': snap-id}}}
2624+ """
2625+ args = {}
2626+ for key, value in items:
2627+ parts = key.split(".")
2628+ key = camelcase_to_underscore(parts[0])
2629+ if isinstance(value, str) or isinstance(value, unicode):
2630+ # NOTE(vish): Automatically convert strings back
2631+ # into their respective values
2632+ value = _try_convert(value)
2633+
2634+ if len(parts) > 1:
2635+ d = args.get(key, {})
2636+ args[key] = d
2637+ for k in parts[1:-1]:
2638+ k = camelcase_to_underscore(k)
2639+ v = d.get(k, {})
2640+ d[k] = v
2641+ d = v
2642+ d[camelcase_to_underscore(parts[-1])] = value
2643+ else:
2644+ args[key] = value
2645+
2646+ return args
2647
2648=== modified file 'nova/api/ec2/metadatarequesthandler.py'
2649--- nova/api/ec2/metadatarequesthandler.py 2011-03-03 16:04:33 +0000
2650+++ nova/api/ec2/metadatarequesthandler.py 2011-07-14 20:24:25 +0000
2651@@ -23,6 +23,7 @@
2652
2653 from nova import log as logging
2654 from nova import flags
2655+from nova import utils
2656 from nova import wsgi
2657 from nova.api.ec2 import cloud
2658
2659@@ -34,6 +35,9 @@
2660 class MetadataRequestHandler(wsgi.Application):
2661 """Serve metadata from the EC2 API."""
2662
2663+ def __init__(self):
2664+ self.cc = cloud.CloudController()
2665+
2666 def print_data(self, data):
2667 if isinstance(data, dict):
2668 output = ''
2669@@ -67,11 +71,18 @@
2670
2671 @webob.dec.wsgify(RequestClass=wsgi.Request)
2672 def __call__(self, req):
2673- cc = cloud.CloudController()
2674 remote_address = req.remote_addr
2675 if FLAGS.use_forwarded_for:
2676 remote_address = req.headers.get('X-Forwarded-For', remote_address)
2677- meta_data = cc.get_metadata(remote_address)
2678+ try:
2679+ meta_data = self.cc.get_metadata(remote_address)
2680+ except Exception:
2681+ LOG.exception(_('Failed to get metadata for ip: %s'),
2682+ remote_address)
2683+ msg = _('An unknown error has occurred. '
2684+ 'Please try your request again.')
2685+ exc = webob.exc.HTTPInternalServerError(explanation=unicode(msg))
2686+ return exc
2687 if meta_data is None:
2688 LOG.error(_('Failed to get metadata for ip: %s'), remote_address)
2689 raise webob.exc.HTTPNotFound()
2690
2691=== modified file 'nova/api/openstack/__init__.py'
2692--- nova/api/openstack/__init__.py 2011-05-13 15:17:19 +0000
2693+++ nova/api/openstack/__init__.py 2011-07-14 20:24:25 +0000
2694@@ -26,7 +26,7 @@
2695
2696 from nova import flags
2697 from nova import log as logging
2698-from nova import wsgi
2699+from nova import wsgi as base_wsgi
2700 from nova.api.openstack import accounts
2701 from nova.api.openstack import faults
2702 from nova.api.openstack import backup_schedules
2703@@ -40,6 +40,7 @@
2704 from nova.api.openstack import server_metadata
2705 from nova.api.openstack import shared_ip_groups
2706 from nova.api.openstack import users
2707+from nova.api.openstack import wsgi
2708 from nova.api.openstack import zones
2709
2710
2711@@ -50,7 +51,7 @@
2712 'When True, this API service will accept admin operations.')
2713
2714
2715-class FaultWrapper(wsgi.Middleware):
2716+class FaultWrapper(base_wsgi.Middleware):
2717 """Calls down the middleware stack, making exceptions into faults."""
2718
2719 @webob.dec.wsgify(RequestClass=wsgi.Request)
2720@@ -63,7 +64,7 @@
2721 return faults.Fault(exc)
2722
2723
2724-class APIRouter(wsgi.Router):
2725+class APIRouter(base_wsgi.Router):
2726 """
2727 Routes requests on the OpenStack API to the appropriate controller
2728 and method.
2729@@ -80,7 +81,9 @@
2730 self._setup_routes(mapper)
2731 super(APIRouter, self).__init__(mapper)
2732
2733- def _setup_routes(self, mapper):
2734+ def _setup_routes(self, mapper, version):
2735+ """Routes common to all versions."""
2736+
2737 server_members = self.server_members
2738 server_members['action'] = 'POST'
2739 if FLAGS.allow_admin_api:
2740@@ -97,21 +100,41 @@
2741 server_members['reset_network'] = 'POST'
2742 server_members['inject_network_info'] = 'POST'
2743
2744- mapper.resource("zone", "zones", controller=zones.Controller(),
2745- collection={'detail': 'GET', 'info': 'GET',
2746- 'select': 'GET'})
2747-
2748- mapper.resource("user", "users", controller=users.Controller(),
2749+ mapper.resource("user", "users",
2750+ controller=users.create_resource(),
2751 collection={'detail': 'GET'})
2752
2753 mapper.resource("account", "accounts",
2754- controller=accounts.Controller(),
2755+ controller=accounts.create_resource(),
2756 collection={'detail': 'GET'})
2757
2758+ mapper.resource("zone", "zones",
2759+ controller=zones.create_resource(version),
2760+ collection={'detail': 'GET',
2761+ 'info': 'GET',
2762+ 'select': 'POST',
2763+ 'boot': 'POST'})
2764+
2765 mapper.resource("console", "consoles",
2766- controller=consoles.Controller(),
2767- parent_resource=dict(member_name='server',
2768- collection_name='servers'))
2769+ controller=consoles.create_resource(),
2770+ parent_resource=dict(member_name='server',
2771+ collection_name='servers'))
2772+
2773+ mapper.resource("server", "servers",
2774+ controller=servers.create_resource(version),
2775+ collection={'detail': 'GET'},
2776+ member=self.server_members)
2777+
2778+ mapper.resource("image", "images",
2779+ controller=images.create_resource(version),
2780+ collection={'detail': 'GET'})
2781+
2782+ mapper.resource("limit", "limits",
2783+ controller=limits.create_resource(version))
2784+
2785+ mapper.resource("flavor", "flavors",
2786+ controller=flavors.create_resource(version),
2787+ collection={'detail': 'GET'})
2788
2789 super(APIRouter, self).__init__(mapper)
2790
2791@@ -120,33 +143,21 @@
2792 """Define routes specific to OpenStack API V1.0."""
2793
2794 def _setup_routes(self, mapper):
2795- super(APIRouterV10, self)._setup_routes(mapper)
2796- mapper.resource("server", "servers",
2797- controller=servers.ControllerV10(),
2798- collection={'detail': 'GET'},
2799- member=self.server_members)
2800-
2801+ super(APIRouterV10, self)._setup_routes(mapper, '1.0')
2802 mapper.resource("image", "images",
2803- controller=images.ControllerV10(),
2804- collection={'detail': 'GET'})
2805-
2806- mapper.resource("flavor", "flavors",
2807- controller=flavors.ControllerV10(),
2808+ controller=images.create_resource('1.0'),
2809 collection={'detail': 'GET'})
2810
2811 mapper.resource("shared_ip_group", "shared_ip_groups",
2812 collection={'detail': 'GET'},
2813- controller=shared_ip_groups.Controller())
2814+ controller=shared_ip_groups.create_resource())
2815
2816 mapper.resource("backup_schedule", "backup_schedule",
2817- controller=backup_schedules.Controller(),
2818+ controller=backup_schedules.create_resource(),
2819 parent_resource=dict(member_name='server',
2820 collection_name='servers'))
2821
2822- mapper.resource("limit", "limits",
2823- controller=limits.LimitsControllerV10())
2824-
2825- mapper.resource("ip", "ips", controller=ips.Controller(),
2826+ mapper.resource("ip", "ips", controller=ips.create_resource(),
2827 collection=dict(public='GET', private='GET'),
2828 parent_resource=dict(member_name='server',
2829 collection_name='servers'))
2830@@ -156,29 +167,13 @@
2831 """Define routes specific to OpenStack API V1.1."""
2832
2833 def _setup_routes(self, mapper):
2834- super(APIRouterV11, self)._setup_routes(mapper)
2835- mapper.resource("server", "servers",
2836- controller=servers.ControllerV11(),
2837- collection={'detail': 'GET'},
2838- member=self.server_members)
2839-
2840- mapper.resource("image", "images",
2841- controller=images.ControllerV11(),
2842- collection={'detail': 'GET'})
2843-
2844+ super(APIRouterV11, self)._setup_routes(mapper, '1.1')
2845 mapper.resource("image_meta", "meta",
2846- controller=image_metadata.Controller(),
2847+ controller=image_metadata.create_resource(),
2848 parent_resource=dict(member_name='image',
2849 collection_name='images'))
2850
2851 mapper.resource("server_meta", "meta",
2852- controller=server_metadata.Controller(),
2853+ controller=server_metadata.create_resource(),
2854 parent_resource=dict(member_name='server',
2855 collection_name='servers'))
2856-
2857- mapper.resource("flavor", "flavors",
2858- controller=flavors.ControllerV11(),
2859- collection={'detail': 'GET'})
2860-
2861- mapper.resource("limit", "limits",
2862- controller=limits.LimitsControllerV11())
2863
2864=== modified file 'nova/api/openstack/accounts.py'
2865--- nova/api/openstack/accounts.py 2011-04-27 21:03:05 +0000
2866+++ nova/api/openstack/accounts.py 2011-07-14 20:24:25 +0000
2867@@ -20,8 +20,9 @@
2868 from nova import log as logging
2869
2870 from nova.auth import manager
2871-from nova.api.openstack import common
2872 from nova.api.openstack import faults
2873+from nova.api.openstack import wsgi
2874+
2875
2876 FLAGS = flags.FLAGS
2877 LOG = logging.getLogger('nova.api.openstack')
2878@@ -34,12 +35,7 @@
2879 manager=account.project_manager_id)
2880
2881
2882-class Controller(common.OpenstackController):
2883-
2884- _serialization_metadata = {
2885- 'application/xml': {
2886- "attributes": {
2887- "account": ["id", "name", "description", "manager"]}}}
2888+class Controller(object):
2889
2890 def __init__(self):
2891 self.manager = manager.AuthManager()
2892@@ -66,20 +62,33 @@
2893 self.manager.delete_project(id)
2894 return {}
2895
2896- def create(self, req):
2897+ def create(self, req, body):
2898 """We use update with create-or-update semantics
2899 because the id comes from an external source"""
2900 raise faults.Fault(webob.exc.HTTPNotImplemented())
2901
2902- def update(self, req, id):
2903+ def update(self, req, id, body):
2904 """This is really create or update."""
2905 self._check_admin(req.environ['nova.context'])
2906- env = self._deserialize(req.body, req.get_content_type())
2907- description = env['account'].get('description')
2908- manager = env['account'].get('manager')
2909+ description = body['account'].get('description')
2910+ manager = body['account'].get('manager')
2911 try:
2912 account = self.manager.get_project(id)
2913 self.manager.modify_project(id, manager, description)
2914 except exception.NotFound:
2915 account = self.manager.create_project(id, manager, description)
2916 return dict(account=_translate_keys(account))
2917+
2918+
2919+def create_resource():
2920+ metadata = {
2921+ "attributes": {
2922+ "account": ["id", "name", "description", "manager"],
2923+ },
2924+ }
2925+
2926+ body_serializers = {
2927+ 'application/xml': wsgi.XMLDictSerializer(metadata=metadata),
2928+ }
2929+ serializer = wsgi.ResponseSerializer(body_serializers)
2930+ return wsgi.Resource(Controller(), serializer=serializer)
2931
2932=== modified file 'nova/api/openstack/auth.py'
2933--- nova/api/openstack/auth.py 2011-05-17 19:12:48 +0000
2934+++ nova/api/openstack/auth.py 2011-07-14 20:24:25 +0000
2935@@ -13,9 +13,8 @@
2936 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2937 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2938 # License for the specific language governing permissions and limitations
2939-# under the License.import datetime
2940+# under the License.
2941
2942-import datetime
2943 import hashlib
2944 import time
2945
2946@@ -50,19 +49,22 @@
2947 if not self.has_authentication(req):
2948 return self.authenticate(req)
2949 user = self.get_user_by_authentication(req)
2950- accounts = self.auth.get_projects(user=user)
2951 if not user:
2952 token = req.headers["X-Auth-Token"]
2953 msg = _("%(user)s could not be found with token '%(token)s'")
2954 LOG.warn(msg % locals())
2955 return faults.Fault(webob.exc.HTTPUnauthorized())
2956
2957- if accounts:
2958- #we are punting on this til auth is settled,
2959- #and possibly til api v1.1 (mdragon)
2960- account = accounts[0]
2961- else:
2962- return faults.Fault(webob.exc.HTTPUnauthorized())
2963+ try:
2964+ account = req.headers["X-Auth-Project-Id"]
2965+ except KeyError:
2966+ # FIXME(usrleon): It needed only for compatibility
2967+ # while osapi clients don't use this header
2968+ accounts = self.auth.get_projects(user=user)
2969+ if accounts:
2970+ account = accounts[0]
2971+ else:
2972+ return faults.Fault(webob.exc.HTTPUnauthorized())
2973
2974 if not self.auth.is_admin(user) and \
2975 not self.auth.is_project_member(user, account):
2976@@ -127,7 +129,7 @@
2977 except exception.NotFound:
2978 return None
2979 if token:
2980- delta = datetime.datetime.utcnow() - token['created_at']
2981+ delta = utils.utcnow() - token['created_at']
2982 if delta.days >= 2:
2983 self.db.auth_token_destroy(ctxt, token['token_hash'])
2984 else:
2985
2986=== modified file 'nova/api/openstack/backup_schedules.py'
2987--- nova/api/openstack/backup_schedules.py 2011-03-30 17:05:06 +0000
2988+++ nova/api/openstack/backup_schedules.py 2011-07-14 20:24:25 +0000
2989@@ -19,9 +19,8 @@
2990
2991 from webob import exc
2992
2993-from nova.api.openstack import common
2994 from nova.api.openstack import faults
2995-import nova.image.service
2996+from nova.api.openstack import wsgi
2997
2998
2999 def _translate_keys(inst):
3000@@ -29,30 +28,41 @@
3001 return dict(backupSchedule=inst)
3002
3003
3004-class Controller(common.OpenstackController):
3005+class Controller(object):
3006 """ The backup schedule API controller for the Openstack API """
3007
3008- _serialization_metadata = {
3009- 'application/xml': {
3010- 'attributes': {
3011- 'backupSchedule': []}}}
3012-
3013 def __init__(self):
3014 pass
3015
3016- def index(self, req, server_id):
3017+ def index(self, req, server_id, **kwargs):
3018 """ Returns the list of backup schedules for a given instance """
3019 return faults.Fault(exc.HTTPNotImplemented())
3020
3021- def show(self, req, server_id, id):
3022+ def show(self, req, server_id, id, **kwargs):
3023 """ Returns a single backup schedule for a given instance """
3024 return faults.Fault(exc.HTTPNotImplemented())
3025
3026- def create(self, req, server_id):
3027+ def create(self, req, server_id, **kwargs):
3028 """ No actual update method required, since the existing API allows
3029 both create and update through a POST """
3030 return faults.Fault(exc.HTTPNotImplemented())
3031
3032- def delete(self, req, server_id, id):
3033+ def delete(self, req, server_id, id, **kwargs):
3034 """ Deletes an existing backup schedule """
3035 return faults.Fault(exc.HTTPNotImplemented())
3036+
3037+
3038+def create_resource():
3039+ metadata = {
3040+ 'attributes': {
3041+ 'backupSchedule': [],
3042+ },
3043+ }
3044+
3045+ body_serializers = {
3046+ 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10,
3047+ metadata=metadata),
3048+ }
3049+
3050+ serializer = wsgi.ResponseSerializer(body_serializers)
3051+ return wsgi.Resource(Controller(), serializer=serializer)
3052
3053=== modified file 'nova/api/openstack/common.py'
3054--- nova/api/openstack/common.py 2011-05-02 20:14:41 +0000
3055+++ nova/api/openstack/common.py 2011-07-14 20:24:25 +0000
3056@@ -23,12 +23,9 @@
3057 from nova import exception
3058 from nova import flags
3059 from nova import log as logging
3060-from nova import wsgi
3061
3062
3063 LOG = logging.getLogger('nova.api.openstack.common')
3064-
3065-
3066 FLAGS = flags.FLAGS
3067
3068
3069@@ -36,6 +33,34 @@
3070 XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1'
3071
3072
3073+def get_pagination_params(request):
3074+ """Return marker, limit tuple from request.
3075+
3076+ :param request: `wsgi.Request` possibly containing 'marker' and 'limit'
3077+ GET variables. 'marker' is the id of the last element
3078+ the client has seen, and 'limit' is the maximum number
3079+ of items to return. If 'limit' is not specified, 0, or
3080+ > max_limit, we default to max_limit. Negative values
3081+ for either marker or limit will cause
3082+ exc.HTTPBadRequest() exceptions to be raised.
3083+
3084+ """
3085+ params = {}
3086+ for param in ['marker', 'limit']:
3087+ if not param in request.GET:
3088+ continue
3089+ try:
3090+ params[param] = int(request.GET[param])
3091+ except ValueError:
3092+ msg = _('%s param must be an integer') % param
3093+ raise webob.exc.HTTPBadRequest(msg)
3094+ if params[param] < 0:
3095+ msg = _('%s param must be positive') % param
3096+ raise webob.exc.HTTPBadRequest(msg)
3097+
3098+ return params
3099+
3100+
3101 def limited(items, request, max_limit=FLAGS.osapi_max_limit):
3102 """
3103 Return a slice of items according to requested offset and limit.
3104@@ -72,19 +97,10 @@
3105
3106 def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit):
3107 """Return a slice of items according to the requested marker and limit."""
3108-
3109- try:
3110- marker = int(request.GET.get('marker', 0))
3111- except ValueError:
3112- raise webob.exc.HTTPBadRequest(_('marker param must be an integer'))
3113-
3114- try:
3115- limit = int(request.GET.get('limit', max_limit))
3116- except ValueError:
3117- raise webob.exc.HTTPBadRequest(_('limit param must be an integer'))
3118-
3119- if limit < 0:
3120- raise webob.exc.HTTPBadRequest(_('limit param must be positive'))
3121+ params = get_pagination_params(request)
3122+
3123+ limit = params.get('limit', max_limit)
3124+ marker = params.get('marker')
3125
3126 limit = min(max_limit, limit)
3127 start_index = 0
3128@@ -100,34 +116,6 @@
3129 return items[start_index:range_end]
3130
3131
3132-def get_image_id_from_image_hash(image_service, context, image_hash):
3133- """Given an Image ID Hash, return an objectstore Image ID.
3134-
3135- image_service - reference to objectstore compatible image service.
3136- context - security context for image service requests.
3137- image_hash - hash of the image ID.
3138- """
3139-
3140- # FIX(sandy): This is terribly inefficient. It pulls all images
3141- # from objectstore in order to find the match. ObjectStore
3142- # should have a numeric counterpart to the string ID.
3143- try:
3144- items = image_service.detail(context)
3145- except NotImplementedError:
3146- items = image_service.index(context)
3147- for image in items:
3148- image_id = image['id']
3149- try:
3150- if abs(hash(image_id)) == int(image_hash):
3151- return image_id
3152- except ValueError:
3153- msg = _("Requested image_id has wrong format: %s,"
3154- "should have numerical format") % image_id
3155- LOG.error(msg)
3156- raise Exception(msg)
3157- raise exception.ImageNotFound(image_id=image_hash)
3158-
3159-
3160 def get_id_from_href(href):
3161 """Return the id portion of a url as an int.
3162
3163@@ -145,10 +133,25 @@
3164 return int(urlparse(href).path.split('/')[-1])
3165 except:
3166 LOG.debug(_("Error extracting id from href: %s") % href)
3167- raise webob.exc.HTTPBadRequest(_('could not parse id from href'))
3168-
3169-
3170-class OpenstackController(wsgi.Controller):
3171- def get_default_xmlns(self, req):
3172- # Use V10 by default
3173- return XML_NS_V10
3174+ raise ValueError(_('could not parse id from href'))
3175+
3176+
3177+def remove_version_from_href(href):
3178+ """Removes the api version from the href.
3179+
3180+ Given: 'http://www.nova.com/v1.1/123'
3181+ Returns: 'http://www.nova.com/123'
3182+
3183+ """
3184+ try:
3185+ #matches /v#.#
3186+ new_href = re.sub(r'[/][v][0-9]*.[0-9]*', '', href)
3187+ except:
3188+ LOG.debug(_("Error removing version from href: %s") % href)
3189+ msg = _('could not parse version from href')
3190+ raise ValueError(msg)
3191+
3192+ if new_href == href:
3193+ msg = _('href does not contain version')
3194+ raise ValueError(msg)
3195+ return new_href
3196
3197=== modified file 'nova/api/openstack/consoles.py'
3198--- nova/api/openstack/consoles.py 2011-03-30 17:05:06 +0000
3199+++ nova/api/openstack/consoles.py 2011-07-14 20:24:25 +0000
3200@@ -19,8 +19,8 @@
3201
3202 from nova import console
3203 from nova import exception
3204-from nova.api.openstack import common
3205 from nova.api.openstack import faults
3206+from nova.api.openstack import wsgi
3207
3208
3209 def _translate_keys(cons):
3210@@ -43,17 +43,11 @@
3211 return dict(console=info)
3212
3213
3214-class Controller(common.OpenstackController):
3215- """The Consoles Controller for the Openstack API"""
3216-
3217- _serialization_metadata = {
3218- 'application/xml': {
3219- 'attributes': {
3220- 'console': []}}}
3221+class Controller(object):
3222+ """The Consoles controller for the Openstack API"""
3223
3224 def __init__(self):
3225 self.console_api = console.API()
3226- super(Controller, self).__init__()
3227
3228 def index(self, req, server_id):
3229 """Returns a list of consoles for this instance"""
3230@@ -63,9 +57,8 @@
3231 return dict(consoles=[_translate_keys(console)
3232 for console in consoles])
3233
3234- def create(self, req, server_id):
3235+ def create(self, req, server_id, body):
3236 """Creates a new console"""
3237- #info = self._deserialize(req.body, req.get_content_type())
3238 self.console_api.create_console(
3239 req.environ['nova.context'],
3240 int(server_id))
3241@@ -94,3 +87,7 @@
3242 except exception.NotFound:
3243 return faults.Fault(exc.HTTPNotFound())
3244 return exc.HTTPAccepted()
3245+
3246+
3247+def create_resource():
3248+ return wsgi.Resource(Controller())
3249
3250=== modified file 'nova/api/openstack/contrib/__init__.py'
3251--- nova/api/openstack/contrib/__init__.py 2011-03-30 01:16:09 +0000
3252+++ nova/api/openstack/contrib/__init__.py 2011-07-14 20:24:25 +0000
3253@@ -13,7 +13,7 @@
3254 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3255 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3256 # License for the specific language governing permissions and limitations
3257-# under the License.import datetime
3258+# under the License.
3259
3260 """Contrib contains extensions that are shipped with nova.
3261
3262
3263=== added file 'nova/api/openstack/contrib/flavorextraspecs.py'
3264--- nova/api/openstack/contrib/flavorextraspecs.py 1970-01-01 00:00:00 +0000
3265+++ nova/api/openstack/contrib/flavorextraspecs.py 2011-07-14 20:24:25 +0000
3266@@ -0,0 +1,126 @@
3267+# vim: tabstop=4 shiftwidth=4 softtabstop=4
3268+
3269+# Copyright 2011 University of Southern California
3270+# All Rights Reserved.
3271+#
3272+# Licensed under the Apache License, Version 2.0 (the "License"); you may
3273+# not use this file except in compliance with the License. You may obtain
3274+# a copy of the License at
3275+#
3276+# http://www.apache.org/licenses/LICENSE-2.0
3277+#
3278+# Unless required by applicable law or agreed to in writing, software
3279+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3280+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3281+# License for the specific language governing permissions and limitations
3282+# under the License.
3283+
3284+""" The instance type extra specs extension"""
3285+
3286+from webob import exc
3287+
3288+from nova import db
3289+from nova import quota
3290+from nova.api.openstack import extensions
3291+from nova.api.openstack import faults
3292+from nova.api.openstack import wsgi
3293+
3294+
3295+class FlavorExtraSpecsController(object):
3296+ """ The flavor extra specs API controller for the Openstack API """
3297+
3298+ def _get_extra_specs(self, context, flavor_id):
3299+ extra_specs = db.api.instance_type_extra_specs_get(context, flavor_id)
3300+ specs_dict = {}
3301+ for key, value in extra_specs.iteritems():
3302+ specs_dict[key] = value
3303+ return dict(extra_specs=specs_dict)
3304+
3305+ def _check_body(self, body):
3306+ if body == None or body == "":
3307+ expl = _('No Request Body')
3308+ raise exc.HTTPBadRequest(explanation=expl)
3309+
3310+ def index(self, req, flavor_id):
3311+ """ Returns the list of extra specs for a givenflavor """
3312+ context = req.environ['nova.context']
3313+ return self._get_extra_specs(context, flavor_id)
3314+
3315+ def create(self, req, flavor_id, body):
3316+ self._check_body(body)
3317+ context = req.environ['nova.context']
3318+ specs = body.get('extra_specs')
3319+ try:
3320+ db.api.instance_type_extra_specs_update_or_create(context,
3321+ flavor_id,
3322+ specs)
3323+ except quota.QuotaError as error:
3324+ self._handle_quota_error(error)
3325+ return body
3326+
3327+ def update(self, req, flavor_id, id, body):
3328+ self._check_body(body)
3329+ context = req.environ['nova.context']
3330+ if not id in body:
3331+ expl = _('Request body and URI mismatch')
3332+ raise exc.HTTPBadRequest(explanation=expl)
3333+ if len(body) > 1:
3334+ expl = _('Request body contains too many items')
3335+ raise exc.HTTPBadRequest(explanation=expl)
3336+ try:
3337+ db.api.instance_type_extra_specs_update_or_create(context,
3338+ flavor_id,
3339+ body)
3340+ except quota.QuotaError as error:
3341+ self._handle_quota_error(error)
3342+
3343+ return body
3344+
3345+ def show(self, req, flavor_id, id):
3346+ """ Return a single extra spec item """
3347+ context = req.environ['nova.context']
3348+ specs = self._get_extra_specs(context, flavor_id)
3349+ if id in specs['extra_specs']:
3350+ return {id: specs['extra_specs'][id]}
3351+ else:
3352+ return faults.Fault(exc.HTTPNotFound())
3353+
3354+ def delete(self, req, flavor_id, id):
3355+ """ Deletes an existing extra spec """
3356+ context = req.environ['nova.context']
3357+ db.api.instance_type_extra_specs_delete(context, flavor_id, id)
3358+
3359+ def _handle_quota_error(self, error):
3360+ """Reraise quota errors as api-specific http exceptions."""
3361+ if error.code == "MetadataLimitExceeded":
3362+ raise exc.HTTPBadRequest(explanation=error.message)
3363+ raise error
3364+
3365+
3366+class Flavorextraspecs(extensions.ExtensionDescriptor):
3367+
3368+ def get_name(self):
3369+ return "FlavorExtraSpecs"
3370+
3371+ def get_alias(self):
3372+ return "os-flavor-extra-specs"
3373+
3374+ def get_description(self):
3375+ return "Instance type (flavor) extra specs"
3376+
3377+ def get_namespace(self):
3378+ return \
3379+ "http://docs.openstack.org/ext/flavor_extra_specs/api/v1.1"
3380+
3381+ def get_updated(self):
3382+ return "2011-06-23T00:00:00+00:00"
3383+
3384+ def get_resources(self):
3385+ resources = []
3386+ res = extensions.ResourceExtension(
3387+ 'os-extra_specs',
3388+ FlavorExtraSpecsController(),
3389+ parent=dict(member_name='flavor', collection_name='flavors'))
3390+
3391+ resources.append(res)
3392+ return resources
3393
3394=== added file 'nova/api/openstack/contrib/floating_ips.py'
3395--- nova/api/openstack/contrib/floating_ips.py 1970-01-01 00:00:00 +0000
3396+++ nova/api/openstack/contrib/floating_ips.py 2011-07-14 20:24:25 +0000
3397@@ -0,0 +1,173 @@
3398+# vim: tabstop=4 shiftwidth=4 softtabstop=4
3399+
3400+# Copyright 2011 OpenStack LLC.
3401+# Copyright 2011 Grid Dynamics
3402+# Copyright 2011 Eldar Nugaev, Kirill Shileev, Ilya Alekseyev
3403+#
3404+# Licensed under the Apache License, Version 2.0 (the "License"); you may
3405+# not use this file except in compliance with the License. You may obtain
3406+# a copy of the License at
3407+#
3408+# http://www.apache.org/licenses/LICENSE-2.0
3409+#
3410+# Unless required by applicable law or agreed to in writing, software
3411+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3412+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3413+# License for the specific language governing permissions and limitations
3414+# under the License
3415+from webob import exc
3416+
3417+from nova import exception
3418+from nova import network
3419+from nova import rpc
3420+from nova.api.openstack import faults
3421+from nova.api.openstack import extensions
3422+
3423+
3424+def _translate_floating_ip_view(floating_ip):
3425+ result = {'id': floating_ip['id'],
3426+ 'ip': floating_ip['address']}
3427+ if 'fixed_ip' in floating_ip:
3428+ result['fixed_ip'] = floating_ip['fixed_ip']['address']
3429+ else:
3430+ result['fixed_ip'] = None
3431+ if 'instance' in floating_ip:
3432+ result['instance_id'] = floating_ip['instance']['id']
3433+ else:
3434+ result['instance_id'] = None
3435+ return {'floating_ip': result}
3436+
3437+
3438+def _translate_floating_ips_view(floating_ips):
3439+ return {'floating_ips': [_translate_floating_ip_view(floating_ip)
3440+ for floating_ip in floating_ips]}
3441+
3442+
3443+class FloatingIPController(object):
3444+ """The Floating IPs API controller for the OpenStack API."""
3445+
3446+ _serialization_metadata = {
3447+ 'application/xml': {
3448+ "attributes": {
3449+ "floating_ip": [
3450+ "id",
3451+ "ip",
3452+ "instance_id",
3453+ "fixed_ip",
3454+ ]}}}
3455+
3456+ def __init__(self):
3457+ self.network_api = network.API()
3458+ super(FloatingIPController, self).__init__()
3459+
3460+ def show(self, req, id):
3461+ """Return data about the given floating ip."""
3462+ context = req.environ['nova.context']
3463+
3464+ try:
3465+ floating_ip = self.network_api.get_floating_ip(context, id)
3466+ except exception.NotFound:
3467+ return faults.Fault(exc.HTTPNotFound())
3468+
3469+ return _translate_floating_ip_view(floating_ip)
3470+
3471+ def index(self, req):
3472+ context = req.environ['nova.context']
3473+
3474+ floating_ips = self.network_api.list_floating_ips(context)
3475+
3476+ return _translate_floating_ips_view(floating_ips)
3477+
3478+ def create(self, req):
3479+ context = req.environ['nova.context']
3480+
3481+ try:
3482+ address = self.network_api.allocate_floating_ip(context)
3483+ ip = self.network_api.get_floating_ip_by_ip(context, address)
3484+ except rpc.RemoteError as ex:
3485+ # NOTE(tr3buchet) - why does this block exist?
3486+ if ex.exc_type == 'NoMoreFloatingIps':
3487+ raise exception.NoMoreFloatingIps()
3488+ else:
3489+ raise
3490+
3491+ return {'allocated': {
3492+ "id": ip['id'],
3493+ "floating_ip": ip['address']}}
3494+
3495+ def delete(self, req, id):
3496+ context = req.environ['nova.context']
3497+
3498+ ip = self.network_api.get_floating_ip(context, id)
3499+ self.network_api.release_floating_ip(context, address=ip)
3500+
3501+ return {'released': {
3502+ "id": ip['id'],
3503+ "floating_ip": ip['address']}}
3504+
3505+ def associate(self, req, id, body):
3506+ """ /floating_ips/{id}/associate fixed ip in body """
3507+ context = req.environ['nova.context']
3508+ floating_ip = self._get_ip_by_id(context, id)
3509+
3510+ fixed_ip = body['associate_address']['fixed_ip']
3511+
3512+ try:
3513+ self.network_api.associate_floating_ip(context,
3514+ floating_ip, fixed_ip)
3515+ except rpc.RemoteError:
3516+ raise
3517+
3518+ return {'associated':
3519+ {
3520+ "floating_ip_id": id,
3521+ "floating_ip": floating_ip,
3522+ "fixed_ip": fixed_ip}}
3523+
3524+ def disassociate(self, req, id):
3525+ """ POST /floating_ips/{id}/disassociate """
3526+ context = req.environ['nova.context']
3527+ floating_ip = self.network_api.get_floating_ip(context, id)
3528+ address = floating_ip['address']
3529+ fixed_ip = floating_ip['fixed_ip']['address']
3530+
3531+ try:
3532+ self.network_api.disassociate_floating_ip(context, address)
3533+ except rpc.RemoteError:
3534+ raise
3535+
3536+ return {'disassociated': {'floating_ip': address,
3537+ 'fixed_ip': fixed_ip}}
3538+
3539+ def _get_ip_by_id(self, context, value):
3540+ """Checks that value is id and then returns its address."""
3541+ return self.network_api.get_floating_ip(context, value)['address']
3542+
3543+
3544+class Floating_ips(extensions.ExtensionDescriptor):
3545+ def get_name(self):
3546+ return "Floating_ips"
3547+
3548+ def get_alias(self):
3549+ return "os-floating-ips"
3550+
3551+ def get_description(self):
3552+ return "Floating IPs support"
3553+
3554+ def get_namespace(self):
3555+ return "http://docs.openstack.org/ext/floating_ips/api/v1.1"
3556+
3557+ def get_updated(self):
3558+ return "2011-06-16T00:00:00+00:00"
3559+
3560+ def get_resources(self):
3561+ resources = []
3562+
3563+ res = extensions.ResourceExtension('os-floating-ips',
3564+ FloatingIPController(),
3565+ member_actions={
3566+ 'associate': 'POST',
3567+ 'disassociate': 'POST'})
3568+ resources.append(res)
3569+
3570+ return resources
3571
3572=== added file 'nova/api/openstack/contrib/hosts.py'
3573--- nova/api/openstack/contrib/hosts.py 1970-01-01 00:00:00 +0000
3574+++ nova/api/openstack/contrib/hosts.py 2011-07-14 20:24:25 +0000
3575@@ -0,0 +1,114 @@
3576+# Copyright (c) 2011 Openstack, LLC.
3577+# All Rights Reserved.
3578+#
3579+# Licensed under the Apache License, Version 2.0 (the "License"); you may
3580+# not use this file except in compliance with the License. You may obtain
3581+# a copy of the License at
3582+#
3583+# http://www.apache.org/licenses/LICENSE-2.0
3584+#
3585+# Unless required by applicable law or agreed to in writing, software
3586+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3587+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3588+# License for the specific language governing permissions and limitations
3589+# under the License.
3590+
3591+"""The hosts admin extension."""
3592+
3593+import webob.exc
3594+
3595+from nova import compute
3596+from nova import exception
3597+from nova import flags
3598+from nova import log as logging
3599+from nova.api.openstack import common
3600+from nova.api.openstack import extensions
3601+from nova.api.openstack import faults
3602+from nova.scheduler import api as scheduler_api
3603+
3604+
3605+LOG = logging.getLogger("nova.api.hosts")
3606+FLAGS = flags.FLAGS
3607+
3608+
3609+def _list_hosts(req, service=None):
3610+ """Returns a summary list of hosts, optionally filtering
3611+ by service type.
3612+ """
3613+ context = req.environ['nova.context']
3614+ hosts = scheduler_api.get_host_list(context)
3615+ if service:
3616+ hosts = [host for host in hosts
3617+ if host["service"] == service]
3618+ return hosts
3619+
3620+
3621+def check_host(fn):
3622+ """Makes sure that the host exists."""
3623+ def wrapped(self, req, id, service=None, *args, **kwargs):
3624+ listed_hosts = _list_hosts(req, service)
3625+ hosts = [h["host_name"] for h in listed_hosts]
3626+ if id in hosts:
3627+ return fn(self, req, id, *args, **kwargs)
3628+ else:
3629+ raise exception.HostNotFound(host=id)
3630+ return wrapped
3631+
3632+
3633+class HostController(object):
3634+ """The Hosts API controller for the OpenStack API."""
3635+ def __init__(self):
3636+ self.compute_api = compute.API()
3637+ super(HostController, self).__init__()
3638+
3639+ def index(self, req):
3640+ return {'hosts': _list_hosts(req)}
3641+
3642+ @check_host
3643+ def update(self, req, id, body):
3644+ for raw_key, raw_val in body.iteritems():
3645+ key = raw_key.lower().strip()
3646+ val = raw_val.lower().strip()
3647+ # NOTE: (dabo) Right now only 'status' can be set, but other
3648+ # actions may follow.
3649+ if key == "status":
3650+ if val[:6] in ("enable", "disabl"):
3651+ return self._set_enabled_status(req, id,
3652+ enabled=(val.startswith("enable")))
3653+ else:
3654+ explanation = _("Invalid status: '%s'") % raw_val
3655+ raise webob.exc.HTTPBadRequest(explanation=explanation)
3656+ else:
3657+ explanation = _("Invalid update setting: '%s'") % raw_key
3658+ raise webob.exc.HTTPBadRequest(explanation=explanation)
3659+
3660+ def _set_enabled_status(self, req, host, enabled):
3661+ """Sets the specified host's ability to accept new instances."""
3662+ context = req.environ['nova.context']
3663+ state = "enabled" if enabled else "disabled"
3664+ LOG.audit(_("Setting host %(host)s to %(state)s.") % locals())
3665+ result = self.compute_api.set_host_enabled(context, host=host,
3666+ enabled=enabled)
3667+ return {"host": host, "status": result}
3668+
3669+
3670+class Hosts(extensions.ExtensionDescriptor):
3671+ def get_name(self):
3672+ return "Hosts"
3673+
3674+ def get_alias(self):
3675+ return "os-hosts"
3676+
3677+ def get_description(self):
3678+ return "Host administration"
3679+
3680+ def get_namespace(self):
3681+ return "http://docs.openstack.org/ext/hosts/api/v1.1"
3682+
3683+ def get_updated(self):
3684+ return "2011-06-29T00:00:00+00:00"
3685+
3686+ def get_resources(self):
3687+ resources = [extensions.ResourceExtension('os-hosts', HostController(),
3688+ collection_actions={'update': 'PUT'}, member_actions={})]
3689+ return resources
3690
3691=== added file 'nova/api/openstack/contrib/multinic.py'
3692--- nova/api/openstack/contrib/multinic.py 1970-01-01 00:00:00 +0000
3693+++ nova/api/openstack/contrib/multinic.py 2011-07-14 20:24:25 +0000
3694@@ -0,0 +1,125 @@
3695+# Copyright 2011 OpenStack LLC.
3696+# All Rights Reserved.
3697+#
3698+# Licensed under the Apache License, Version 2.0 (the "License"); you may
3699+# not use this file except in compliance with the License. You may obtain
3700+# a copy of the License at
3701+#
3702+# http://www.apache.org/licenses/LICENSE-2.0
3703+#
3704+# Unless required by applicable law or agreed to in writing, software
3705+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3706+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3707+# License for the specific language governing permissions and limitations
3708+# under the License.
3709+
3710+"""The multinic extension."""
3711+
3712+from webob import exc
3713+
3714+from nova import compute
3715+from nova import log as logging
3716+from nova.api.openstack import extensions
3717+from nova.api.openstack import faults
3718+
3719+
3720+LOG = logging.getLogger("nova.api.multinic")
3721+
3722+
3723+# Note: The class name is as it has to be for this to be loaded as an
3724+# extension--only first character capitalized.
3725+class Multinic(extensions.ExtensionDescriptor):
3726+ """The multinic extension.
3727+
3728+ Exposes addFixedIp and removeFixedIp actions on servers.
3729+
3730+ """
3731+
3732+ def __init__(self, *args, **kwargs):
3733+ """Initialize the extension.
3734+
3735+ Gets a compute.API object so we can call the back-end
3736+ add_fixed_ip() and remove_fixed_ip() methods.
3737+ """
3738+
3739+ super(Multinic, self).__init__(*args, **kwargs)
3740+ self.compute_api = compute.API()
3741+
3742+ def get_name(self):
3743+ """Return the extension name, as required by contract."""
3744+
3745+ return "Multinic"
3746+
3747+ def get_alias(self):
3748+ """Return the extension alias, as required by contract."""
3749+
3750+ return "NMN"
3751+
3752+ def get_description(self):
3753+ """Return the extension description, as required by contract."""
3754+
3755+ return "Multiple network support"
3756+
3757+ def get_namespace(self):
3758+ """Return the namespace, as required by contract."""
3759+
3760+ return "http://docs.openstack.org/ext/multinic/api/v1.1"
3761+
3762+ def get_updated(self):
3763+ """Return the last updated timestamp, as required by contract."""
3764+
3765+ return "2011-06-09T00:00:00+00:00"
3766+
3767+ def get_actions(self):
3768+ """Return the actions the extension adds, as required by contract."""
3769+
3770+ actions = []
3771+
3772+ # Add the add_fixed_ip action
3773+ act = extensions.ActionExtension("servers", "addFixedIp",
3774+ self._add_fixed_ip)
3775+ actions.append(act)
3776+
3777+ # Add the remove_fixed_ip action
3778+ act = extensions.ActionExtension("servers", "removeFixedIp",
3779+ self._remove_fixed_ip)
3780+ actions.append(act)
3781+
3782+ return actions
3783+
3784+ def _add_fixed_ip(self, input_dict, req, id):
3785+ """Adds an IP on a given network to an instance."""
3786+
3787+ try:
3788+ # Validate the input entity
3789+ if 'networkId' not in input_dict['addFixedIp']:
3790+ LOG.exception(_("Missing 'networkId' argument for addFixedIp"))
3791+ return faults.Fault(exc.HTTPUnprocessableEntity())
3792+
3793+ # Add the fixed IP
3794+ network_id = input_dict['addFixedIp']['networkId']
3795+ self.compute_api.add_fixed_ip(req.environ['nova.context'], id,
3796+ network_id)
3797+ except Exception, e:
3798+ LOG.exception(_("Error in addFixedIp %s"), e)
3799+ return faults.Fault(exc.HTTPBadRequest())
3800+ return exc.HTTPAccepted()
3801+
3802+ def _remove_fixed_ip(self, input_dict, req, id):
3803+ """Removes an IP from an instance."""
3804+
3805+ try:
3806+ # Validate the input entity
3807+ if 'address' not in input_dict['removeFixedIp']:
3808+ LOG.exception(_("Missing 'address' argument for "
3809+ "removeFixedIp"))
3810+ return faults.Fault(exc.HTTPUnprocessableEntity())
3811+
3812+ # Remove the fixed IP
3813+ address = input_dict['removeFixedIp']['address']
3814+ self.compute_api.remove_fixed_ip(req.environ['nova.context'], id,
3815+ address)
3816+ except Exception, e:
3817+ LOG.exception(_("Error in removeFixedIp %s"), e)
3818+ return faults.Fault(exc.HTTPBadRequest())
3819+ return exc.HTTPAccepted()
3820
3821=== modified file 'nova/api/openstack/contrib/volumes.py'
3822--- nova/api/openstack/contrib/volumes.py 2011-04-19 09:54:47 +0000
3823+++ nova/api/openstack/contrib/volumes.py 2011-07-14 20:24:25 +0000
3824@@ -22,7 +22,6 @@
3825 from nova import flags
3826 from nova import log as logging
3827 from nova import volume
3828-from nova import wsgi
3829 from nova.api.openstack import common
3830 from nova.api.openstack import extensions
3831 from nova.api.openstack import faults
3832@@ -64,7 +63,7 @@
3833 return d
3834
3835
3836-class VolumeController(wsgi.Controller):
3837+class VolumeController(object):
3838 """The Volumes API controller for the OpenStack API."""
3839
3840 _serialization_metadata = {
3841@@ -124,18 +123,17 @@
3842 res = [entity_maker(context, vol) for vol in limited_list]
3843 return {'volumes': res}
3844
3845- def create(self, req):
3846+ def create(self, req, body):
3847 """Creates a new volume."""
3848 context = req.environ['nova.context']
3849
3850- env = self._deserialize(req.body, req.get_content_type())
3851- if not env:
3852+ if not body:
3853 return faults.Fault(exc.HTTPUnprocessableEntity())
3854
3855- vol = env['volume']
3856+ vol = body['volume']
3857 size = vol['size']
3858 LOG.audit(_("Create volume of %s GB"), size, context=context)
3859- new_volume = self.volume_api.create(context, size,
3860+ new_volume = self.volume_api.create(context, size, None,
3861 vol.get('display_name'),
3862 vol.get('display_description'))
3863
3864@@ -175,7 +173,7 @@
3865 return d
3866
3867
3868-class VolumeAttachmentController(wsgi.Controller):
3869+class VolumeAttachmentController(object):
3870 """The volume attachment API controller for the Openstack API.
3871
3872 A child resource of the server. Note that we use the volume id
3873@@ -219,17 +217,16 @@
3874 return {'volumeAttachment': _translate_attachment_detail_view(context,
3875 vol)}
3876
3877- def create(self, req, server_id):
3878+ def create(self, req, server_id, body):
3879 """Attach a volume to an instance."""
3880 context = req.environ['nova.context']
3881
3882- env = self._deserialize(req.body, req.get_content_type())
3883- if not env:
3884+ if not body:
3885 return faults.Fault(exc.HTTPUnprocessableEntity())
3886
3887 instance_id = server_id
3888- volume_id = env['volumeAttachment']['volumeId']
3889- device = env['volumeAttachment']['device']
3890+ volume_id = body['volumeAttachment']['volumeId']
3891+ device = body['volumeAttachment']['device']
3892
3893 msg = _("Attach volume %(volume_id)s to instance %(server_id)s"
3894 " at %(device)s") % locals()
3895@@ -259,7 +256,7 @@
3896 # TODO(justinsb): How do I return "accepted" here?
3897 return {'volumeAttachment': attachment}
3898
3899- def update(self, _req, _server_id, _id):
3900+ def update(self, req, server_id, id, body):
3901 """Update a volume attachment. We don't currently support this."""
3902 return faults.Fault(exc.HTTPBadRequest())
3903
3904@@ -304,7 +301,7 @@
3905 return "Volumes"
3906
3907 def get_alias(self):
3908- return "VOLUMES"
3909+ return "os-volumes"
3910
3911 def get_description(self):
3912 return "Volumes support"
3913@@ -320,12 +317,12 @@
3914
3915 # NOTE(justinsb): No way to provide singular name ('volume')
3916 # Does this matter?
3917- res = extensions.ResourceExtension('volumes',
3918+ res = extensions.ResourceExtension('os-volumes',
3919 VolumeController(),
3920 collection_actions={'detail': 'GET'})
3921 resources.append(res)
3922
3923- res = extensions.ResourceExtension('volume_attachments',
3924+ res = extensions.ResourceExtension('os-volume_attachments',
3925 VolumeAttachmentController(),
3926 parent=dict(
3927 member_name='server',
3928
3929=== added file 'nova/api/openstack/create_instance_helper.py'
3930--- nova/api/openstack/create_instance_helper.py 1970-01-01 00:00:00 +0000
3931+++ nova/api/openstack/create_instance_helper.py 2011-07-14 20:24:25 +0000
3932@@ -0,0 +1,354 @@
3933+# Copyright 2011 OpenStack LLC.
3934+# All Rights Reserved.
3935+#
3936+# Licensed under the Apache License, Version 2.0 (the "License"); you may
3937+# not use this file except in compliance with the License. You may obtain
3938+# a copy of the License at
3939+#
3940+# http://www.apache.org/licenses/LICENSE-2.0
3941+#
3942+# Unless required by applicable law or agreed to in writing, software
3943+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3944+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3945+# License for the specific language governing permissions and limitations
3946+# under the License.
3947+
3948+import base64
3949+import re
3950+import webob
3951+
3952+from webob import exc
3953+from xml.dom import minidom
3954+
3955+from nova import exception
3956+from nova import flags
3957+from nova import log as logging
3958+import nova.image
3959+from nova import quota
3960+from nova import utils
3961+
3962+from nova.compute import instance_types
3963+from nova.api.openstack import faults
3964+from nova.api.openstack import wsgi
3965+from nova.auth import manager as auth_manager
3966+
3967+
3968+LOG = logging.getLogger('nova.api.openstack.create_instance_helper')
3969+FLAGS = flags.FLAGS
3970+
3971+
3972+class CreateFault(exception.NovaException):
3973+ message = _("Invalid parameters given to create_instance.")
3974+
3975+ def __init__(self, fault):
3976+ self.fault = fault
3977+ super(CreateFault, self).__init__()
3978+
3979+
3980+class CreateInstanceHelper(object):
3981+ """This is the base class for OS API Controllers that
3982+ are capable of creating instances (currently Servers and Zones).
3983+
3984+ Once we stabilize the Zones portion of the API we may be able
3985+ to move this code back into servers.py
3986+ """
3987+
3988+ def __init__(self, controller):
3989+ """We need the image service to create an instance."""
3990+ self.controller = controller
3991+ self._image_service = utils.import_object(FLAGS.image_service)
3992+ super(CreateInstanceHelper, self).__init__()
3993+
3994+ def create_instance(self, req, body, create_method):
3995+ """Creates a new server for the given user. The approach
3996+ used depends on the create_method. For example, the standard
3997+ POST /server call uses compute.api.create(), while
3998+ POST /zones/server uses compute.api.create_all_at_once().
3999+
4000+ The problem is, both approaches return different values (i.e.
4001+ [instance dicts] vs. reservation_id). So the handling of the
4002+ return type from this method is left to the caller.
4003+ """
4004+ if not body:
4005+ raise faults.Fault(exc.HTTPUnprocessableEntity())
4006+
4007+ context = req.environ['nova.context']
4008+
4009+ password = self.controller._get_server_admin_password(body['server'])
4010+
4011+ key_name = None
4012+ key_data = None
4013+ key_pairs = auth_manager.AuthManager.get_key_pairs(context)
4014+ if key_pairs:
4015+ key_pair = key_pairs[0]
4016+ key_name = key_pair['name']
4017+ key_data = key_pair['public_key']
4018+
4019+ image_href = self.controller._image_ref_from_req_data(body)
4020+ try:
4021+ image_service, image_id = nova.image.get_image_service(image_href)
4022+ kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
4023+ req, image_id)
4024+ images = set([str(x['id']) for x in image_service.index(context)])
4025+ assert str(image_id) in images
4026+ except Exception, e:
4027+ msg = _("Cannot find requested image %(image_href)s: %(e)s" %
4028+ locals())
4029+ raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
4030+
4031+ personality = body['server'].get('personality')
4032+
4033+ injected_files = []
4034+ if personality:
4035+ injected_files = self._get_injected_files(personality)
4036+
4037+ flavor_id = self.controller._flavor_id_from_req_data(body)
4038+
4039+ if not 'name' in body['server']:
4040+ msg = _("Server name is not defined")
4041+ raise exc.HTTPBadRequest(explanation=msg)
4042+
4043+ zone_blob = body['server'].get('blob')
4044+ name = body['server']['name']
4045+ self._validate_server_name(name)
4046+ name = name.strip()
4047+
4048+ reservation_id = body['server'].get('reservation_id')
4049+ min_count = body['server'].get('min_count')
4050+ max_count = body['server'].get('max_count')
4051+ # min_count and max_count are optional. If they exist, they come
4052+ # in as strings. We want to default 'min_count' to 1, and default
4053+ # 'max_count' to be 'min_count'.
4054+ min_count = int(min_count) if min_count else 1
4055+ max_count = int(max_count) if max_count else min_count
4056+ if min_count > max_count:
4057+ min_count = max_count
4058+
4059+ try:
4060+ inst_type = \
4061+ instance_types.get_instance_type_by_flavor_id(flavor_id)
4062+ extra_values = {
4063+ 'instance_type': inst_type,
4064+ 'image_ref': image_href,
4065+ 'password': password}
4066+
4067+ return (extra_values,
4068+ create_method(context,
4069+ inst_type,
4070+ image_id,
4071+ kernel_id=kernel_id,
4072+ ramdisk_id=ramdisk_id,
4073+ display_name=name,
4074+ display_description=name,
4075+ key_name=key_name,
4076+ key_data=key_data,
4077+ metadata=body['server'].get('metadata', {}),
4078+ injected_files=injected_files,
4079+ admin_password=password,
4080+ zone_blob=zone_blob,
4081+ reservation_id=reservation_id,
4082+ min_count=min_count,
4083+ max_count=max_count))
4084+ except quota.QuotaError as error:
4085+ self._handle_quota_error(error)
4086+ except exception.ImageNotFound as error:
4087+ msg = _("Can not find requested image")
4088+ raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
4089+
4090+ # Let the caller deal with unhandled exceptions.
4091+
4092+ def _handle_quota_error(self, error):
4093+ """
4094+ Reraise quota errors as api-specific http exceptions
4095+ """
4096+ if error.code == "OnsetFileLimitExceeded":
4097+ expl = _("Personality file limit exceeded")
4098+ raise exc.HTTPBadRequest(explanation=expl)
4099+ if error.code == "OnsetFilePathLimitExceeded":
4100+ expl = _("Personality file path too long")
4101+ raise exc.HTTPBadRequest(explanation=expl)
4102+ if error.code == "OnsetFileContentLimitExceeded":
4103+ expl = _("Personality file content too long")
4104+ raise exc.HTTPBadRequest(explanation=expl)
4105+ # if the original error is okay, just reraise it
4106+ raise error
4107+
4108+ def _deserialize_create(self, request):
4109+ """
4110+ Deserialize a create request
4111+
4112+ Overrides normal behavior in the case of xml content
4113+ """
4114+ if request.content_type == "application/xml":
4115+ deserializer = ServerCreateRequestXMLDeserializer()
4116+ return deserializer.deserialize(request.body)
4117+ else:
4118+ return self._deserialize(request.body, request.get_content_type())
4119+
4120+ def _validate_server_name(self, value):
4121+ if not isinstance(value, basestring):
4122+ msg = _("Server name is not a string or unicode")
4123+ raise exc.HTTPBadRequest(explanation=msg)
4124+
4125+ if value.strip() == '':
4126+ msg = _("Server name is an empty string")
4127+ raise exc.HTTPBadRequest(explanation=msg)
4128+
4129+ def _get_kernel_ramdisk_from_image(self, req, image_id):
4130+ """Fetch an image from the ImageService, then if present, return the
4131+ associated kernel and ramdisk image IDs.
4132+ """
4133+ context = req.environ['nova.context']
4134+ image_meta = self._image_service.show(context, image_id)
4135+ # NOTE(sirp): extracted to a separate method to aid unit-testing, the
4136+ # new method doesn't need a request obj or an ImageService stub
4137+ kernel_id, ramdisk_id = self._do_get_kernel_ramdisk_from_image(
4138+ image_meta)
4139+ return kernel_id, ramdisk_id
4140+
4141+ @staticmethod
4142+ def _do_get_kernel_ramdisk_from_image(image_meta):
4143+ """Given an ImageService image_meta, return kernel and ramdisk image
4144+ ids if present.
4145+
4146+ This is only valid for `ami` style images.
4147+ """
4148+ image_id = image_meta['id']
4149+ if image_meta['status'] != 'active':
4150+ raise exception.ImageUnacceptable(image_id=image_id,
4151+ reason=_("status is not active"))
4152+
4153+ if image_meta.get('container_format') != 'ami':
4154+ return None, None
4155+
4156+ try:
4157+ kernel_id = image_meta['properties']['kernel_id']
4158+ except KeyError:
4159+ raise exception.KernelNotFoundForImage(image_id=image_id)
4160+
4161+ try:
4162+ ramdisk_id = image_meta['properties']['ramdisk_id']
4163+ except KeyError:
4164+ raise exception.RamdiskNotFoundForImage(image_id=image_id)
4165+
4166+ return kernel_id, ramdisk_id
4167+
4168+ def _get_injected_files(self, personality):
4169+ """
4170+ Create a list of injected files from the personality attribute
4171+
4172+ At this time, injected_files must be formatted as a list of
4173+ (file_path, file_content) pairs for compatibility with the
4174+ underlying compute service.
4175+ """
4176+ injected_files = []
4177+
4178+ for item in personality:
4179+ try:
4180+ path = item['path']
4181+ contents = item['contents']
4182+ except KeyError as key:
4183+ expl = _('Bad personality format: missing %s') % key
4184+ raise exc.HTTPBadRequest(explanation=expl)
4185+ except TypeError:
4186+ expl = _('Bad personality format')
4187+ raise exc.HTTPBadRequest(explanation=expl)
4188+ try:
4189+ contents = base64.b64decode(contents)
4190+ except TypeError:
4191+ expl = _('Personality content for %s cannot be decoded') % path
4192+ raise exc.HTTPBadRequest(explanation=expl)
4193+ injected_files.append((path, contents))
4194+ return injected_files
4195+
4196+ def _get_server_admin_password_old_style(self, server):
4197+ """ Determine the admin password for a server on creation """
4198+ return utils.generate_password(16)
4199+
4200+ def _get_server_admin_password_new_style(self, server):
4201+ """ Determine the admin password for a server on creation """
4202+ password = server.get('adminPass')
4203+
4204+ if password is None:
4205+ return utils.generate_password(16)
4206+ if not isinstance(password, basestring) or password == '':
4207+ msg = _("Invalid adminPass")
4208+ raise exc.HTTPBadRequest(explanation=msg)
4209+ return password
4210+
4211+
4212+class ServerXMLDeserializer(wsgi.XMLDeserializer):
4213+ """
4214+ Deserializer to handle xml-formatted server create requests.
4215+
4216+ Handles standard server attributes as well as optional metadata
4217+ and personality attributes
4218+ """
4219+
4220+ def create(self, string):
4221+ """Deserialize an xml-formatted server create request"""
4222+ dom = minidom.parseString(string)
4223+ server = self._extract_server(dom)
4224+ return {'body': {'server': server}}
4225+
4226+ def _extract_server(self, node):
4227+ """Marshal the server attribute of a parsed request"""
4228+ server = {}
4229+ server_node = self._find_first_child_named(node, 'server')
4230+ for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]:
4231+ if server_node.getAttribute(attr):
4232+ server[attr] = server_node.getAttribute(attr)
4233+ metadata = self._extract_metadata(server_node)
4234+ if metadata is not None:
4235+ server["metadata"] = metadata
4236+ personality = self._extract_personality(server_node)
4237+ if personality is not None:
4238+ server["personality"] = personality
4239+ return server
4240+
4241+ def _extract_metadata(self, server_node):
4242+ """Marshal the metadata attribute of a parsed request"""
4243+ metadata_node = self._find_first_child_named(server_node, "metadata")
4244+ if metadata_node is None:
4245+ return None
4246+ metadata = {}
4247+ for meta_node in self._find_children_named(metadata_node, "meta"):
4248+ key = meta_node.getAttribute("key")
4249+ metadata[key] = self._extract_text(meta_node)
4250+ return metadata
4251+
4252+ def _extract_personality(self, server_node):
4253+ """Marshal the personality attribute of a parsed request"""
4254+ personality_node = \
4255+ self._find_first_child_named(server_node, "personality")
4256+ if personality_node is None:
4257+ return None
4258+ personality = []
4259+ for file_node in self._find_children_named(personality_node, "file"):
4260+ item = {}
4261+ if file_node.hasAttribute("path"):
4262+ item["path"] = file_node.getAttribute("path")
4263+ item["contents"] = self._extract_text(file_node)
4264+ personality.append(item)
4265+ return personality
4266+
4267+ def _find_first_child_named(self, parent, name):
4268+ """Search a nodes children for the first child with a given name"""
4269+ for node in parent.childNodes:
4270+ if node.nodeName == name:
4271+ return node
4272+ return None
4273+
4274+ def _find_children_named(self, parent, name):
4275+ """Return all of a nodes children who have the given name"""
4276+ for node in parent.childNodes:
4277+ if node.nodeName == name:
4278+ yield node
4279+
4280+ def _extract_text(self, node):
4281+ """Get the text field contained by the given node"""
4282+ if len(node.childNodes) == 1:
4283+ child = node.childNodes[0]
4284+ if child.nodeType == child.TEXT_NODE:
4285+ return child.nodeValue
4286+ return ""
4287
4288=== modified file 'nova/api/openstack/extensions.py'
4289--- nova/api/openstack/extensions.py 2011-05-17 03:14:51 +0000
4290+++ nova/api/openstack/extensions.py 2011-07-14 20:24:25 +0000
4291@@ -27,9 +27,10 @@
4292 from nova import exception
4293 from nova import flags
4294 from nova import log as logging
4295-from nova import wsgi
4296+from nova import wsgi as base_wsgi
4297 from nova.api.openstack import common
4298 from nova.api.openstack import faults
4299+from nova.api.openstack import wsgi
4300
4301
4302 LOG = logging.getLogger('extensions')
4303@@ -115,28 +116,34 @@
4304 return request_exts
4305
4306
4307-class ActionExtensionController(common.OpenstackController):
4308-
4309+class ActionExtensionController(object):
4310 def __init__(self, application):
4311-
4312 self.application = application
4313 self.action_handlers = {}
4314
4315 def add_action(self, action_name, handler):
4316 self.action_handlers[action_name] = handler
4317
4318- def action(self, req, id):
4319-
4320- input_dict = self._deserialize(req.body, req.get_content_type())
4321+ def action(self, req, id, body):
4322 for action_name, handler in self.action_handlers.iteritems():
4323- if action_name in input_dict:
4324- return handler(input_dict, req, id)
4325+ if action_name in body:
4326+ return handler(body, req, id)
4327 # no action handler found (bump to downstream application)
4328 res = self.application
4329 return res
4330
4331
4332-class RequestExtensionController(common.OpenstackController):
4333+class ActionExtensionResource(wsgi.Resource):
4334+
4335+ def __init__(self, application):
4336+ controller = ActionExtensionController(application)
4337+ wsgi.Resource.__init__(self, controller)
4338+
4339+ def add_action(self, action_name, handler):
4340+ self.controller.add_action(action_name, handler)
4341+
4342+
4343+class RequestExtensionController(object):
4344
4345 def __init__(self, application):
4346 self.application = application
4347@@ -153,7 +160,17 @@
4348 return res
4349
4350
4351-class ExtensionController(common.OpenstackController):
4352+class RequestExtensionResource(wsgi.Resource):
4353+
4354+ def __init__(self, application):
4355+ controller = RequestExtensionController(application)
4356+ wsgi.Resource.__init__(self, controller)
4357+
4358+ def add_handler(self, handler):
4359+ self.controller.add_handler(handler)
4360+
4361+
4362+class ExtensionsResource(wsgi.Resource):
4363
4364 def __init__(self, extension_manager):
4365 self.extension_manager = extension_manager
4366@@ -186,7 +203,7 @@
4367 raise faults.Fault(webob.exc.HTTPNotFound())
4368
4369
4370-class ExtensionMiddleware(wsgi.Middleware):
4371+class ExtensionMiddleware(base_wsgi.Middleware):
4372 """Extensions middleware for WSGI."""
4373 @classmethod
4374 def factory(cls, global_config, **local_config):
4375@@ -195,43 +212,43 @@
4376 return cls(app, **local_config)
4377 return _factory
4378
4379- def _action_ext_controllers(self, application, ext_mgr, mapper):
4380- """Return a dict of ActionExtensionController-s by collection."""
4381- action_controllers = {}
4382+ def _action_ext_resources(self, application, ext_mgr, mapper):
4383+ """Return a dict of ActionExtensionResource-s by collection."""
4384+ action_resources = {}
4385 for action in ext_mgr.get_actions():
4386- if not action.collection in action_controllers.keys():
4387- controller = ActionExtensionController(application)
4388+ if not action.collection in action_resources.keys():
4389+ resource = ActionExtensionResource(application)
4390 mapper.connect("/%s/:(id)/action.:(format)" %
4391 action.collection,
4392 action='action',
4393- controller=controller,
4394+ controller=resource,
4395 conditions=dict(method=['POST']))
4396 mapper.connect("/%s/:(id)/action" % action.collection,
4397 action='action',
4398- controller=controller,
4399+ controller=resource,
4400 conditions=dict(method=['POST']))
4401- action_controllers[action.collection] = controller
4402-
4403- return action_controllers
4404-
4405- def _request_ext_controllers(self, application, ext_mgr, mapper):
4406- """Returns a dict of RequestExtensionController-s by collection."""
4407- request_ext_controllers = {}
4408+ action_resources[action.collection] = resource
4409+
4410+ return action_resources
4411+
4412+ def _request_ext_resources(self, application, ext_mgr, mapper):
4413+ """Returns a dict of RequestExtensionResource-s by collection."""
4414+ request_ext_resources = {}
4415 for req_ext in ext_mgr.get_request_extensions():
4416- if not req_ext.key in request_ext_controllers.keys():
4417- controller = RequestExtensionController(application)
4418+ if not req_ext.key in request_ext_resources.keys():
4419+ resource = RequestExtensionResource(application)
4420 mapper.connect(req_ext.url_route + '.:(format)',
4421 action='process',
4422- controller=controller,
4423+ controller=resource,
4424 conditions=req_ext.conditions)
4425
4426 mapper.connect(req_ext.url_route,
4427 action='process',
4428- controller=controller,
4429+ controller=resource,
4430 conditions=req_ext.conditions)
4431- request_ext_controllers[req_ext.key] = controller
4432+ request_ext_resources[req_ext.key] = resource
4433
4434- return request_ext_controllers
4435+ return request_ext_resources
4436
4437 def __init__(self, application, ext_mgr=None):
4438
4439@@ -246,22 +263,22 @@
4440 LOG.debug(_('Extended resource: %s'),
4441 resource.collection)
4442 mapper.resource(resource.collection, resource.collection,
4443- controller=resource.controller,
4444+ controller=wsgi.Resource(resource.controller),
4445 collection=resource.collection_actions,
4446 member=resource.member_actions,
4447 parent_resource=resource.parent)
4448
4449 # extended actions
4450- action_controllers = self._action_ext_controllers(application, ext_mgr,
4451+ action_resources = self._action_ext_resources(application, ext_mgr,
4452 mapper)
4453 for action in ext_mgr.get_actions():
4454 LOG.debug(_('Extended action: %s'), action.action_name)
4455- controller = action_controllers[action.collection]
4456- controller.add_action(action.action_name, action.handler)
4457+ resource = action_resources[action.collection]
4458+ resource.add_action(action.action_name, action.handler)
4459
4460 # extended requests
4461- req_controllers = self._request_ext_controllers(application, ext_mgr,
4462- mapper)
4463+ req_controllers = self._request_ext_resources(application, ext_mgr,
4464+ mapper)
4465 for request_ext in ext_mgr.get_request_extensions():
4466 LOG.debug(_('Extended request: %s'), request_ext.key)
4467 controller = req_controllers[request_ext.key]
4468@@ -313,7 +330,7 @@
4469 """Returns a list of ResourceExtension objects."""
4470 resources = []
4471 resources.append(ResourceExtension('extensions',
4472- ExtensionController(self)))
4473+ ExtensionsResource(self)))
4474 for alias, ext in self.extensions.iteritems():
4475 try:
4476 resources.extend(ext.get_resources())
4477@@ -357,6 +374,8 @@
4478 LOG.debug(_('Ext updated: %s'), extension.get_updated())
4479 except AttributeError as ex:
4480 LOG.exception(_("Exception loading extension: %s"), unicode(ex))
4481+ return False
4482+ return True
4483
4484 def _load_all_extensions(self):
4485 """Load extensions from the configured path.
4486@@ -395,22 +414,23 @@
4487 'file': ext_path})
4488 continue
4489 new_ext = new_ext_class()
4490- self._check_extension(new_ext)
4491- self._add_extension(new_ext)
4492-
4493- def _add_extension(self, ext):
4494+ self.add_extension(new_ext)
4495+
4496+ def add_extension(self, ext):
4497+ # Do nothing if the extension doesn't check out
4498+ if not self._check_extension(ext):
4499+ return
4500+
4501 alias = ext.get_alias()
4502 LOG.audit(_('Loaded extension: %s'), alias)
4503
4504- self._check_extension(ext)
4505-
4506 if alias in self.extensions:
4507 raise exception.Error("Found duplicate extension: %s" % alias)
4508 self.extensions[alias] = ext
4509
4510
4511 class RequestExtension(object):
4512- """Extend requests and responses of core nova OpenStack API controllers.
4513+ """Extend requests and responses of core nova OpenStack API resources.
4514
4515 Provide a way to add data to responses and handle custom request data
4516 that is sent to core nova OpenStack API controllers.
4517@@ -424,7 +444,7 @@
4518
4519
4520 class ActionExtension(object):
4521- """Add custom actions to core nova OpenStack API controllers."""
4522+ """Add custom actions to core nova OpenStack API resources."""
4523
4524 def __init__(self, collection, action_name, handler):
4525 self.collection = collection
4526
4527=== modified file 'nova/api/openstack/faults.py'
4528--- nova/api/openstack/faults.py 2011-04-07 18:55:42 +0000
4529+++ nova/api/openstack/faults.py 2011-07-14 20:24:25 +0000
4530@@ -19,8 +19,7 @@
4531 import webob.dec
4532 import webob.exc
4533
4534-from nova import wsgi
4535-from nova.api.openstack import common
4536+from nova.api.openstack import wsgi
4537
4538
4539 class Fault(webob.exc.HTTPException):
4540@@ -55,13 +54,21 @@
4541 if code == 413:
4542 retry = self.wrapped_exc.headers['Retry-After']
4543 fault_data[fault_name]['retryAfter'] = retry
4544+
4545 # 'code' is an attribute on the fault tag itself
4546- metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}
4547- default_xmlns = common.XML_NS_V10
4548- serializer = wsgi.Serializer(metadata, default_xmlns)
4549+ metadata = {'attributes': {fault_name: 'code'}}
4550+
4551 content_type = req.best_match_content_type()
4552- self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
4553+
4554+ serializer = {
4555+ 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
4556+ xmlns=wsgi.XMLNS_V10),
4557+ 'application/json': wsgi.JSONDictSerializer(),
4558+ }[content_type]
4559+
4560+ self.wrapped_exc.body = serializer.serialize(fault_data)
4561 self.wrapped_exc.content_type = content_type
4562+
4563 return self.wrapped_exc
4564
4565
4566@@ -70,14 +77,6 @@
4567 Rate-limited request response.
4568 """
4569
4570- _serialization_metadata = {
4571- "application/xml": {
4572- "attributes": {
4573- "overLimitFault": "code",
4574- },
4575- },
4576- }
4577-
4578 def __init__(self, message, details, retry_time):
4579 """
4580 Initialize new `OverLimitFault` with relevant information.
4581@@ -97,8 +96,16 @@
4582 Return the wrapped exception with a serialized body conforming to our
4583 error format.
4584 """
4585- serializer = wsgi.Serializer(self._serialization_metadata)
4586 content_type = request.best_match_content_type()
4587- content = serializer.serialize(self.content, content_type)
4588+ metadata = {"attributes": {"overLimitFault": "code"}}
4589+
4590+ serializer = {
4591+ 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
4592+ xmlns=wsgi.XMLNS_V10),
4593+ 'application/json': wsgi.JSONDictSerializer(),
4594+ }[content_type]
4595+
4596+ content = serializer.serialize(self.content)
4597 self.wrapped_exc.body = content
4598+
4599 return self.wrapped_exc
4600
4601=== modified file 'nova/api/openstack/flavors.py'
4602--- nova/api/openstack/flavors.py 2011-05-06 20:13:35 +0000
4603+++ nova/api/openstack/flavors.py 2011-07-14 20:24:25 +0000
4604@@ -19,22 +19,13 @@
4605
4606 from nova import db
4607 from nova import exception
4608-from nova.api.openstack import common
4609 from nova.api.openstack import views
4610-
4611-
4612-class Controller(common.OpenstackController):
4613+from nova.api.openstack import wsgi
4614+
4615+
4616+class Controller(object):
4617 """Flavor controller for the OpenStack API."""
4618
4619- _serialization_metadata = {
4620- 'application/xml': {
4621- "attributes": {
4622- "flavor": ["id", "name", "ram", "disk"],
4623- "link": ["rel", "type", "href"],
4624- }
4625- }
4626- }
4627-
4628 def index(self, req):
4629 """Return all flavors in brief."""
4630 items = self._get_flavors(req, is_detail=False)
4631@@ -71,14 +62,33 @@
4632
4633
4634 class ControllerV10(Controller):
4635+
4636 def _get_view_builder(self, req):
4637 return views.flavors.ViewBuilder()
4638
4639
4640 class ControllerV11(Controller):
4641+
4642 def _get_view_builder(self, req):
4643 base_url = req.application_url
4644 return views.flavors.ViewBuilderV11(base_url)
4645
4646- def get_default_xmlns(self, req):
4647- return common.XML_NS_V11
4648+
4649+def create_resource(version='1.0'):
4650+ controller = {
4651+ '1.0': ControllerV10,
4652+ '1.1': ControllerV11,
4653+ }[version]()
4654+
4655+ xmlns = {
4656+ '1.0': wsgi.XMLNS_V10,
4657+ '1.1': wsgi.XMLNS_V11,
4658+ }[version]
4659+
4660+ body_serializers = {
4661+ 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns),
4662+ }
4663+
4664+ serializer = wsgi.ResponseSerializer(body_serializers)
4665+
4666+ return wsgi.Resource(controller, serializer=serializer)
4667
4668=== modified file 'nova/api/openstack/image_metadata.py'
4669--- nova/api/openstack/image_metadata.py 2011-04-08 07:58:48 +0000
4670+++ nova/api/openstack/image_metadata.py 2011-07-14 20:24:25 +0000
4671@@ -16,24 +16,24 @@
4672 # under the License.
4673
4674 from webob import exc
4675+from xml.dom import minidom
4676
4677 from nova import flags
4678+from nova import image
4679 from nova import quota
4680 from nova import utils
4681-from nova import wsgi
4682-from nova.api.openstack import common
4683 from nova.api.openstack import faults
4684+from nova.api.openstack import wsgi
4685
4686
4687 FLAGS = flags.FLAGS
4688
4689
4690-class Controller(common.OpenstackController):
4691+class Controller(object):
4692 """The image metadata API controller for the Openstack API"""
4693
4694 def __init__(self):
4695- self.image_service = utils.import_object(FLAGS.image_service)
4696- super(Controller, self).__init__()
4697+ self.image_service = image.get_default_image_service()
4698
4699 def _get_metadata(self, context, image_id, image=None):
4700 if not image:
4701@@ -60,13 +60,12 @@
4702 context = req.environ['nova.context']
4703 metadata = self._get_metadata(context, image_id)
4704 if id in metadata:
4705- return {id: metadata[id]}
4706+ return {'meta': {id: metadata[id]}}
4707 else:
4708 return faults.Fault(exc.HTTPNotFound())
4709
4710- def create(self, req, image_id):
4711+ def create(self, req, image_id, body):
4712 context = req.environ['nova.context']
4713- body = self._deserialize(req.body, req.get_content_type())
4714 img = self.image_service.show(context, image_id)
4715 metadata = self._get_metadata(context, image_id, img)
4716 if 'metadata' in body:
4717@@ -77,18 +76,24 @@
4718 self.image_service.update(context, image_id, img, None)
4719 return dict(metadata=metadata)
4720
4721- def update(self, req, image_id, id):
4722+ def update(self, req, image_id, id, body):
4723 context = req.environ['nova.context']
4724- body = self._deserialize(req.body, req.get_content_type())
4725- if not id in body:
4726+
4727+ try:
4728+ meta = body['meta']
4729+ except KeyError:
4730+ expl = _('Incorrect request body format')
4731+ raise exc.HTTPBadRequest(explanation=expl)
4732+
4733+ if not id in meta:
4734 expl = _('Request body and URI mismatch')
4735 raise exc.HTTPBadRequest(explanation=expl)
4736- if len(body) > 1:
4737+ if len(meta) > 1:
4738 expl = _('Request body contains too many items')
4739 raise exc.HTTPBadRequest(explanation=expl)
4740 img = self.image_service.show(context, image_id)
4741 metadata = self._get_metadata(context, image_id, img)
4742- metadata[id] = body[id]
4743+ metadata[id] = meta[id]
4744 self._check_quota_limit(context, metadata)
4745 img['properties'] = metadata
4746 self.image_service.update(context, image_id, img, None)
4747@@ -104,3 +109,60 @@
4748 metadata.pop(id)
4749 img['properties'] = metadata
4750 self.image_service.update(context, image_id, img, None)
4751+
4752+
4753+class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer):
4754+ def __init__(self, xmlns=wsgi.XMLNS_V11):
4755+ super(ImageMetadataXMLSerializer, self).__init__(xmlns=xmlns)
4756+
4757+ def _meta_item_to_xml(self, doc, key, value):
4758+ node = doc.createElement('meta')
4759+ doc.appendChild(node)
4760+ node.setAttribute('key', '%s' % key)
4761+ text = doc.createTextNode('%s' % value)
4762+ node.appendChild(text)
4763+ return node
4764+
4765+ def meta_list_to_xml(self, xml_doc, meta_items):
4766+ container_node = xml_doc.createElement('metadata')
4767+ for (key, value) in meta_items:
4768+ item_node = self._meta_item_to_xml(xml_doc, key, value)
4769+ container_node.appendChild(item_node)
4770+ return container_node
4771+
4772+ def _meta_list_to_xml_string(self, metadata_dict):
4773+ xml_doc = minidom.Document()
4774+ items = metadata_dict['metadata'].items()
4775+ container_node = self.meta_list_to_xml(xml_doc, items)
4776+ xml_doc.appendChild(container_node)
4777+ self._add_xmlns(container_node)
4778+ return xml_doc.toprettyxml(indent=' ', encoding='UTF-8')
4779+
4780+ def index(self, metadata_dict):
4781+ return self._meta_list_to_xml_string(metadata_dict)
4782+
4783+ def create(self, metadata_dict):
4784+ return self._meta_list_to_xml_string(metadata_dict)
4785+
4786+ def _meta_item_to_xml_string(self, meta_item_dict):
4787+ xml_doc = minidom.Document()
4788+ item_key, item_value = meta_item_dict.items()[0]
4789+ item_node = self._meta_item_to_xml(xml_doc, item_key, item_value)
4790+ xml_doc.appendChild(item_node)
4791+ self._add_xmlns(item_node)
4792+ return xml_doc.toprettyxml(indent=' ', encoding='UTF-8')
4793+
4794+ def show(self, meta_item_dict):
4795+ return self._meta_item_to_xml_string(meta_item_dict['meta'])
4796+
4797+ def update(self, meta_item_dict):
4798+ return self._meta_item_to_xml_string(meta_item_dict['meta'])
4799+
4800+
4801+def create_resource():
4802+ body_serializers = {
4803+ 'application/xml': ImageMetadataXMLSerializer(),
4804+ }
4805+ serializer = wsgi.ResponseSerializer(body_serializers)
4806+
4807+ return wsgi.Resource(Controller(), serializer=serializer)
4808
4809=== modified file 'nova/api/openstack/images.py'
4810--- nova/api/openstack/images.py 2011-04-21 17:29:11 +0000
4811+++ nova/api/openstack/images.py 2011-07-14 20:24:25 +0000
4812@@ -13,88 +13,73 @@
4813 # License for the specific language governing permissions and limitations
4814 # under the License.
4815
4816+import urlparse
4817+import os.path
4818+
4819 import webob.exc
4820+from xml.dom import minidom
4821
4822 from nova import compute
4823 from nova import exception
4824 from nova import flags
4825+import nova.image
4826 from nova import log
4827-from nova import utils
4828 from nova.api.openstack import common
4829 from nova.api.openstack import faults
4830+from nova.api.openstack import image_metadata
4831+from nova.api.openstack import servers
4832 from nova.api.openstack.views import images as images_view
4833+from nova.api.openstack import wsgi
4834
4835
4836 LOG = log.getLogger('nova.api.openstack.images')
4837 FLAGS = flags.FLAGS
4838
4839-
4840-class Controller(common.OpenstackController):
4841- """Base `wsgi.Controller` for retrieving/displaying images."""
4842-
4843- _serialization_metadata = {
4844- 'application/xml': {
4845- "attributes": {
4846- "image": ["id", "name", "updated", "created", "status",
4847- "serverId", "progress"],
4848- "link": ["rel", "type", "href"],
4849- },
4850- },
4851- }
4852+SUPPORTED_FILTERS = ['name', 'status']
4853+
4854+
4855+class Controller(object):
4856+ """Base controller for retrieving/displaying images."""
4857
4858 def __init__(self, image_service=None, compute_service=None):
4859 """Initialize new `ImageController`.
4860
4861 :param compute_service: `nova.compute.api:API`
4862 :param image_service: `nova.image.service:BaseImageService`
4863+
4864 """
4865- _default_service = utils.import_object(flags.FLAGS.image_service)
4866-
4867 self._compute_service = compute_service or compute.API()
4868- self._image_service = image_service or _default_service
4869-
4870- def index(self, req):
4871- """Return an index listing of images available to the request.
4872-
4873- :param req: `wsgi.Request` object
4874- """
4875- context = req.environ['nova.context']
4876- images = self._image_service.index(context)
4877- images = common.limited(images, req)
4878- builder = self.get_builder(req).build
4879- return dict(images=[builder(image, detail=False) for image in images])
4880-
4881- def detail(self, req):
4882- """Return a detailed index listing of images available to the request.
4883-
4884- :param req: `wsgi.Request` object.
4885- """
4886- context = req.environ['nova.context']
4887- images = self._image_service.detail(context)
4888- images = common.limited(images, req)
4889- builder = self.get_builder(req).build
4890- return dict(images=[builder(image, detail=True) for image in images])
4891+ self._image_service = image_service or \
4892+ nova.image.get_default_image_service()
4893+
4894+ def _get_filters(self, req):
4895+ """
4896+ Return a dictionary of query param filters from the request
4897+
4898+ :param req: the Request object coming from the wsgi layer
4899+ :retval a dict of key/value filters
4900+ """
4901+ filters = {}
4902+ for param in req.str_params:
4903+ if param in SUPPORTED_FILTERS or param.startswith('property-'):
4904+ filters[param] = req.str_params.get(param)
4905+
4906+ return filters
4907
4908 def show(self, req, id):
4909 """Return detailed information about a specific image.
4910
4911 :param req: `wsgi.Request` object
4912- :param id: Image identifier (integer)
4913+ :param id: Image identifier
4914 """
4915 context = req.environ['nova.context']
4916
4917 try:
4918- image_id = int(id)
4919- except ValueError:
4920+ image = self._image_service.show(context, id)
4921+ except (exception.NotFound, exception.InvalidImageRef):
4922 explanation = _("Image not found.")
4923 raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation))
4924
4925- try:
4926- image = self._image_service.show(context, image_id)
4927- except exception.NotFound:
4928- explanation = _("Image '%d' not found.") % (image_id)
4929- raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation))
4930-
4931 return dict(image=self.get_builder(req).build(image, detail=True))
4932
4933 def delete(self, req, id):
4934@@ -103,35 +88,78 @@
4935 :param req: `wsgi.Request` object
4936 :param id: Image identifier (integer)
4937 """
4938- image_id = id
4939 context = req.environ['nova.context']
4940- self._image_service.delete(context, image_id)
4941+ self._image_service.delete(context, id)
4942 return webob.exc.HTTPNoContent()
4943
4944- def create(self, req):
4945- """Snapshot a server instance and save the image.
4946+ def create(self, req, body):
4947+ """Snapshot or backup a server instance and save the image.
4948+
4949+ Images now have an `image_type` associated with them, which can be
4950+ 'snapshot' or the backup type, like 'daily' or 'weekly'.
4951+
4952+ If the image_type is backup-like, then the rotation factor can be
4953+ included and that will cause the oldest backups that exceed the
4954+ rotation factor to be deleted.
4955
4956 :param req: `wsgi.Request` object
4957 """
4958+ def get_param(param):
4959+ try:
4960+ return body["image"][param]
4961+ except KeyError:
4962+ raise webob.exc.HTTPBadRequest(explanation="Missing required "
4963+ "param: %s" % param)
4964+
4965 context = req.environ['nova.context']
4966 content_type = req.get_content_type()
4967- image = self._deserialize(req.body, content_type)
4968
4969- if not image:
4970+ if not body:
4971 raise webob.exc.HTTPBadRequest()
4972
4973+ image_type = body["image"].get("image_type", "snapshot")
4974+
4975 try:
4976- server_id = image["image"]["serverId"]
4977- image_name = image["image"]["name"]
4978+ server_id = self._server_id_from_req(req, body)
4979 except KeyError:
4980 raise webob.exc.HTTPBadRequest()
4981
4982- image = self._compute_service.snapshot(context, server_id, image_name)
4983+ image_name = get_param("name")
4984+ props = self._get_extra_properties(req, body)
4985+
4986+ if image_type == "snapshot":
4987+ image = self._compute_service.snapshot(
4988+ context, server_id, image_name,
4989+ extra_properties=props)
4990+ elif image_type == "backup":
4991+ # NOTE(sirp): Unlike snapshot, backup is not a customer facing
4992+ # API call; rather, it's used by the internal backup scheduler
4993+ if not FLAGS.allow_admin_api:
4994+ raise webob.exc.HTTPBadRequest(
4995+ explanation="Admin API Required")
4996+
4997+ backup_type = get_param("backup_type")
4998+ rotation = int(get_param("rotation"))
4999+
5000+ image = self._compute_service.backup(
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: