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
=== modified file '.mailmap'
--- .mailmap 2011-05-11 19:16:37 +0000
+++ .mailmap 2011-07-14 20:24:25 +0000
@@ -47,3 +47,8 @@
47<vishvananda@gmail.com> <root@mirror.nasanebula.net>47<vishvananda@gmail.com> <root@mirror.nasanebula.net>
48<vishvananda@gmail.com> <root@ubuntu>48<vishvananda@gmail.com> <root@ubuntu>
49<vishvananda@gmail.com> <vishvananda@yahoo.com>49<vishvananda@gmail.com> <vishvananda@yahoo.com>
50<ilyaalekseyev@acm.org> <ialekseev@griddynamics.com>
51<ilyaalekseyev@acm.org> <ilya@oscloud.ru>
52<reldan@oscloud.ru> <enugaev@griddynamics.com>
53<kshileev@gmail.com> <kshileev@griddynamics.com>
54<nsokolov@griddynamics.com> <nsokolov@griddynamics.net>
5055
=== modified file 'Authors'
--- Authors 2011-05-20 06:51:29 +0000
+++ Authors 2011-07-14 20:24:25 +0000
@@ -1,4 +1,6 @@
1Alex Meade <alex.meade@rackspace.com>1Alex Meade <alex.meade@rackspace.com>
2Alexander Sakhnov <asakhnov@mirantis.com>
3Andrey Brindeyev <abrindeyev@griddynamics.com>
2Andy Smith <code@term.ie>4Andy Smith <code@term.ie>
3Andy Southgate <andy.southgate@citrix.com>5Andy Southgate <andy.southgate@citrix.com>
4Anne Gentle <anne@openstack.org>6Anne Gentle <anne@openstack.org>
@@ -16,18 +18,22 @@
16Chuck Short <zulcss@ubuntu.com>18Chuck Short <zulcss@ubuntu.com>
17Cory Wright <corywright@gmail.com>19Cory Wright <corywright@gmail.com>
18Dan Prince <dan.prince@rackspace.com>20Dan Prince <dan.prince@rackspace.com>
21Dave Walker <DaveWalker@ubuntu.com>
19David Pravec <David.Pravec@danix.org>22David Pravec <David.Pravec@danix.org>
20Dean Troyer <dtroyer@gmail.com>23Dean Troyer <dtroyer@gmail.com>
24Devendra Modium <dmodium@isi.edu>
21Devin Carlen <devin.carlen@gmail.com>25Devin Carlen <devin.carlen@gmail.com>
22Ed Leafe <ed@leafe.com>26Ed Leafe <ed@leafe.com>
23Eldar Nugaev <enugaev@griddynamics.com>27Eldar Nugaev <reldan@oscloud.ru>
24Eric Day <eday@oddments.org>28Eric Day <eday@oddments.org>
25Eric Windisch <eric@cloudscaling.com>29Eric Windisch <eric@cloudscaling.com>
26Ewan Mellor <ewan.mellor@citrix.com>30Ewan Mellor <ewan.mellor@citrix.com>
27Gabe Westmaas <gabe.westmaas@rackspace.com>31Gabe Westmaas <gabe.westmaas@rackspace.com>
28Hisaharu Ishii <ishii.hisaharu@lab.ntt.co.jp>32Hisaharu Ishii <ishii.hisaharu@lab.ntt.co.jp>
29Hisaki Ohara <hisaki.ohara@intel.com>33Hisaki Ohara <hisaki.ohara@intel.com>
30Ilya Alekseyev <ialekseev@griddynamics.com>34Ilya Alekseyev <ilyaalekseyev@acm.org>
35Isaku Yamahata <yamahata@valinux.co.jp>
36Jason Cannavale <jason.cannavale@rackspace.com>
31Jason Koelker <jason@koelker.net>37Jason Koelker <jason@koelker.net>
32Jay Pipes <jaypipes@gmail.com>38Jay Pipes <jaypipes@gmail.com>
33Jesse Andrews <anotherjesse@gmail.com>39Jesse Andrews <anotherjesse@gmail.com>
@@ -39,6 +45,7 @@
39John Tran <jtran@attinteractive.com>45John Tran <jtran@attinteractive.com>
40Jonathan Bryce <jbryce@jbryce.com>46Jonathan Bryce <jbryce@jbryce.com>
41Jordan Rinke <jordan@openstack.org>47Jordan Rinke <jordan@openstack.org>
48Joseph Suh <jsuh@isi.edu>
42Josh Durgin <joshd@hq.newdream.net>49Josh Durgin <joshd@hq.newdream.net>
43Josh Kearney <josh@jk0.org>50Josh Kearney <josh@jk0.org>
44Josh Kleinpeter <josh@kleinpeter.org>51Josh Kleinpeter <josh@kleinpeter.org>
@@ -49,6 +56,7 @@
49Ken Pepple <ken.pepple@gmail.com>56Ken Pepple <ken.pepple@gmail.com>
50Kevin Bringard <kbringard@attinteractive.com>57Kevin Bringard <kbringard@attinteractive.com>
51Kevin L. Mitchell <kevin.mitchell@rackspace.com>58Kevin L. Mitchell <kevin.mitchell@rackspace.com>
59Kirill Shileev <kshileev@gmail.com>
52Koji Iida <iida.koji@lab.ntt.co.jp>60Koji Iida <iida.koji@lab.ntt.co.jp>
53Lorin Hochstein <lorin@isi.edu>61Lorin Hochstein <lorin@isi.edu>
54Lvov Maxim <usrleon@gmail.com>62Lvov Maxim <usrleon@gmail.com>
@@ -56,14 +64,18 @@
56Masanori Itoh <itoumsn@nttdata.co.jp>64Masanori Itoh <itoumsn@nttdata.co.jp>
57Matt Dietz <matt.dietz@rackspace.com>65Matt Dietz <matt.dietz@rackspace.com>
58Michael Gundlach <michael.gundlach@rackspace.com>66Michael Gundlach <michael.gundlach@rackspace.com>
67Mike Scherbakov <mihgen@gmail.com>
68Mohammed Naser <mnaser@vexxhost.com>
59Monsyne Dragon <mdragon@rackspace.com>69Monsyne Dragon <mdragon@rackspace.com>
60Monty Taylor <mordred@inaugust.com>70Monty Taylor <mordred@inaugust.com>
61MORITA Kazutaka <morita.kazutaka@gmail.com>71MORITA Kazutaka <morita.kazutaka@gmail.com>
62Muneyuki Noguchi <noguchimn@nttdata.co.jp>72Muneyuki Noguchi <noguchimn@nttdata.co.jp>
63Nachi Ueno <ueno.nachi@lab.ntt.co.jp>73Nachi Ueno <ueno.nachi@lab.ntt.co.jp>
64Naveed Massjouni <naveedm9@gmail.com>74Naveed Massjouni <naveedm9@gmail.com>
75Nikolay Sokolov <nsokolov@griddynamics.com>
65Nirmal Ranganathan <nirmal.ranganathan@rackspace.com>76Nirmal Ranganathan <nirmal.ranganathan@rackspace.com>
66Paul Voccio <paul@openstack.org>77Paul Voccio <paul@openstack.org>
78Renuka Apte <renuka.apte@citrix.com>
67Ricardo Carrillo Cruz <emaildericky@gmail.com>79Ricardo Carrillo Cruz <emaildericky@gmail.com>
68Rick Clark <rick@openstack.org>80Rick Clark <rick@openstack.org>
69Rick Harris <rconradharris@gmail.com>81Rick Harris <rconradharris@gmail.com>
@@ -73,6 +85,7 @@
73Salvatore Orlando <salvatore.orlando@eu.citrix.com>85Salvatore Orlando <salvatore.orlando@eu.citrix.com>
74Sandy Walsh <sandy.walsh@rackspace.com>86Sandy Walsh <sandy.walsh@rackspace.com>
75Sateesh Chodapuneedi <sateesh.chodapuneedi@citrix.com>87Sateesh Chodapuneedi <sateesh.chodapuneedi@citrix.com>
88Scott Moser <smoser@ubuntu.com>
76Soren Hansen <soren.hansen@rackspace.com>89Soren Hansen <soren.hansen@rackspace.com>
77Thierry Carrez <thierry@openstack.org>90Thierry Carrez <thierry@openstack.org>
78Todd Willey <todd@ansolabs.com>91Todd Willey <todd@ansolabs.com>
@@ -80,6 +93,7 @@
80Tushar Patil <tushar.vitthal.patil@gmail.com>93Tushar Patil <tushar.vitthal.patil@gmail.com>
81Vasiliy Shlykov <vash@vasiliyshlykov.org>94Vasiliy Shlykov <vash@vasiliyshlykov.org>
82Vishvananda Ishaya <vishvananda@gmail.com>95Vishvananda Ishaya <vishvananda@gmail.com>
96Vivek Y S <vivek.ys@gmail.com>
83William Wolf <throughnothing@gmail.com>97William Wolf <throughnothing@gmail.com>
84Yoshiaki Tamura <yoshi@midokura.jp>98Yoshiaki Tamura <yoshi@midokura.jp>
85Youcef Laribi <Youcef.Laribi@eu.citrix.com>99Youcef Laribi <Youcef.Laribi@eu.citrix.com>
86100
=== modified file 'MANIFEST.in'
--- MANIFEST.in 2011-05-20 19:21:04 +0000
+++ MANIFEST.in 2011-07-14 20:24:25 +0000
@@ -23,6 +23,7 @@
23include nova/console/xvp.conf.template23include nova/console/xvp.conf.template
24include nova/db/sqlalchemy/migrate_repo/migrate.cfg24include nova/db/sqlalchemy/migrate_repo/migrate.cfg
25include nova/db/sqlalchemy/migrate_repo/README25include nova/db/sqlalchemy/migrate_repo/README
26include nova/db/sqlalchemy/migrate_repo/versions/*.sql
26include nova/virt/interfaces.template27include nova/virt/interfaces.template
27include nova/virt/libvirt*.xml.template28include nova/virt/libvirt*.xml.template
28include nova/virt/cpuinfo.xml.template29include nova/virt/cpuinfo.xml.template
2930
=== added file 'bin/instance-usage-audit'
--- bin/instance-usage-audit 1970-01-01 00:00:00 +0000
+++ bin/instance-usage-audit 2011-07-14 20:24:25 +0000
@@ -0,0 +1,116 @@
1#!/usr/bin/env python
2# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
4# Copyright (c) 2011 Openstack, LLC.
5# All Rights Reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License. You may obtain
9# a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations
17# under the License.
18
19"""Cron script to generate usage notifications for instances neither created
20 nor destroyed in a given time period.
21
22 Together with the notifications generated by compute on instance
23 create/delete/resize, over that ime period, this allows an external
24 system consuming usage notification feeds to calculate instance usage
25 for each tenant.
26
27 Time periods are specified like so:
28 <number>[mdy]
29
30 1m = previous month. If the script is run April 1, it will generate usages
31 for March 1 thry March 31.
32 3m = 3 previous months.
33 90d = previous 90 days.
34 1y = previous year. If run on Jan 1, it generates usages for
35 Jan 1 thru Dec 31 of the previous year.
36"""
37
38import datetime
39import gettext
40import os
41import sys
42import time
43
44# If ../nova/__init__.py exists, add ../ to Python search path, so that
45# it will override what happens to be installed in /usr/(local/)lib/python...
46POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
47 os.pardir,
48 os.pardir))
49if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')):
50 sys.path.insert(0, POSSIBLE_TOPDIR)
51
52gettext.install('nova', unicode=1)
53
54
55from nova import context
56from nova import db
57from nova import exception
58from nova import flags
59from nova import log as logging
60from nova import utils
61
62from nova.notifier import api as notifier_api
63
64FLAGS = flags.FLAGS
65flags.DEFINE_string('instance_usage_audit_period', '1m',
66 'time period to generate instance usages for.')
67
68
69def time_period(period):
70 today = datetime.date.today()
71 unit = period[-1]
72 if unit not in 'mdy':
73 raise ValueError('Time period must be m, d, or y')
74 n = int(period[:-1])
75 if unit == 'm':
76 year = today.year - (n // 12)
77 n = n % 12
78 if n >= today.month:
79 year -= 1
80 month = 12 + (today.month - n)
81 else:
82 month = today.month - n
83 begin = datetime.datetime(day=1, month=month, year=year)
84 end = datetime.datetime(day=1, month=today.month, year=today.year)
85
86 elif unit == 'y':
87 begin = datetime.datetime(day=1, month=1, year=today.year - n)
88 end = datetime.datetime(day=1, month=1, year=today.year)
89
90 elif unit == 'd':
91 b = today - datetime.timedelta(days=n)
92 begin = datetime.datetime(day=b.day, month=b.month, year=b.year)
93 end = datetime.datetime(day=today.day,
94 month=today.month,
95 year=today.year)
96
97 return (begin, end)
98
99if __name__ == '__main__':
100 utils.default_flagfile()
101 flags.FLAGS(sys.argv)
102 logging.setup()
103 begin, end = time_period(FLAGS.instance_usage_audit_period)
104 print "Creating usages for %s until %s" % (str(begin), str(end))
105 instances = db.instance_get_active_by_window(context.get_admin_context(),
106 begin,
107 end)
108 print "%s instances" % len(instances)
109 for instance_ref in instances:
110 usage_info = utils.usage_from_instance(instance_ref,
111 audit_period_begining=str(begin),
112 audit_period_ending=str(end))
113 notifier_api.notify('compute.%s' % FLAGS.host,
114 'compute.instance.exists',
115 notifier_api.INFO,
116 usage_info)
0117
=== modified file 'bin/nova-ajax-console-proxy'
--- bin/nova-ajax-console-proxy 2011-03-29 23:13:09 +0000
+++ bin/nova-ajax-console-proxy 2011-07-14 20:24:25 +0000
@@ -137,8 +137,9 @@
137 utils.default_flagfile()137 utils.default_flagfile()
138 FLAGS(sys.argv)138 FLAGS(sys.argv)
139 logging.setup()139 logging.setup()
140 server = wsgi.Server()140 acp_port = FLAGS.ajax_console_proxy_port
141 acp = AjaxConsoleProxy()141 acp = AjaxConsoleProxy()
142 acp.register_listeners()142 acp.register_listeners()
143 server.start(acp, FLAGS.ajax_console_proxy_port, host='0.0.0.0')143 server = wsgi.Server("AJAX Console Proxy", acp, port=acp_port)
144 server.start()
144 server.wait()145 server.wait()
145146
=== modified file 'bin/nova-api'
--- bin/nova-api 2011-03-18 13:56:05 +0000
+++ bin/nova-api 2011-07-14 20:24:25 +0000
@@ -1,5 +1,4 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# pylint: disable=C0103
3# vim: tabstop=4 shiftwidth=4 softtabstop=42# vim: tabstop=4 shiftwidth=4 softtabstop=4
43
5# Copyright 2010 United States Government as represented by the4# Copyright 2010 United States Government as represented by the
@@ -18,44 +17,48 @@
18# See the License for the specific language governing permissions and17# See the License for the specific language governing permissions and
19# limitations under the License.18# limitations under the License.
2019
21"""Starter script for Nova API."""20"""Starter script for Nova API.
2221
23import gettext22Starts both the EC2 and OpenStack APIs in separate processes.
23
24"""
25
24import os26import os
27import signal
25import sys28import sys
2629
27# If ../nova/__init__.py exists, add ../ to Python search path, so that30
28# it will override what happens to be installed in /usr/(local/)lib/python...31possible_topdir = os.path.normpath(os.path.join(os.path.abspath(
29possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),32 sys.argv[0]), os.pardir, os.pardir))
30 os.pardir,33if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")):
31 os.pardir))
32if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
33 sys.path.insert(0, possible_topdir)34 sys.path.insert(0, possible_topdir)
3435
35gettext.install('nova', unicode=1)36import nova.service
37import nova.utils
3638
37from nova import flags39from nova import flags
38from nova import log as logging40
39from nova import service
40from nova import utils
41from nova import version
42from nova import wsgi
43
44
45LOG = logging.getLogger('nova.api')
4641
47FLAGS = flags.FLAGS42FLAGS = flags.FLAGS
4843
44
45def main():
46 """Launch EC2 and OSAPI services."""
47 nova.utils.Bootstrapper.bootstrap_binary(sys.argv)
48
49 launcher = nova.service.Launcher()
50
51 for api in FLAGS.enabled_apis:
52 service = nova.service.WSGIService(api)
53 launcher.launch_service(service)
54
55 signal.signal(signal.SIGTERM, lambda *_: launcher.stop())
56
57 try:
58 launcher.wait()
59 except KeyboardInterrupt:
60 launcher.stop()
61
62
49if __name__ == '__main__':63if __name__ == '__main__':
50 utils.default_flagfile()64 sys.exit(main())
51 FLAGS(sys.argv)
52 logging.setup()
53 LOG.audit(_("Starting nova-api node (version %s)"),
54 version.version_string_with_vcs())
55 LOG.debug(_("Full set of FLAGS:"))
56 for flag in FLAGS:
57 flag_get = FLAGS.get(flag, None)
58 LOG.debug("%(flag)s : %(flag_get)s" % locals())
59
60 service = service.serve_wsgi(service.ApiService)
61 service.wait()
6265
=== modified file 'bin/nova-dhcpbridge'
--- bin/nova-dhcpbridge 2011-03-29 20:32:44 +0000
+++ bin/nova-dhcpbridge 2011-07-14 20:24:25 +0000
@@ -59,14 +59,12 @@
59 LOG.debug(_("leasing ip"))59 LOG.debug(_("leasing ip"))
60 network_manager = utils.import_object(FLAGS.network_manager)60 network_manager = utils.import_object(FLAGS.network_manager)
61 network_manager.lease_fixed_ip(context.get_admin_context(),61 network_manager.lease_fixed_ip(context.get_admin_context(),
62 mac,
63 ip_address)62 ip_address)
64 else:63 else:
65 rpc.cast(context.get_admin_context(),64 rpc.cast(context.get_admin_context(),
66 "%s.%s" % (FLAGS.network_topic, FLAGS.host),65 "%s.%s" % (FLAGS.network_topic, FLAGS.host),
67 {"method": "lease_fixed_ip",66 {"method": "lease_fixed_ip",
68 "args": {"mac": mac,67 "args": {"address": ip_address}})
69 "address": ip_address}})
7068
7169
72def old_lease(mac, ip_address, hostname, interface):70def old_lease(mac, ip_address, hostname, interface):
@@ -81,14 +79,12 @@
81 LOG.debug(_("releasing ip"))79 LOG.debug(_("releasing ip"))
82 network_manager = utils.import_object(FLAGS.network_manager)80 network_manager = utils.import_object(FLAGS.network_manager)
83 network_manager.release_fixed_ip(context.get_admin_context(),81 network_manager.release_fixed_ip(context.get_admin_context(),
84 mac,
85 ip_address)82 ip_address)
86 else:83 else:
87 rpc.cast(context.get_admin_context(),84 rpc.cast(context.get_admin_context(),
88 "%s.%s" % (FLAGS.network_topic, FLAGS.host),85 "%s.%s" % (FLAGS.network_topic, FLAGS.host),
89 {"method": "release_fixed_ip",86 {"method": "release_fixed_ip",
90 "args": {"mac": mac,87 "args": {"address": ip_address}})
91 "address": ip_address}})
9288
9389
94def init_leases(interface):90def init_leases(interface):
@@ -108,6 +104,13 @@
108 interface = os.environ.get('DNSMASQ_INTERFACE', FLAGS.dnsmasq_interface)104 interface = os.environ.get('DNSMASQ_INTERFACE', FLAGS.dnsmasq_interface)
109 if int(os.environ.get('TESTING', '0')):105 if int(os.environ.get('TESTING', '0')):
110 from nova.tests import fake_flags106 from nova.tests import fake_flags
107
108 #if FLAGS.fake_rabbit:
109 # LOG.debug(_("leasing ip"))
110 # network_manager = utils.import_object(FLAGS.network_manager)
111 ## reload(fake_flags)
112 # from nova.tests import fake_flags
113
111 action = argv[1]114 action = argv[1]
112 if action in ['add', 'del', 'old']:115 if action in ['add', 'del', 'old']:
113 mac = argv[2]116 mac = argv[2]
114117
=== modified file 'bin/nova-direct-api'
--- bin/nova-direct-api 2011-03-24 20:20:15 +0000
+++ bin/nova-direct-api 2011-07-14 20:24:25 +0000
@@ -93,6 +93,9 @@
93 with_req = direct.PostParamsMiddleware(with_json)93 with_req = direct.PostParamsMiddleware(with_json)
94 with_auth = direct.DelegatedAuthMiddleware(with_req)94 with_auth = direct.DelegatedAuthMiddleware(with_req)
9595
96 server = wsgi.Server()96 server = wsgi.Server("Direct API",
97 server.start(with_auth, FLAGS.direct_port, host=FLAGS.direct_host)97 with_auth,
98 host=FLAGS.direct_host,
99 port=FLAGS.direct_port)
100 server.start()
98 server.wait()101 server.wait()
99102
=== modified file 'bin/nova-manage'
--- bin/nova-manage 2011-05-20 09:29:54 +0000
+++ bin/nova-manage 2011-07-14 20:24:25 +0000
@@ -53,15 +53,14 @@
53 CLI interface for nova management.53 CLI interface for nova management.
54"""54"""
5555
56import datetime
57import gettext56import gettext
58import glob57import glob
59import json58import json
59import netaddr
60import os60import os
61import sys61import sys
62import time62import time
6363
64import IPy
6564
66# If ../nova/__init__.py exists, add ../ to Python search path, so that65# If ../nova/__init__.py exists, add ../ to Python search path, so that
67# it will override what happens to be installed in /usr/(local/)lib/python...66# it will override what happens to be installed in /usr/(local/)lib/python...
@@ -78,6 +77,7 @@
78from nova import db77from nova import db
79from nova import exception78from nova import exception
80from nova import flags79from nova import flags
80from nova import image
81from nova import log as logging81from nova import log as logging
82from nova import quota82from nova import quota
83from nova import rpc83from nova import rpc
@@ -96,8 +96,8 @@
96flags.DECLARE('vlan_start', 'nova.network.manager')96flags.DECLARE('vlan_start', 'nova.network.manager')
97flags.DECLARE('vpn_start', 'nova.network.manager')97flags.DECLARE('vpn_start', 'nova.network.manager')
98flags.DECLARE('fixed_range_v6', 'nova.network.manager')98flags.DECLARE('fixed_range_v6', 'nova.network.manager')
99flags.DECLARE('images_path', 'nova.image.local')99flags.DECLARE('gateway_v6', 'nova.network.manager')
100flags.DECLARE('libvirt_type', 'nova.virt.libvirt_conn')100flags.DECLARE('libvirt_type', 'nova.virt.libvirt.connection')
101flags.DEFINE_flag(flags.HelpFlag())101flags.DEFINE_flag(flags.HelpFlag())
102flags.DEFINE_flag(flags.HelpshortFlag())102flags.DEFINE_flag(flags.HelpshortFlag())
103flags.DEFINE_flag(flags.HelpXMLFlag())103flags.DEFINE_flag(flags.HelpXMLFlag())
@@ -172,17 +172,23 @@
172 def change(self, project_id, ip, port):172 def change(self, project_id, ip, port):
173 """Change the ip and port for a vpn.173 """Change the ip and port for a vpn.
174174
175 this will update all networks associated with a project
176 not sure if that's the desired behavior or not, patches accepted
177
175 args: project, ip, port"""178 args: project, ip, port"""
179 # TODO(tr3buchet): perhaps this shouldn't update all networks
180 # associated with a project in the future
176 project = self.manager.get_project(project_id)181 project = self.manager.get_project(project_id)
177 if not project:182 if not project:
178 print 'No project %s' % (project_id)183 print 'No project %s' % (project_id)
179 return184 return
180 admin = context.get_admin_context()185 admin_context = context.get_admin_context()
181 network_ref = db.project_get_network(admin, project_id)186 networks = db.project_get_networks(admin_context, project_id)
182 db.network_update(admin,187 for network in networks:
183 network_ref['id'],188 db.network_update(admin_context,
184 {'vpn_public_address': ip,189 network['id'],
185 'vpn_public_port': int(port)})190 {'vpn_public_address': ip,
191 'vpn_public_port': int(port)})
186192
187193
188class ShellCommands(object):194class ShellCommands(object):
@@ -257,6 +263,11 @@
257 """adds role to user263 """adds role to user
258 if project is specified, adds project specific role264 if project is specified, adds project specific role
259 arguments: user, role [project]"""265 arguments: user, role [project]"""
266 if project:
267 projobj = self.manager.get_project(project)
268 if not projobj.has_member(user):
269 print "%s not a member of %s" % (user, project)
270 return
260 self.manager.add_role(user, role, project)271 self.manager.add_role(user, role, project)
261272
262 def has(self, user, role, project=None):273 def has(self, user, role, project=None):
@@ -403,8 +414,11 @@
403 except (exception.UserNotFound, exception.ProjectNotFound) as ex:414 except (exception.UserNotFound, exception.ProjectNotFound) as ex:
404 print ex415 print ex
405 raise416 raise
406 with open(filename, 'w') as f:417 if filename == "-":
407 f.write(rc)418 sys.stdout.write(rc)
419 else:
420 with open(filename, 'w') as f:
421 f.write(rc)
408422
409 def list(self, username=None):423 def list(self, username=None):
410 """Lists all projects424 """Lists all projects
@@ -417,12 +431,16 @@
417 arguments: project_id [key] [value]"""431 arguments: project_id [key] [value]"""
418 ctxt = context.get_admin_context()432 ctxt = context.get_admin_context()
419 if key:433 if key:
434 if value.lower() == 'unlimited':
435 value = None
420 try:436 try:
421 db.quota_update(ctxt, project_id, key, value)437 db.quota_update(ctxt, project_id, key, value)
422 except exception.ProjectQuotaNotFound:438 except exception.ProjectQuotaNotFound:
423 db.quota_create(ctxt, project_id, key, value)439 db.quota_create(ctxt, project_id, key, value)
424 project_quota = quota.get_quota(ctxt, project_id)440 project_quota = quota.get_project_quotas(ctxt, project_id)
425 for key, value in project_quota.iteritems():441 for key, value in project_quota.iteritems():
442 if value is None:
443 value = 'unlimited'
426 print '%s: %s' % (key, value)444 print '%s: %s' % (key, value)
427445
428 def remove(self, project_id, user_id):446 def remove(self, project_id, user_id):
@@ -437,20 +455,24 @@
437 def scrub(self, project_id):455 def scrub(self, project_id):
438 """Deletes data associated with project456 """Deletes data associated with project
439 arguments: project_id"""457 arguments: project_id"""
440 ctxt = context.get_admin_context()458 admin_context = context.get_admin_context()
441 network_ref = db.project_get_network(ctxt, project_id)459 networks = db.project_get_networks(admin_context, project_id)
442 db.network_disassociate(ctxt, network_ref['id'])460 for network in networks:
443 groups = db.security_group_get_by_project(ctxt, project_id)461 db.network_disassociate(admin_context, network['id'])
462 groups = db.security_group_get_by_project(admin_context, project_id)
444 for group in groups:463 for group in groups:
445 db.security_group_destroy(ctxt, group['id'])464 db.security_group_destroy(admin_context, group['id'])
446465
447 def zipfile(self, project_id, user_id, filename='nova.zip'):466 def zipfile(self, project_id, user_id, filename='nova.zip'):
448 """Exports credentials for project to a zip file467 """Exports credentials for project to a zip file
449 arguments: project_id user_id [filename='nova.zip]"""468 arguments: project_id user_id [filename='nova.zip]"""
450 try:469 try:
451 zip_file = self.manager.get_credentials(user_id, project_id)470 zip_file = self.manager.get_credentials(user_id, project_id)
452 with open(filename, 'w') as f:471 if filename == "-":
453 f.write(zip_file)472 sys.stdout.write(zip_file)
473 else:
474 with open(filename, 'w') as f:
475 f.write(zip_file)
454 except (exception.UserNotFound, exception.ProjectNotFound) as ex:476 except (exception.UserNotFound, exception.ProjectNotFound) as ex:
455 print ex477 print ex
456 raise478 raise
@@ -496,7 +518,7 @@
496 instance = fixed_ip['instance']518 instance = fixed_ip['instance']
497 hostname = instance['hostname']519 hostname = instance['hostname']
498 host = instance['host']520 host = instance['host']
499 mac_address = instance['mac_address']521 mac_address = fixed_ip['mac_address']['address']
500 print "%-18s\t%-15s\t%-17s\t%-15s\t%s" % (522 print "%-18s\t%-15s\t%-17s\t%-15s\t%s" % (
501 fixed_ip['network']['cidr'],523 fixed_ip['network']['cidr'],
502 fixed_ip['address'],524 fixed_ip['address'],
@@ -506,24 +528,24 @@
506class FloatingIpCommands(object):528class FloatingIpCommands(object):
507 """Class for managing floating ip."""529 """Class for managing floating ip."""
508530
509 def create(self, host, range):531 def create(self, range):
510 """Creates floating ips for host by range532 """Creates floating ips for zone by range
511 arguments: host ip_range"""533 arguments: ip_range"""
512 for address in IPy.IP(range):534 for address in netaddr.IPNetwork(range):
513 db.floating_ip_create(context.get_admin_context(),535 db.floating_ip_create(context.get_admin_context(),
514 {'address': str(address),536 {'address': str(address)})
515 'host': host})
516537
517 def delete(self, ip_range):538 def delete(self, ip_range):
518 """Deletes floating ips by range539 """Deletes floating ips by range
519 arguments: range"""540 arguments: range"""
520 for address in IPy.IP(ip_range):541 for address in netaddr.IPNetwork(ip_range):
521 db.floating_ip_destroy(context.get_admin_context(),542 db.floating_ip_destroy(context.get_admin_context(),
522 str(address))543 str(address))
523544
524 def list(self, host=None):545 def list(self, host=None):
525 """Lists all floating ips (optionally by host)546 """Lists all floating ips (optionally by host)
526 arguments: [host]"""547 arguments: [host]
548 Note: if host is given, only active floating IPs are returned"""
527 ctxt = context.get_admin_context()549 ctxt = context.get_admin_context()
528 if host is None:550 if host is None:
529 floating_ips = db.floating_ip_get_all(ctxt)551 floating_ips = db.floating_ip_get_all(ctxt)
@@ -532,7 +554,7 @@
532 for floating_ip in floating_ips:554 for floating_ip in floating_ips:
533 instance = None555 instance = None
534 if floating_ip['fixed_ip']:556 if floating_ip['fixed_ip']:
535 instance = floating_ip['fixed_ip']['instance']['ec2_id']557 instance = floating_ip['fixed_ip']['instance']['hostname']
536 print "%s\t%s\t%s" % (floating_ip['host'],558 print "%s\t%s\t%s" % (floating_ip['host'],
537 floating_ip['address'],559 floating_ip['address'],
538 instance)560 instance)
@@ -541,13 +563,23 @@
541class NetworkCommands(object):563class NetworkCommands(object):
542 """Class for managing networks."""564 """Class for managing networks."""
543565
544 def create(self, fixed_range=None, num_networks=None,566 def create(self, label=None, fixed_range=None, num_networks=None,
545 network_size=None, vlan_start=None,567 network_size=None, vlan_start=None,
546 vpn_start=None, fixed_range_v6=None, label='public'):568 vpn_start=None, fixed_range_v6=None, gateway_v6=None,
569 flat_network_bridge=None, bridge_interface=None):
547 """Creates fixed ips for host by range570 """Creates fixed ips for host by range
548 arguments: fixed_range=FLAG, [num_networks=FLAG],571 arguments: label, fixed_range, [num_networks=FLAG],
549 [network_size=FLAG], [vlan_start=FLAG],572 [network_size=FLAG], [vlan_start=FLAG],
550 [vpn_start=FLAG], [fixed_range_v6=FLAG]"""573 [vpn_start=FLAG], [fixed_range_v6=FLAG], [gateway_v6=FLAG],
574 [flat_network_bridge=FLAG], [bridge_interface=FLAG]
575 If you wish to use a later argument fill in the gaps with 0s
576 Ex: network create private 10.0.0.0/8 1 15 0 0 0 0 xenbr1 eth1
577 network create private 10.0.0.0/8 1 15
578 """
579 if not label:
580 msg = _('a label (ex: public) is required to create networks.')
581 print msg
582 raise TypeError(msg)
551 if not fixed_range:583 if not fixed_range:
552 msg = _('Fixed range in the form of 10.0.0.0/8 is '584 msg = _('Fixed range in the form of 10.0.0.0/8 is '
553 'required to create networks.')585 'required to create networks.')
@@ -563,31 +595,45 @@
563 vpn_start = FLAGS.vpn_start595 vpn_start = FLAGS.vpn_start
564 if not fixed_range_v6:596 if not fixed_range_v6:
565 fixed_range_v6 = FLAGS.fixed_range_v6597 fixed_range_v6 = FLAGS.fixed_range_v6
598 if not flat_network_bridge:
599 flat_network_bridge = FLAGS.flat_network_bridge
600 if not bridge_interface:
601 bridge_interface = FLAGS.flat_interface or FLAGS.vlan_interface
602 if not gateway_v6:
603 gateway_v6 = FLAGS.gateway_v6
566 net_manager = utils.import_object(FLAGS.network_manager)604 net_manager = utils.import_object(FLAGS.network_manager)
605
567 try:606 try:
568 net_manager.create_networks(context.get_admin_context(),607 net_manager.create_networks(context.get_admin_context(),
608 label=label,
569 cidr=fixed_range,609 cidr=fixed_range,
570 num_networks=int(num_networks),610 num_networks=int(num_networks),
571 network_size=int(network_size),611 network_size=int(network_size),
572 vlan_start=int(vlan_start),612 vlan_start=int(vlan_start),
573 vpn_start=int(vpn_start),613 vpn_start=int(vpn_start),
574 cidr_v6=fixed_range_v6,614 cidr_v6=fixed_range_v6,
575 label=label)615 gateway_v6=gateway_v6,
616 bridge=flat_network_bridge,
617 bridge_interface=bridge_interface)
576 except ValueError, e:618 except ValueError, e:
577 print e619 print e
578 raise e620 raise e
579621
580 def list(self):622 def list(self):
581 """List all created networks"""623 """List all created networks"""
582 print "%-18s\t%-15s\t%-15s\t%-15s" % (_('network'),624 print "%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % (_('network'),
583 _('netmask'),625 _('netmask'),
584 _('start address'),626 _('start address'),
585 'DNS')627 _('DNS'),
628 _('VlanID'),
629 'project')
586 for network in db.network_get_all(context.get_admin_context()):630 for network in db.network_get_all(context.get_admin_context()):
587 print "%-18s\t%-15s\t%-15s\t%-15s" % (network.cidr,631 print "%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % (network.cidr,
588 network.netmask,632 network.netmask,
589 network.dhcp_start,633 network.dhcp_start,
590 network.dns)634 network.dns,
635 network.vlan,
636 network.project_id)
591637
592 def delete(self, fixed_range):638 def delete(self, fixed_range):
593 """Deletes a network"""639 """Deletes a network"""
@@ -608,7 +654,7 @@
608 :param host: show all instance on specified host.654 :param host: show all instance on specified host.
609 :param instance: show specificed instance.655 :param instance: show specificed instance.
610 """656 """
611 print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \657 print "%-10s %-15s %-10s %-10s %-26s %-9s %-9s %-9s" \
612 " %-10s %-10s %-10s %-5s" % (658 " %-10s %-10s %-10s %-5s" % (
613 _('instance'),659 _('instance'),
614 _('node'),660 _('node'),
@@ -630,14 +676,14 @@
630 context.get_admin_context(), host)676 context.get_admin_context(), host)
631677
632 for instance in instances:678 for instance in instances:
633 print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \679 print "%-10s %-15s %-10s %-10s %-26s %-9s %-9s %-9s" \
634 " %-10s %-10s %-10s %-5d" % (680 " %-10s %-10s %-10s %-5d" % (
635 instance['hostname'],681 instance['hostname'],
636 instance['host'],682 instance['host'],
637 instance['instance_type'],683 instance['instance_type'].name,
638 instance['state_description'],684 instance['state_description'],
639 instance['launched_at'],685 instance['launched_at'],
640 instance['image_id'],686 instance['image_ref'],
641 instance['kernel_id'],687 instance['kernel_id'],
642 instance['ramdisk_id'],688 instance['ramdisk_id'],
643 instance['project_id'],689 instance['project_id'],
@@ -685,7 +731,7 @@
685 """Show a list of all running services. Filter by host & service name.731 """Show a list of all running services. Filter by host & service name.
686 args: [host] [service]"""732 args: [host] [service]"""
687 ctxt = context.get_admin_context()733 ctxt = context.get_admin_context()
688 now = datetime.datetime.utcnow()734 now = utils.utcnow()
689 services = db.service_get_all(ctxt)735 services = db.service_get_all(ctxt)
690 if host:736 if host:
691 services = [s for s in services if s['host'] == host]737 services = [s for s in services if s['host'] == host]
@@ -776,6 +822,28 @@
776 {"method": "update_available_resource"})822 {"method": "update_available_resource"})
777823
778824
825class HostCommands(object):
826 """List hosts"""
827
828 def list(self, zone=None):
829 """Show a list of all physical hosts. Filter by zone.
830 args: [zone]"""
831 print "%-25s\t%-15s" % (_('host'),
832 _('zone'))
833 ctxt = context.get_admin_context()
834 now = utils.utcnow()
835 services = db.service_get_all(ctxt)
836 if zone:
837 services = [s for s in services if s['availability_zone'] == zone]
838 hosts = []
839 for srv in services:
840 if not [h for h in hosts if h['host'] == srv['host']]:
841 hosts.append(srv)
842
843 for h in hosts:
844 print "%-25s\t%-15s" % (h['host'], h['availability_zone'])
845
846
779class DbCommands(object):847class DbCommands(object):
780 """Class for managing the database."""848 """Class for managing the database."""
781849
@@ -869,7 +937,7 @@
869 try:937 try:
870 instance_types.create(name, memory, vcpus, local_gb,938 instance_types.create(name, memory, vcpus, local_gb,
871 flavorid, swap, rxtx_quota, rxtx_cap)939 flavorid, swap, rxtx_quota, rxtx_cap)
872 except exception.InvalidInputException:940 except exception.InvalidInput, e:
873 print "Must supply valid parameters to create instance_type"941 print "Must supply valid parameters to create instance_type"
874 print e942 print e
875 sys.exit(1)943 sys.exit(1)
@@ -932,7 +1000,7 @@
932 """Methods for dealing with a cloud in an odd state"""1000 """Methods for dealing with a cloud in an odd state"""
9331001
934 def __init__(self, *args, **kwargs):1002 def __init__(self, *args, **kwargs):
935 self.image_service = utils.import_object(FLAGS.image_service)1003 self.image_service = image.get_default_image_service()
9361004
937 def _register(self, container_format, disk_format,1005 def _register(self, container_format, disk_format,
938 path, owner, name=None, is_public='T',1006 path, owner, name=None, is_public='T',
@@ -1051,16 +1119,6 @@
1051 machine_images = {}1119 machine_images = {}
1052 other_images = {}1120 other_images = {}
1053 directory = os.path.abspath(directory)1121 directory = os.path.abspath(directory)
1054 # NOTE(vish): If we're importing from the images path dir, attempt
1055 # to move the files out of the way before importing
1056 # so we aren't writing to the same directory. This
1057 # may fail if the dir was a mointpoint.
1058 if (FLAGS.image_service == 'nova.image.local.LocalImageService'
1059 and directory == os.path.abspath(FLAGS.images_path)):
1060 new_dir = "%s_bak" % directory
1061 os.rename(directory, new_dir)
1062 os.mkdir(directory)
1063 directory = new_dir
1064 for fn in glob.glob("%s/*/info.json" % directory):1122 for fn in glob.glob("%s/*/info.json" % directory):
1065 try:1123 try:
1066 image_path = os.path.join(fn.rpartition('/')[0], 'image')1124 image_path = os.path.join(fn.rpartition('/')[0], 'image')
@@ -1077,24 +1135,101 @@
1077 self._convert_images(machine_images)1135 self._convert_images(machine_images)
10781136
10791137
1138class AgentBuildCommands(object):
1139 """Class for managing agent builds."""
1140
1141 def create(self, os, architecture, version, url, md5hash,
1142 hypervisor='xen'):
1143 """Creates a new agent build.
1144 arguments: os architecture version url md5hash [hypervisor='xen']"""
1145 ctxt = context.get_admin_context()
1146 agent_build = db.agent_build_create(ctxt,
1147 {'hypervisor': hypervisor,
1148 'os': os,
1149 'architecture': architecture,
1150 'version': version,
1151 'url': url,
1152 'md5hash': md5hash})
1153
1154 def delete(self, os, architecture, hypervisor='xen'):
1155 """Deletes an existing agent build.
1156 arguments: os architecture [hypervisor='xen']"""
1157 ctxt = context.get_admin_context()
1158 agent_build_ref = db.agent_build_get_by_triple(ctxt,
1159 hypervisor, os, architecture)
1160 db.agent_build_destroy(ctxt, agent_build_ref['id'])
1161
1162 def list(self, hypervisor=None):
1163 """Lists all agent builds.
1164 arguments: <none>"""
1165 fmt = "%-10s %-8s %12s %s"
1166 ctxt = context.get_admin_context()
1167 by_hypervisor = {}
1168 for agent_build in db.agent_build_get_all(ctxt):
1169 buildlist = by_hypervisor.get(agent_build.hypervisor)
1170 if not buildlist:
1171 buildlist = by_hypervisor[agent_build.hypervisor] = []
1172
1173 buildlist.append(agent_build)
1174
1175 for key, buildlist in by_hypervisor.iteritems():
1176 if hypervisor and key != hypervisor:
1177 continue
1178
1179 print "Hypervisor: %s" % key
1180 print fmt % ('-' * 10, '-' * 8, '-' * 12, '-' * 32)
1181 for agent_build in buildlist:
1182 print fmt % (agent_build.os, agent_build.architecture,
1183 agent_build.version, agent_build.md5hash)
1184 print ' %s' % agent_build.url
1185
1186 print
1187
1188 def modify(self, os, architecture, version, url, md5hash,
1189 hypervisor='xen'):
1190 """Update an existing agent build.
1191 arguments: os architecture version url md5hash [hypervisor='xen']
1192 """
1193 ctxt = context.get_admin_context()
1194 agent_build_ref = db.agent_build_get_by_triple(ctxt,
1195 hypervisor, os, architecture)
1196 db.agent_build_update(ctxt, agent_build_ref['id'],
1197 {'version': version,
1198 'url': url,
1199 'md5hash': md5hash})
1200
1201
1202class ConfigCommands(object):
1203 """Class for exposing the flags defined by flag_file(s)."""
1204
1205 def __init__(self):
1206 pass
1207
1208 def list(self):
1209 print FLAGS.FlagsIntoString()
1210
1211
1080CATEGORIES = [1212CATEGORIES = [
1081 ('user', UserCommands),
1082 ('account', AccountCommands),1213 ('account', AccountCommands),
1214 ('agent', AgentBuildCommands),
1215 ('config', ConfigCommands),
1216 ('db', DbCommands),
1217 ('fixed', FixedIpCommands),
1218 ('flavor', InstanceTypeCommands),
1219 ('floating', FloatingIpCommands),
1220 ('host', HostCommands),
1221 ('instance_type', InstanceTypeCommands),
1222 ('image', ImageCommands),
1223 ('network', NetworkCommands),
1083 ('project', ProjectCommands),1224 ('project', ProjectCommands),
1084 ('role', RoleCommands),1225 ('role', RoleCommands),
1226 ('service', ServiceCommands),
1085 ('shell', ShellCommands),1227 ('shell', ShellCommands),
1086 ('vpn', VpnCommands),1228 ('user', UserCommands),
1087 ('fixed', FixedIpCommands),1229 ('version', VersionCommands),
1088 ('floating', FloatingIpCommands),
1089 ('network', NetworkCommands),
1090 ('vm', VmCommands),1230 ('vm', VmCommands),
1091 ('service', ServiceCommands),
1092 ('db', DbCommands),
1093 ('volume', VolumeCommands),1231 ('volume', VolumeCommands),
1094 ('instance_type', InstanceTypeCommands),1232 ('vpn', VpnCommands)]
1095 ('image', ImageCommands),
1096 ('flavor', InstanceTypeCommands),
1097 ('version', VersionCommands)]
10981233
10991234
1100def lazy_match(name, key_value_tuples):1235def lazy_match(name, key_value_tuples):
11011236
=== modified file 'bin/nova-objectstore'
--- bin/nova-objectstore 2011-03-24 23:37:35 +0000
+++ bin/nova-objectstore 2011-07-14 20:24:25 +0000
@@ -50,6 +50,9 @@
50 FLAGS(sys.argv)50 FLAGS(sys.argv)
51 logging.setup()51 logging.setup()
52 router = s3server.S3Application(FLAGS.buckets_path)52 router = s3server.S3Application(FLAGS.buckets_path)
53 server = wsgi.Server()53 server = wsgi.Server("S3 Objectstore",
54 server.start(router, FLAGS.s3_port, host=FLAGS.s3_host)54 router,
55 port=FLAGS.s3_port,
56 host=FLAGS.s3_host)
57 server.start()
55 server.wait()58 server.wait()
5659
=== modified file 'bin/nova-vncproxy'
--- bin/nova-vncproxy 2011-03-29 21:53:38 +0000
+++ bin/nova-vncproxy 2011-07-14 20:24:25 +0000
@@ -63,6 +63,19 @@
63flags.DEFINE_flag(flags.HelpXMLFlag())63flags.DEFINE_flag(flags.HelpXMLFlag())
6464
6565
66def handle_flash_socket_policy(socket):
67 LOG.info(_("Received connection on flash socket policy port"))
68
69 fd = socket.makefile('rw')
70 expected_command = "<policy-file-request/>"
71 if expected_command in fd.read(len(expected_command) + 1):
72 LOG.info(_("Received valid flash socket policy request"))
73 fd.write('<?xml version="1.0"?><cross-domain-policy><allow-'
74 'access-from domain="*" to-ports="%d" /></cross-'
75 'domain-policy>' % (FLAGS.vncproxy_port))
76 fd.flush()
77 socket.close()
78
66if __name__ == "__main__":79if __name__ == "__main__":
67 utils.default_flagfile()80 utils.default_flagfile()
68 FLAGS(sys.argv)81 FLAGS(sys.argv)
@@ -96,6 +109,11 @@
96109
97 service.serve()110 service.serve()
98111
99 server = wsgi.Server()112 server = wsgi.Server("VNC Proxy",
100 server.start(with_auth, FLAGS.vncproxy_port, host=FLAGS.vncproxy_host)113 with_auth,
114 host=FLAGS.vncproxy_host,
115 port=FLAGS.vncproxy_port)
116 server.start()
117 server.start_tcp(handle_flash_socket_policy, 843, host=FLAGS.vncproxy_host)
118
101 server.wait()119 server.wait()
102120
=== modified file 'contrib/nova.sh'
--- contrib/nova.sh 2011-03-25 16:40:59 +0000
+++ contrib/nova.sh 2011-07-14 20:24:25 +0000
@@ -17,7 +17,7 @@
17 HOST_IP=`LC_ALL=C ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'`17 HOST_IP=`LC_ALL=C ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'`
18fi18fi
1919
20USE_MYSQL=${USE_MYSQL:-0}20USE_MYSQL=${USE_MYSQL:-1}
21INTERFACE=${INTERFACE:-eth0}21INTERFACE=${INTERFACE:-eth0}
22FLOATING_RANGE=${FLOATING_RANGE:-10.6.0.0/27}22FLOATING_RANGE=${FLOATING_RANGE:-10.6.0.0/27}
23FIXED_RANGE=${FIXED_RANGE:-10.0.0.0/24}23FIXED_RANGE=${FIXED_RANGE:-10.0.0.0/24}
@@ -159,10 +159,6 @@
159 mkdir -p $NOVA_DIR/instances159 mkdir -p $NOVA_DIR/instances
160 rm -rf $NOVA_DIR/networks160 rm -rf $NOVA_DIR/networks
161 mkdir -p $NOVA_DIR/networks161 mkdir -p $NOVA_DIR/networks
162 if [ ! -d "$NOVA_DIR/images" ]; then
163 ln -s $DIR/images $NOVA_DIR/images
164 fi
165
166 if [ "$TEST" == 1 ]; then162 if [ "$TEST" == 1 ]; then
167 cd $NOVA_DIR163 cd $NOVA_DIR
168 python $NOVA_DIR/run_tests.py164 python $NOVA_DIR/run_tests.py
@@ -181,8 +177,18 @@
181 # create some floating ips177 # create some floating ips
182 $NOVA_DIR/bin/nova-manage floating create `hostname` $FLOATING_RANGE178 $NOVA_DIR/bin/nova-manage floating create `hostname` $FLOATING_RANGE
183179
184 # convert old images180 if [ ! -d "$NOVA_DIR/images" ]; then
185 $NOVA_DIR/bin/nova-manage image convert $DIR/images181 if [ ! -d "$DIR/converted-images" ]; then
182 # convert old images
183 mkdir $DIR/converted-images
184 ln -s $DIR/converted-images $NOVA_DIR/images
185 $NOVA_DIR/bin/nova-manage image convert $DIR/images
186 else
187 ln -s $DIR/converted-images $NOVA_DIR/images
188 fi
189
190 fi
191
186192
187 # nova api crashes if we start it with a regular screen command,193 # nova api crashes if we start it with a regular screen command,
188 # so send the start command by forcing text into the window.194 # so send the start command by forcing text into the window.
189195
=== removed file 'doc/.autogenerated'
--- doc/.autogenerated 2011-03-03 00:57:56 +0000
+++ doc/.autogenerated 1970-01-01 00:00:00 +0000
@@ -1,283 +0,0 @@
1source/api/nova..adminclient.rst
2source/api/nova..api.direct.rst
3source/api/nova..api.ec2.admin.rst
4source/api/nova..api.ec2.apirequest.rst
5source/api/nova..api.ec2.cloud.rst
6source/api/nova..api.ec2.metadatarequesthandler.rst
7source/api/nova..api.openstack.auth.rst
8source/api/nova..api.openstack.backup_schedules.rst
9source/api/nova..api.openstack.common.rst
10source/api/nova..api.openstack.consoles.rst
11source/api/nova..api.openstack.faults.rst
12source/api/nova..api.openstack.flavors.rst
13source/api/nova..api.openstack.images.rst
14source/api/nova..api.openstack.servers.rst
15source/api/nova..api.openstack.shared_ip_groups.rst
16source/api/nova..api.openstack.zones.rst
17source/api/nova..auth.dbdriver.rst
18source/api/nova..auth.fakeldap.rst
19source/api/nova..auth.ldapdriver.rst
20source/api/nova..auth.manager.rst
21source/api/nova..auth.signer.rst
22source/api/nova..cloudpipe.pipelib.rst
23source/api/nova..compute.api.rst
24source/api/nova..compute.instance_types.rst
25source/api/nova..compute.manager.rst
26source/api/nova..compute.monitor.rst
27source/api/nova..compute.power_state.rst
28source/api/nova..console.api.rst
29source/api/nova..console.fake.rst
30source/api/nova..console.manager.rst
31source/api/nova..console.xvp.rst
32source/api/nova..context.rst
33source/api/nova..crypto.rst
34source/api/nova..db.api.rst
35source/api/nova..db.base.rst
36source/api/nova..db.migration.rst
37source/api/nova..db.sqlalchemy.api.rst
38source/api/nova..db.sqlalchemy.migrate_repo.manage.rst
39source/api/nova..db.sqlalchemy.migrate_repo.versions.001_austin.rst
40source/api/nova..db.sqlalchemy.migrate_repo.versions.002_bexar.rst
41source/api/nova..db.sqlalchemy.migrate_repo.versions.003_add_label_to_networks.rst
42source/api/nova..db.sqlalchemy.migrate_repo.versions.004_add_zone_tables.rst
43source/api/nova..db.sqlalchemy.migrate_repo.versions.005_add_instance_metadata.rst
44source/api/nova..db.sqlalchemy.migrate_repo.versions.006_add_provider_data_to_volumes.rst
45source/api/nova..db.sqlalchemy.migrate_repo.versions.007_add_instance_types.rst
46source/api/nova..db.sqlalchemy.migration.rst
47source/api/nova..db.sqlalchemy.models.rst
48source/api/nova..db.sqlalchemy.session.rst
49source/api/nova..exception.rst
50source/api/nova..fakememcache.rst
51source/api/nova..fakerabbit.rst
52source/api/nova..flags.rst
53source/api/nova..image.glance.rst
54source/api/nova..image.local.rst
55source/api/nova..image.s3.rst
56source/api/nova..image.service.rst
57source/api/nova..log.rst
58source/api/nova..manager.rst
59source/api/nova..network.api.rst
60source/api/nova..network.linux_net.rst
61source/api/nova..network.manager.rst
62source/api/nova..objectstore.bucket.rst
63source/api/nova..objectstore.handler.rst
64source/api/nova..objectstore.image.rst
65source/api/nova..objectstore.stored.rst
66source/api/nova..quota.rst
67source/api/nova..rpc.rst
68source/api/nova..scheduler.chance.rst
69source/api/nova..scheduler.driver.rst
70source/api/nova..scheduler.manager.rst
71source/api/nova..scheduler.simple.rst
72source/api/nova..scheduler.zone.rst
73source/api/nova..service.rst
74source/api/nova..test.rst
75source/api/nova..tests.api.openstack.fakes.rst
76source/api/nova..tests.api.openstack.test_adminapi.rst
77source/api/nova..tests.api.openstack.test_api.rst
78source/api/nova..tests.api.openstack.test_auth.rst
79source/api/nova..tests.api.openstack.test_common.rst
80source/api/nova..tests.api.openstack.test_faults.rst
81source/api/nova..tests.api.openstack.test_flavors.rst
82source/api/nova..tests.api.openstack.test_images.rst
83source/api/nova..tests.api.openstack.test_ratelimiting.rst
84source/api/nova..tests.api.openstack.test_servers.rst
85source/api/nova..tests.api.openstack.test_shared_ip_groups.rst
86source/api/nova..tests.api.openstack.test_zones.rst
87source/api/nova..tests.api.test_wsgi.rst
88source/api/nova..tests.db.fakes.rst
89source/api/nova..tests.declare_flags.rst
90source/api/nova..tests.fake_flags.rst
91source/api/nova..tests.glance.stubs.rst
92source/api/nova..tests.hyperv_unittest.rst
93source/api/nova..tests.objectstore_unittest.rst
94source/api/nova..tests.real_flags.rst
95source/api/nova..tests.runtime_flags.rst
96source/api/nova..tests.test_access.rst
97source/api/nova..tests.test_api.rst
98source/api/nova..tests.test_auth.rst
99source/api/nova..tests.test_cloud.rst
100source/api/nova..tests.test_compute.rst
101source/api/nova..tests.test_console.rst
102source/api/nova..tests.test_direct.rst
103source/api/nova..tests.test_flags.rst
104source/api/nova..tests.test_instance_types.rst
105source/api/nova..tests.test_localization.rst
106source/api/nova..tests.test_log.rst
107source/api/nova..tests.test_middleware.rst
108source/api/nova..tests.test_misc.rst
109source/api/nova..tests.test_network.rst
110source/api/nova..tests.test_quota.rst
111source/api/nova..tests.test_rpc.rst
112source/api/nova..tests.test_scheduler.rst
113source/api/nova..tests.test_service.rst
114source/api/nova..tests.test_test.rst
115source/api/nova..tests.test_twistd.rst
116source/api/nova..tests.test_utils.rst
117source/api/nova..tests.test_virt.rst
118source/api/nova..tests.test_volume.rst
119source/api/nova..tests.test_xenapi.rst
120source/api/nova..tests.xenapi.stubs.rst
121source/api/nova..twistd.rst
122source/api/nova..utils.rst
123source/api/nova..version.rst
124source/api/nova..virt.connection.rst
125source/api/nova..virt.disk.rst
126source/api/nova..virt.fake.rst
127source/api/nova..virt.hyperv.rst
128source/api/nova..virt.images.rst
129source/api/nova..virt.libvirt_conn.rst
130source/api/nova..virt.xenapi.fake.rst
131source/api/nova..virt.xenapi.network_utils.rst
132source/api/nova..virt.xenapi.vm_utils.rst
133source/api/nova..virt.xenapi.vmops.rst
134source/api/nova..virt.xenapi.volume_utils.rst
135source/api/nova..virt.xenapi.volumeops.rst
136source/api/nova..virt.xenapi_conn.rst
137source/api/nova..volume.api.rst
138source/api/nova..volume.driver.rst
139source/api/nova..volume.manager.rst
140source/api/nova..volume.san.rst
141source/api/nova..wsgi.rst
142source/api/autoindex.rst
143source/api/nova..adminclient.rst
144source/api/nova..api.direct.rst
145source/api/nova..api.ec2.admin.rst
146source/api/nova..api.ec2.apirequest.rst
147source/api/nova..api.ec2.cloud.rst
148source/api/nova..api.ec2.metadatarequesthandler.rst
149source/api/nova..api.openstack.auth.rst
150source/api/nova..api.openstack.backup_schedules.rst
151source/api/nova..api.openstack.common.rst
152source/api/nova..api.openstack.consoles.rst
153source/api/nova..api.openstack.faults.rst
154source/api/nova..api.openstack.flavors.rst
155source/api/nova..api.openstack.images.rst
156source/api/nova..api.openstack.servers.rst
157source/api/nova..api.openstack.shared_ip_groups.rst
158source/api/nova..api.openstack.zones.rst
159source/api/nova..auth.dbdriver.rst
160source/api/nova..auth.fakeldap.rst
161source/api/nova..auth.ldapdriver.rst
162source/api/nova..auth.manager.rst
163source/api/nova..auth.signer.rst
164source/api/nova..cloudpipe.pipelib.rst
165source/api/nova..compute.api.rst
166source/api/nova..compute.instance_types.rst
167source/api/nova..compute.manager.rst
168source/api/nova..compute.monitor.rst
169source/api/nova..compute.power_state.rst
170source/api/nova..console.api.rst
171source/api/nova..console.fake.rst
172source/api/nova..console.manager.rst
173source/api/nova..console.xvp.rst
174source/api/nova..context.rst
175source/api/nova..crypto.rst
176source/api/nova..db.api.rst
177source/api/nova..db.base.rst
178source/api/nova..db.migration.rst
179source/api/nova..db.sqlalchemy.api.rst
180source/api/nova..db.sqlalchemy.migrate_repo.manage.rst
181source/api/nova..db.sqlalchemy.migrate_repo.versions.001_austin.rst
182source/api/nova..db.sqlalchemy.migrate_repo.versions.002_bexar.rst
183source/api/nova..db.sqlalchemy.migrate_repo.versions.003_add_label_to_networks.rst
184source/api/nova..db.sqlalchemy.migrate_repo.versions.004_add_zone_tables.rst
185source/api/nova..db.sqlalchemy.migrate_repo.versions.005_add_instance_metadata.rst
186source/api/nova..db.sqlalchemy.migrate_repo.versions.006_add_provider_data_to_volumes.rst
187source/api/nova..db.sqlalchemy.migrate_repo.versions.007_add_instance_types.rst
188source/api/nova..db.sqlalchemy.migration.rst
189source/api/nova..db.sqlalchemy.models.rst
190source/api/nova..db.sqlalchemy.session.rst
191source/api/nova..exception.rst
192source/api/nova..fakememcache.rst
193source/api/nova..fakerabbit.rst
194source/api/nova..flags.rst
195source/api/nova..image.glance.rst
196source/api/nova..image.local.rst
197source/api/nova..image.s3.rst
198source/api/nova..image.service.rst
199source/api/nova..log.rst
200source/api/nova..manager.rst
201source/api/nova..network.api.rst
202source/api/nova..network.linux_net.rst
203source/api/nova..network.manager.rst
204source/api/nova..objectstore.bucket.rst
205source/api/nova..objectstore.handler.rst
206source/api/nova..objectstore.image.rst
207source/api/nova..objectstore.stored.rst
208source/api/nova..quota.rst
209source/api/nova..rpc.rst
210source/api/nova..scheduler.chance.rst
211source/api/nova..scheduler.driver.rst
212source/api/nova..scheduler.manager.rst
213source/api/nova..scheduler.simple.rst
214source/api/nova..scheduler.zone.rst
215source/api/nova..service.rst
216source/api/nova..test.rst
217source/api/nova..tests.api.openstack.fakes.rst
218source/api/nova..tests.api.openstack.test_adminapi.rst
219source/api/nova..tests.api.openstack.test_api.rst
220source/api/nova..tests.api.openstack.test_auth.rst
221source/api/nova..tests.api.openstack.test_common.rst
222source/api/nova..tests.api.openstack.test_faults.rst
223source/api/nova..tests.api.openstack.test_flavors.rst
224source/api/nova..tests.api.openstack.test_images.rst
225source/api/nova..tests.api.openstack.test_ratelimiting.rst
226source/api/nova..tests.api.openstack.test_servers.rst
227source/api/nova..tests.api.openstack.test_shared_ip_groups.rst
228source/api/nova..tests.api.openstack.test_zones.rst
229source/api/nova..tests.api.test_wsgi.rst
230source/api/nova..tests.db.fakes.rst
231source/api/nova..tests.declare_flags.rst
232source/api/nova..tests.fake_flags.rst
233source/api/nova..tests.glance.stubs.rst
234source/api/nova..tests.hyperv_unittest.rst
235source/api/nova..tests.objectstore_unittest.rst
236source/api/nova..tests.real_flags.rst
237source/api/nova..tests.runtime_flags.rst
238source/api/nova..tests.test_access.rst
239source/api/nova..tests.test_api.rst
240source/api/nova..tests.test_auth.rst
241source/api/nova..tests.test_cloud.rst
242source/api/nova..tests.test_compute.rst
243source/api/nova..tests.test_console.rst
244source/api/nova..tests.test_direct.rst
245source/api/nova..tests.test_flags.rst
246source/api/nova..tests.test_instance_types.rst
247source/api/nova..tests.test_localization.rst
248source/api/nova..tests.test_log.rst
249source/api/nova..tests.test_middleware.rst
250source/api/nova..tests.test_misc.rst
251source/api/nova..tests.test_network.rst
252source/api/nova..tests.test_quota.rst
253source/api/nova..tests.test_rpc.rst
254source/api/nova..tests.test_scheduler.rst
255source/api/nova..tests.test_service.rst
256source/api/nova..tests.test_test.rst
257source/api/nova..tests.test_twistd.rst
258source/api/nova..tests.test_utils.rst
259source/api/nova..tests.test_virt.rst
260source/api/nova..tests.test_volume.rst
261source/api/nova..tests.test_xenapi.rst
262source/api/nova..tests.xenapi.stubs.rst
263source/api/nova..twistd.rst
264source/api/nova..utils.rst
265source/api/nova..version.rst
266source/api/nova..virt.connection.rst
267source/api/nova..virt.disk.rst
268source/api/nova..virt.fake.rst
269source/api/nova..virt.hyperv.rst
270source/api/nova..virt.images.rst
271source/api/nova..virt.libvirt_conn.rst
272source/api/nova..virt.xenapi.fake.rst
273source/api/nova..virt.xenapi.network_utils.rst
274source/api/nova..virt.xenapi.vm_utils.rst
275source/api/nova..virt.xenapi.vmops.rst
276source/api/nova..virt.xenapi.volume_utils.rst
277source/api/nova..virt.xenapi.volumeops.rst
278source/api/nova..virt.xenapi_conn.rst
279source/api/nova..volume.api.rst
280source/api/nova..volume.driver.rst
281source/api/nova..volume.manager.rst
282source/api/nova..volume.san.rst
283source/api/nova..wsgi.rst
2840
=== removed directory 'doc/build/html'
=== removed file 'doc/build/html/.buildinfo'
--- doc/build/html/.buildinfo 2011-02-21 20:30:20 +0000
+++ doc/build/html/.buildinfo 1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
1# Sphinx build info version 1
2# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
3config: 2a2fe6198f4be4a4d6f289b09d16d74a
4tags: fbb0d17656682115ca4d033fb2f83ba1
50
=== added file 'doc/source/devref/distributed_scheduler.rst'
--- doc/source/devref/distributed_scheduler.rst 1970-01-01 00:00:00 +0000
+++ doc/source/devref/distributed_scheduler.rst 2011-07-14 20:24:25 +0000
@@ -0,0 +1,188 @@
1..
2 Copyright 2011 OpenStack LLC
3 All Rights Reserved.
4
5 Licensed under the Apache License, Version 2.0 (the "License"); you may
6 not use this file except in compliance with the License. You may obtain
7 a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 License for the specific language governing permissions and limitations
15 under the License.
16
17 Source for illustrations in doc/source/image_src/zone_distsched_illustrations.odp
18 (OpenOffice Impress format) Illustrations are "exported" to png and then scaled
19 to 400x300 or 640x480 as needed and placed in the doc/source/images directory.
20
21Distributed Scheduler
22=====================
23
24The 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).
25
26 .. image:: /images/dating_service.png
27
28But 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.
29
30This 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.
31
32So, how does this all work?
33
34This document will explain the strategy employed by the `ZoneAwareScheduler` and its derivations. You should read the :doc:`devguide/zones` documentation before reading this.
35
36 .. image:: /images/zone_aware_scheduler.png
37
38Costs & Weights
39---------------
40When 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.
41
42Some 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.
43
44An example of some other costs might include selecting:
45 * a GPU-based host over a standard CPU
46 * a host with fast ethernet over a 10mbps line
47 * a host that can run Windows instances
48 * a host in the EU vs North America
49 * etc
50
51This 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.
52
53 .. image:: /images/costs_weights.png
54
55nova.scheduler.zone_aware_scheduler.ZoneAwareScheduler
56------------------------------------------------------
57As 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.
58
59Here is how it works:
60
61 1. The compute nodes are filtered and the nodes remaining are weighed.
62 2. Filtering the hosts is a simple matter of ensuring the compute node has ample resources (CPU, RAM, Disk, etc) to fulfil the request.
63 3. Weighing of the remaining compute nodes assigns a number based on their suitability for the request.
64 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.
65 5. The parent Zone sorts and aggregates all the weights and a final build plan is constructed.
66 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.
67
68 .. image:: /images/zone_aware_overview.png
69
70`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.
71
72Filtering and Weighing
73----------------------
74The 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.
75
76 .. image:: /images/filtering.png
77
78Requesting a new instance
79-------------------------
80Prior 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.
81
82`nova.compute.api.create()` performed the following actions:
83 1. it validated all the fields passed into it.
84 2. it created an entry in the `Instance` table for each instance requested
85 3. it put one `run_instance` message in the scheduler queue for each instance requested
86 4. the schedulers picked off the messages and decided which compute node should handle the request.
87 5. the `run_instance` message was forwarded to the compute node for processing and the instance is created.
88 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.
89
90 .. image:: /images/nova.compute.api.create.png
91
92Generally, the standard schedulers (like `ChanceScheduler` and `AvailabilityZoneScheduler`) only operate in the current Zone. They have no concept of child Zones.
93
94The 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.
95
96For 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:
97 1. it validates all the fields passed into it.
98 2. it creates a single `reservation_id` for all of instances created. This is a UUID.
99 3. it creates a single `run_instance` request in the scheduler queue
100 4. a scheduler picks the message off the queue and works on it.
101 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`.
102 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.
103 7. if the child Zone has its own child Zones, the `/zones/select` call will be sent down to them as well.
104 8. Finally, when all the estimates have bubbled back to the Zone that initiated the call, all the results are merged, sorted and processed.
105 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.
106 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`.
107
108 .. image:: /images/nova.compute.api.create_all_at_once.png
109
110The Catch
111---------
112This 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.
113
114When `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.
115
116Instead, 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`
117
118In the case of nested child Zones, each Zone re-encrypts the weighted list results and passes those values to the parent.
119
120Throughout 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.
121
122Reservation IDs
123---------------
124
125NOTE: The features described in this section are related to the up-coming 'merge-4' branch.
126
127The 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.
128
129NOTE: 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.
130
131We 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.
132
133Finally, 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.
134
135`python-novaclient` will be extended to support both of these changes.
136
137Host Filter
138-----------
139
140As 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.
141
142The 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:
143
144 * `nova.scheduler.host_filter.InstanceTypeFilter` provides host filtering based on the memory and disk size specified in the `InstanceType` record passed into `run_instance`.
145
146 * `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.
147
148To 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.
149
150Cost Scheduler Weighing
151-----------------------
152Every `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.
153
154Simple Zone Aware Scheduling
155----------------------------
156The 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.
157
158The `--scheduler_driver` flag is how you specify the scheduler class name.
159
160Flags
161-----
162
163All 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:
164
165::
166
167 --allow_admin_api=true
168 --enable_zone_routing=true
169 --zone_name=zone1
170 --build_plan_encryption_key=c286696d887c9aa0611bbb3e2025a45b
171 --scheduler_driver=nova.scheduler.host_filter.HostFilterScheduler
172 --default_host_filter=nova.scheduler.host_filter.AllHostsFilter
173
174`--allow_admin_api` must be set for OS API to enable the new `/zones/*` commands.
175`--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.
176`--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.
177`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.
178`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`.
179`default_host_filter` is the host filter to be used for filtering candidate Compute nodes.
180
181Some optional flags which are handy for debugging are:
182
183::
184
185 --connection_type=fake
186 --verbose
187
188Using 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.
0189
=== modified file 'doc/source/devref/index.rst'
--- doc/source/devref/index.rst 2011-01-04 22:58:08 +0000
+++ doc/source/devref/index.rst 2011-07-14 20:24:25 +0000
@@ -35,6 +35,7 @@
35.. toctree::35.. toctree::
36 :maxdepth: 336 :maxdepth: 3
3737
38 zone
38 rabbit39 rabbit
3940
40API Reference41API Reference
4142
=== added file 'doc/source/devref/multinic.rst'
--- doc/source/devref/multinic.rst 1970-01-01 00:00:00 +0000
+++ doc/source/devref/multinic.rst 2011-07-14 20:24:25 +0000
@@ -0,0 +1,39 @@
1MultiNic
2========
3
4What is it
5----------
6
7Multinic 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.
8
9Managers
10--------
11
12Each 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.
13
14On 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.
15
16Flat Manager
17------------
18
19 .. image:: /images/multinic_flat.png
20
21The 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.
22
23Each 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.
24
25Flat 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.
26
27FlatDHCP Manager
28----------------
29
30 .. image:: /images/multinic_dhcp.png
31
32FlatDHCP 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.
33
34VLAN Manager
35------------
36
37 .. image:: /images/multinic_vlan.png
38
39The 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.
040
=== modified file 'doc/source/devref/zone.rst'
--- doc/source/devref/zone.rst 2011-04-08 18:45:42 +0000
+++ doc/source/devref/zone.rst 2011-07-14 20:24:25 +0000
@@ -17,11 +17,11 @@
17Zones17Zones
18=====18=====
1919
20A 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.20A 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.
2121
22The 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. 22The 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.
2323
24Zones 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. 24Zones 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.
2525
26Zones share nothing. They communicate via the public OpenStack API only. No database, queue, user or project definition is shared between Zones. 26Zones share nothing. They communicate via the public OpenStack API only. No database, queue, user or project definition is shared between Zones.
2727
@@ -34,7 +34,7 @@
3434
35 key=value;value;value, key=value;value;value35 key=value;value;value, key=value;value;value
3636
37Zones 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.37Zones 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.
3838
39Flow within a Zone39Flow within a Zone
40------------------40------------------
@@ -47,7 +47,7 @@
4747
48These 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.48These 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.
4949
50The `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"). 50The `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").
5151
52Zone administrative functions52Zone administrative functions
53-----------------------------53-----------------------------
@@ -99,7 +99,7 @@
99 export NOVA_URL="http://192.168.2.120:8774/v1.0/"99 export NOVA_URL="http://192.168.2.120:8774/v1.0/"
100100
101101
102This 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. 102This 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.
103103
104Getting a list of child Zones104Getting a list of child Zones
105-----------------------------105-----------------------------
106106
=== added directory 'doc/source/image_src'
=== added file 'doc/source/image_src/multinic_1.odg'
107Binary 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 differ107Binary 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
=== added file 'doc/source/image_src/multinic_2.odg'
108Binary 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 differ108Binary 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
=== added file 'doc/source/image_src/multinic_3.odg'
109Binary 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 differ109Binary 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
=== added file 'doc/source/image_src/zones_distsched_illustrations.odp'
110Binary 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 differ110Binary 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
=== added file 'doc/source/images/costs_weights.png'
111Binary 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 differ111Binary 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
=== added file 'doc/source/images/dating_service.png'
112Binary 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 differ112Binary 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
=== added file 'doc/source/images/filtering.png'
113Binary 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 differ113Binary 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
=== added file 'doc/source/images/multinic_dhcp.png'
114Binary 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 differ114Binary 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
=== added file 'doc/source/images/multinic_flat.png'
115Binary 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 differ115Binary 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
=== added file 'doc/source/images/multinic_vlan.png'
116Binary 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 differ116Binary 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
=== added file 'doc/source/images/nova.compute.api.create.png'
117Binary 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 differ117Binary 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
=== added file 'doc/source/images/nova.compute.api.create_all_at_once.png'
118Binary 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 differ118Binary 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
=== added file 'doc/source/images/zone_aware_overview.png'
119Binary 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 differ119Binary 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
=== added file 'doc/source/images/zone_aware_scheduler.png'
120Binary 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 differ120Binary 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
=== modified file 'doc/source/man/novamanage.rst'
--- doc/source/man/novamanage.rst 2011-04-07 18:25:44 +0000
+++ doc/source/man/novamanage.rst 2011-07-14 20:24:25 +0000
@@ -6,7 +6,7 @@
6control and manage cloud computer instances and images6control and manage cloud computer instances and images
7------------------------------------------------------7------------------------------------------------------
88
9:Author: nova@lists.launchpad.net9:Author: openstack@lists.launchpad.net
10:Date: 2010-11-1610:Date: 2010-11-16
11:Copyright: OpenStack LLC11:Copyright: OpenStack LLC
12:Version: 0.112:Version: 0.1
@@ -121,7 +121,7 @@
121nova-manage role <action> [<argument>]121nova-manage role <action> [<argument>]
122``nova-manage role add <username> <rolename> <(optional) projectname>``122``nova-manage role add <username> <rolename> <(optional) projectname>``
123123
124 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.124 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.
125125
126``nova-manage role has <username> <projectname>``126``nova-manage role has <username> <projectname>``
127 Checks the user or project and responds with True if the user has a global role with a particular project.127 Checks the user or project and responds with True if the user has a global role with a particular project.
128128
=== modified file 'doc/source/runnova/managing.users.rst'
--- doc/source/runnova/managing.users.rst 2011-02-21 20:30:20 +0000
+++ doc/source/runnova/managing.users.rst 2011-07-14 20:24:25 +0000
@@ -38,11 +38,11 @@
3838
39Nova’s rights management system employs the RBAC model and currently supports the following five roles:39Nova’s rights management system employs the RBAC model and currently supports the following five roles:
4040
41* **Cloud Administrator.** (admin) Users of this class enjoy complete system access.41* **Cloud Administrator.** (cloudadmin) Users of this class enjoy complete system access.
42* **IT Security.** (itsec) This role is limited to IT security personnel. It permits role holders to quarantine instances.42* **IT Security.** (itsec) This role is limited to IT security personnel. It permits role holders to quarantine instances.
43* **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.43* **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.
44* **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.44* **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.
45* **Developer.** This is a general purpose role that is assigned to users by default.45* **Developer.** (developer) This is a general purpose role that is assigned to users by default.
4646
47RBAC management is exposed through the dashboard for simplified user management.47RBAC management is exposed through the dashboard for simplified user management.
4848
4949
=== modified file 'nova/__init__.py'
--- nova/__init__.py 2011-01-20 08:14:42 +0000
+++ nova/__init__.py 2011-07-14 20:24:25 +0000
@@ -30,3 +30,8 @@
30.. moduleauthor:: Manish Singh <yosh@gimp.org>30.. moduleauthor:: Manish Singh <yosh@gimp.org>
31.. moduleauthor:: Andy Smith <andy@anarkystic.com>31.. moduleauthor:: Andy Smith <andy@anarkystic.com>
32"""32"""
33
34import gettext
35
36
37gettext.install("nova", unicode=1)
3338
=== modified file 'nova/api/direct.py'
--- nova/api/direct.py 2011-04-11 16:34:19 +0000
+++ nova/api/direct.py 2011-07-14 20:24:25 +0000
@@ -42,6 +42,7 @@
42from nova import flags42from nova import flags
43from nova import utils43from nova import utils
44from nova import wsgi44from nova import wsgi
45import nova.api.openstack.wsgi
4546
4647
47# Global storage for registering modules.48# Global storage for registering modules.
@@ -251,7 +252,7 @@
251 return self._methods[method]252 return self._methods[method]
252253
253254
254class ServiceWrapper(wsgi.Controller):255class ServiceWrapper(object):
255 """Wrapper to dynamically povide a WSGI controller for arbitrary objects.256 """Wrapper to dynamically povide a WSGI controller for arbitrary objects.
256257
257 With lightweight introspection allows public methods on the object to258 With lightweight introspection allows public methods on the object to
@@ -265,7 +266,7 @@
265 def __init__(self, service_handle):266 def __init__(self, service_handle):
266 self.service_handle = service_handle267 self.service_handle = service_handle
267268
268 @webob.dec.wsgify(RequestClass=wsgi.Request)269 @webob.dec.wsgify(RequestClass=nova.api.openstack.wsgi.Request)
269 def __call__(self, req):270 def __call__(self, req):
270 arg_dict = req.environ['wsgiorg.routing_args'][1]271 arg_dict = req.environ['wsgiorg.routing_args'][1]
271 action = arg_dict['action']272 action = arg_dict['action']
@@ -289,8 +290,11 @@
289290
290 try:291 try:
291 content_type = req.best_match_content_type()292 content_type = req.best_match_content_type()
292 default_xmlns = self.get_default_xmlns(req)293 serializer = {
293 return self._serialize(result, content_type, default_xmlns)294 'application/xml': nova.api.openstack.wsgi.XMLDictSerializer(),
295 'application/json': nova.api.openstack.wsgi.JSONDictSerializer(),
296 }[content_type]
297 return serializer.serialize(result)
294 except:298 except:
295 raise exception.Error("returned non-serializable type: %s"299 raise exception.Error("returned non-serializable type: %s"
296 % result)300 % result)
@@ -320,7 +324,7 @@
320324
321 def __init__(self, proxy):325 def __init__(self, proxy):
322 self._proxy = proxy326 self._proxy = proxy
323 if not self.__doc__:327 if not self.__doc__: # pylint: disable=E0203
324 self.__doc__ = proxy.__doc__328 self.__doc__ = proxy.__doc__
325 if not self._allowed:329 if not self._allowed:
326 self._allowed = []330 self._allowed = []
327331
=== modified file 'nova/api/ec2/__init__.py'
--- nova/api/ec2/__init__.py 2011-04-29 22:06:18 +0000
+++ nova/api/ec2/__init__.py 2011-07-14 20:24:25 +0000
@@ -242,6 +242,7 @@
242 'CreateKeyPair': ['all'],242 'CreateKeyPair': ['all'],
243 'DeleteKeyPair': ['all'],243 'DeleteKeyPair': ['all'],
244 'DescribeSecurityGroups': ['all'],244 'DescribeSecurityGroups': ['all'],
245 'ImportPublicKey': ['all'],
245 'AuthorizeSecurityGroupIngress': ['netadmin'],246 'AuthorizeSecurityGroupIngress': ['netadmin'],
246 'RevokeSecurityGroupIngress': ['netadmin'],247 'RevokeSecurityGroupIngress': ['netadmin'],
247 'CreateSecurityGroup': ['netadmin'],248 'CreateSecurityGroup': ['netadmin'],
@@ -327,6 +328,12 @@
327 ec2_id = ec2utils.id_to_ec2_id(ex.volume_id, 'vol-%08x')328 ec2_id = ec2utils.id_to_ec2_id(ex.volume_id, 'vol-%08x')
328 message = _('Volume %s not found') % ec2_id329 message = _('Volume %s not found') % ec2_id
329 return self._error(req, context, type(ex).__name__, message)330 return self._error(req, context, type(ex).__name__, message)
331 except exception.SnapshotNotFound as ex:
332 LOG.info(_('SnapshotNotFound raised: %s'), unicode(ex),
333 context=context)
334 ec2_id = ec2utils.id_to_ec2_id(ex.snapshot_id, 'snap-%08x')
335 message = _('Snapshot %s not found') % ec2_id
336 return self._error(req, context, type(ex).__name__, message)
330 except exception.NotFound as ex:337 except exception.NotFound as ex:
331 LOG.info(_('NotFound raised: %s'), unicode(ex), context=context)338 LOG.info(_('NotFound raised: %s'), unicode(ex), context=context)
332 return self._error(req, context, type(ex).__name__, unicode(ex))339 return self._error(req, context, type(ex).__name__, unicode(ex))
@@ -338,6 +345,10 @@
338 else:345 else:
339 return self._error(req, context, type(ex).__name__,346 return self._error(req, context, type(ex).__name__,
340 unicode(ex))347 unicode(ex))
348 except exception.KeyPairExists as ex:
349 LOG.debug(_('KeyPairExists raised: %s'), unicode(ex),
350 context=context)
351 return self._error(req, context, type(ex).__name__, unicode(ex))
341 except Exception as ex:352 except Exception as ex:
342 extra = {'environment': req.environ}353 extra = {'environment': req.environ}
343 LOG.exception(_('Unexpected error raised: %s'), unicode(ex),354 LOG.exception(_('Unexpected error raised: %s'), unicode(ex),
344355
=== modified file 'nova/api/ec2/admin.py'
--- nova/api/ec2/admin.py 2011-04-19 16:19:52 +0000
+++ nova/api/ec2/admin.py 2011-07-14 20:24:25 +0000
@@ -22,7 +22,10 @@
2222
23import base6423import base64
24import datetime24import datetime
25import netaddr
26import urllib
2527
28from nova import compute
26from nova import db29from nova import db
27from nova import exception30from nova import exception
28from nova import flags31from nova import flags
@@ -118,6 +121,9 @@
118 def __str__(self):121 def __str__(self):
119 return 'AdminController'122 return 'AdminController'
120123
124 def __init__(self):
125 self.compute_api = compute.API()
126
121 def describe_instance_types(self, context, **_kwargs):127 def describe_instance_types(self, context, **_kwargs):
122 """Returns all active instance types data (vcpus, memory, etc.)"""128 """Returns all active instance types data (vcpus, memory, etc.)"""
123 return {'instanceTypeSet': [instance_dict(v) for v in129 return {'instanceTypeSet': [instance_dict(v) for v in
@@ -305,7 +311,7 @@
305 * Volume Count311 * Volume Count
306 """312 """
307 services = db.service_get_all(context, False)313 services = db.service_get_all(context, False)
308 now = datetime.datetime.utcnow()314 now = utils.utcnow()
309 hosts = []315 hosts = []
310 rv = []316 rv = []
311 for host in [service['host'] for service in services]:317 for host in [service['host'] for service in services]:
@@ -326,6 +332,60 @@
326 now))332 now))
327 return {'hosts': rv}333 return {'hosts': rv}
328334
329 def describe_host(self, _context, name, **_kwargs):335 def _provider_fw_rule_exists(self, context, rule):
330 """Returns status info for single node."""336 # TODO(todd): we call this repeatedly, can we filter by protocol?
331 return host_dict(db.host_get(name))337 for old_rule in db.provider_fw_rule_get_all(context):
338 if all([rule[k] == old_rule[k] for k in ('cidr', 'from_port',
339 'to_port', 'protocol')]):
340 return True
341 return False
342
343 def block_external_addresses(self, context, cidr):
344 """Add provider-level firewall rules to block incoming traffic."""
345 LOG.audit(_('Blocking traffic to all projects incoming from %s'),
346 cidr, context=context)
347 cidr = urllib.unquote(cidr).decode()
348 # raise if invalid
349 netaddr.IPNetwork(cidr)
350 rule = {'cidr': cidr}
351 tcp_rule = rule.copy()
352 tcp_rule.update({'protocol': 'tcp', 'from_port': 1, 'to_port': 65535})
353 udp_rule = rule.copy()
354 udp_rule.update({'protocol': 'udp', 'from_port': 1, 'to_port': 65535})
355 icmp_rule = rule.copy()
356 icmp_rule.update({'protocol': 'icmp', 'from_port': -1,
357 'to_port': None})
358 rules_added = 0
359 if not self._provider_fw_rule_exists(context, tcp_rule):
360 db.provider_fw_rule_create(context, tcp_rule)
361 rules_added += 1
362 if not self._provider_fw_rule_exists(context, udp_rule):
363 db.provider_fw_rule_create(context, udp_rule)
364 rules_added += 1
365 if not self._provider_fw_rule_exists(context, icmp_rule):
366 db.provider_fw_rule_create(context, icmp_rule)
367 rules_added += 1
368 if not rules_added:
369 raise exception.ApiError(_('Duplicate rule'))
370 self.compute_api.trigger_provider_fw_rules_refresh(context)
371 return {'status': 'OK', 'message': 'Added %s rules' % rules_added}
372
373 def describe_external_address_blocks(self, context):
374 blocks = db.provider_fw_rule_get_all(context)
375 # NOTE(todd): use a set since we have icmp/udp/tcp rules with same cidr
376 blocks = set([b.cidr for b in blocks])
377 blocks = [{'cidr': b} for b in blocks]
378 return {'externalIpBlockInfo':
379 list(sorted(blocks, key=lambda k: k['cidr']))}
380
381 def remove_external_address_block(self, context, cidr):
382 LOG.audit(_('Removing ip block from %s'), cidr, context=context)
383 cidr = urllib.unquote(cidr).decode()
384 # raise if invalid
385 netaddr.IPNetwork(cidr)
386 rules = db.provider_fw_rule_get_all_by_cidr(context, cidr)
387 for rule in rules:
388 db.provider_fw_rule_destroy(context, rule['id'])
389 if rules:
390 self.compute_api.trigger_provider_fw_rules_refresh(context)
391 return {'status': 'OK', 'message': 'Deleted %s rules' % len(rules)}
332392
=== modified file 'nova/api/ec2/apirequest.py'
--- nova/api/ec2/apirequest.py 2011-04-18 20:53:09 +0000
+++ nova/api/ec2/apirequest.py 2011-07-14 20:24:25 +0000
@@ -21,22 +21,15 @@
21"""21"""
2222
23import datetime23import datetime
24import re
25# TODO(termie): replace minidom with etree24# TODO(termie): replace minidom with etree
26from xml.dom import minidom25from xml.dom import minidom
2726
28from nova import log as logging27from nova import log as logging
28from nova.api.ec2 import ec2utils
2929
30LOG = logging.getLogger("nova.api.request")30LOG = logging.getLogger("nova.api.request")
3131
3232
33_c2u = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
34
35
36def _camelcase_to_underscore(str):
37 return _c2u.sub(r'_\1', str).lower().strip('_')
38
39
40def _underscore_to_camelcase(str):33def _underscore_to_camelcase(str):
41 return ''.join([x[:1].upper() + x[1:] for x in str.split('_')])34 return ''.join([x[:1].upper() + x[1:] for x in str.split('_')])
4235
@@ -51,59 +44,6 @@
51 return datetimeobj.strftime("%Y-%m-%dT%H:%M:%SZ")44 return datetimeobj.strftime("%Y-%m-%dT%H:%M:%SZ")
5245
5346
54def _try_convert(value):
55 """Return a non-string from a string or unicode, if possible.
56
57 ============= =====================================================
58 When value is returns
59 ============= =====================================================
60 zero-length ''
61 'None' None
62 'True' True
63 'False' False
64 '0', '-0' 0
65 0xN, -0xN int from hex (postitive) (N is any number)
66 0bN, -0bN int from binary (positive) (N is any number)
67 * try conversion to int, float, complex, fallback value
68
69 """
70 if len(value) == 0:
71 return ''
72 if value == 'None':
73 return None
74 if value == 'True':
75 return True
76 if value == 'False':
77 return False
78 valueneg = value[1:] if value[0] == '-' else value
79 if valueneg == '0':
80 return 0
81 if valueneg == '':
82 return value
83 if valueneg[0] == '0':
84 if valueneg[1] in 'xX':
85 return int(value, 16)
86 elif valueneg[1] in 'bB':
87 return int(value, 2)
88 else:
89 try:
90 return int(value, 8)
91 except ValueError:
92 pass
93 try:
94 return int(value)
95 except ValueError:
96 pass
97 try:
98 return float(value)
99 except ValueError:
100 pass
101 try:
102 return complex(value)
103 except ValueError:
104 return value
105
106
107class APIRequest(object):47class APIRequest(object):
108 def __init__(self, controller, action, version, args):48 def __init__(self, controller, action, version, args):
109 self.controller = controller49 self.controller = controller
@@ -114,7 +54,7 @@
114 def invoke(self, context):54 def invoke(self, context):
115 try:55 try:
116 method = getattr(self.controller,56 method = getattr(self.controller,
117 _camelcase_to_underscore(self.action))57 ec2utils.camelcase_to_underscore(self.action))
118 except AttributeError:58 except AttributeError:
119 controller = self.controller59 controller = self.controller
120 action = self.action60 action = self.action
@@ -125,19 +65,7 @@
125 # and reraise as 400 error.65 # and reraise as 400 error.
126 raise Exception(_error)66 raise Exception(_error)
12767
128 args = {}68 args = ec2utils.dict_from_dotted_str(self.args.items())
129 for key, value in self.args.items():
130 parts = key.split(".")
131 key = _camelcase_to_underscore(parts[0])
132 if isinstance(value, str) or isinstance(value, unicode):
133 # NOTE(vish): Automatically convert strings back
134 # into their respective values
135 value = _try_convert(value)
136 if len(parts) > 1:
137 d = args.get(key, {})
138 d[parts[1]] = value
139 value = d
140 args[key] = value
14169
142 for key in args.keys():70 for key in args.keys():
143 # NOTE(vish): Turn numeric dict keys into lists71 # NOTE(vish): Turn numeric dict keys into lists
14472
=== modified file 'nova/api/ec2/cloud.py'
--- nova/api/ec2/cloud.py 2011-05-20 06:51:29 +0000
+++ nova/api/ec2/cloud.py 2011-07-14 20:24:25 +0000
@@ -23,8 +23,7 @@
23"""23"""
2424
25import base6425import base64
26import datetime26import netaddr
27import IPy
28import os27import os
29import urllib28import urllib
30import tempfile29import tempfile
@@ -40,6 +39,7 @@
40from nova import ipv639from nova import ipv6
41from nova import log as logging40from nova import log as logging
42from nova import network41from nova import network
42from nova import rpc
43from nova import utils43from nova import utils
44from nova import volume44from nova import volume
45from nova.api.ec2 import ec2utils45from nova.api.ec2 import ec2utils
@@ -86,8 +86,7 @@
86 self.volume_api = volume.API()86 self.volume_api = volume.API()
87 self.compute_api = compute.API(87 self.compute_api = compute.API(
88 network_api=self.network_api,88 network_api=self.network_api,
89 volume_api=self.volume_api,89 volume_api=self.volume_api)
90 hostname_factory=ec2utils.id_to_ec2_id)
91 self.setup()90 self.setup()
9291
93 def __str__(self):92 def __str__(self):
@@ -121,8 +120,8 @@
121 result = {}120 result = {}
122 for instance in self.compute_api.get_all(context,121 for instance in self.compute_api.get_all(context,
123 project_id=project_id):122 project_id=project_id):
124 if instance['fixed_ip']:123 if instance['fixed_ips']:
125 line = '%s slots=%d' % (instance['fixed_ip']['address'],124 line = '%s slots=%d' % (instance['fixed_ips'][0]['address'],
126 instance['vcpus'])125 instance['vcpus'])
127 key = str(instance['key_name'])126 key = str(instance['key_name'])
128 if key in result:127 if key in result:
@@ -137,6 +136,13 @@
137 return services[0]['availability_zone']136 return services[0]['availability_zone']
138 return 'unknown zone'137 return 'unknown zone'
139138
139 def _get_image_state(self, image):
140 # NOTE(vish): fallback status if image_state isn't set
141 state = image.get('status')
142 if state == 'active':
143 state = 'available'
144 return image['properties'].get('image_state', state)
145
140 def get_metadata(self, address):146 def get_metadata(self, address):
141 ctxt = context.get_admin_context()147 ctxt = context.get_admin_context()
142 instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address)148 instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address)
@@ -145,7 +151,7 @@
145151
146 # This ensures that all attributes of the instance152 # This ensures that all attributes of the instance
147 # are populated.153 # are populated.
148 instance_ref = db.instance_get(ctxt, instance_ref['id'])154 instance_ref = db.instance_get(ctxt, instance_ref[0]['id'])
149155
150 mpi = self._get_mpi_data(ctxt, instance_ref['project_id'])156 mpi = self._get_mpi_data(ctxt, instance_ref['project_id'])
151 if instance_ref['key_name']:157 if instance_ref['key_name']:
@@ -159,7 +165,10 @@
159 floating_ip = db.instance_get_floating_address(ctxt,165 floating_ip = db.instance_get_floating_address(ctxt,
160 instance_ref['id'])166 instance_ref['id'])
161 ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])167 ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])
162 image_ec2_id = self.image_ec2_id(instance_ref['image_id'])168 image_ec2_id = self.image_ec2_id(instance_ref['image_ref'])
169 security_groups = db.security_group_get_by_instance(ctxt,
170 instance_ref['id'])
171 security_groups = [x['name'] for x in security_groups]
163 data = {172 data = {
164 'user-data': base64.b64decode(instance_ref['user_data']),173 'user-data': base64.b64decode(instance_ref['user_data']),
165 'meta-data': {174 'meta-data': {
@@ -183,7 +192,7 @@
183 'public-ipv4': floating_ip or '',192 'public-ipv4': floating_ip or '',
184 'public-keys': keys,193 'public-keys': keys,
185 'reservation-id': instance_ref['reservation_id'],194 'reservation-id': instance_ref['reservation_id'],
186 'security-groups': '',195 'security-groups': security_groups,
187 'mpi': mpi}}196 'mpi': mpi}}
188197
189 for image_type in ['kernel', 'ramdisk']:198 for image_type in ['kernel', 'ramdisk']:
@@ -235,7 +244,7 @@
235 'zoneState': 'available'}]}244 'zoneState': 'available'}]}
236245
237 services = db.service_get_all(context, False)246 services = db.service_get_all(context, False)
238 now = datetime.datetime.utcnow()247 now = utils.utcnow()
239 hosts = []248 hosts = []
240 for host in [service['host'] for service in services]:249 for host in [service['host'] for service in services]:
241 if not host in hosts:250 if not host in hosts:
@@ -283,14 +292,50 @@
283 owner=None,292 owner=None,
284 restorable_by=None,293 restorable_by=None,
285 **kwargs):294 **kwargs):
286 return {'snapshotSet': [{'snapshotId': 'fixme',295 if snapshot_id:
287 'volumeId': 'fixme',296 snapshots = []
288 'status': 'fixme',297 for ec2_id in snapshot_id:
289 'startTime': 'fixme',298 internal_id = ec2utils.ec2_id_to_id(ec2_id)
290 'progress': 'fixme',299 snapshot = self.volume_api.get_snapshot(
291 'ownerId': 'fixme',300 context,
292 'volumeSize': 0,301 snapshot_id=internal_id)
293 'description': 'fixme'}]}302 snapshots.append(snapshot)
303 else:
304 snapshots = self.volume_api.get_all_snapshots(context)
305 snapshots = [self._format_snapshot(context, s) for s in snapshots]
306 return {'snapshotSet': snapshots}
307
308 def _format_snapshot(self, context, snapshot):
309 s = {}
310 s['snapshotId'] = ec2utils.id_to_ec2_id(snapshot['id'], 'snap-%08x')
311 s['volumeId'] = ec2utils.id_to_ec2_id(snapshot['volume_id'],
312 'vol-%08x')
313 s['status'] = snapshot['status']
314 s['startTime'] = snapshot['created_at']
315 s['progress'] = snapshot['progress']
316 s['ownerId'] = snapshot['project_id']
317 s['volumeSize'] = snapshot['volume_size']
318 s['description'] = snapshot['display_description']
319
320 s['display_name'] = snapshot['display_name']
321 s['display_description'] = snapshot['display_description']
322 return s
323
324 def create_snapshot(self, context, volume_id, **kwargs):
325 LOG.audit(_("Create snapshot of volume %s"), volume_id,
326 context=context)
327 volume_id = ec2utils.ec2_id_to_id(volume_id)
328 snapshot = self.volume_api.create_snapshot(
329 context,
330 volume_id=volume_id,
331 name=kwargs.get('display_name'),
332 description=kwargs.get('display_description'))
333 return self._format_snapshot(context, snapshot)
334
335 def delete_snapshot(self, context, snapshot_id, **kwargs):
336 snapshot_id = ec2utils.ec2_id_to_id(snapshot_id)
337 self.volume_api.delete_snapshot(context, snapshot_id=snapshot_id)
338 return True
294339
295 def describe_key_pairs(self, context, key_name=None, **kwargs):340 def describe_key_pairs(self, context, key_name=None, **kwargs):
296 key_pairs = db.key_pair_get_all_by_user(context, context.user_id)341 key_pairs = db.key_pair_get_all_by_user(context, context.user_id)
@@ -348,15 +393,21 @@
348 pass393 pass
349 return True394 return True
350395
351 def describe_security_groups(self, context, group_name=None, **kwargs):396 def describe_security_groups(self, context, group_name=None, group_id=None,
397 **kwargs):
352 self.compute_api.ensure_default_security_group(context)398 self.compute_api.ensure_default_security_group(context)
353 if group_name:399 if group_name or group_id:
354 groups = []400 groups = []
355 for name in group_name:401 if group_name:
356 group = db.security_group_get_by_name(context,402 for name in group_name:
357 context.project_id,403 group = db.security_group_get_by_name(context,
358 name)404 context.project_id,
359 groups.append(group)405 name)
406 groups.append(group)
407 if group_id:
408 for gid in group_id:
409 group = db.security_group_get(context, gid)
410 groups.append(group)
360 elif context.is_admin:411 elif context.is_admin:
361 groups = db.security_group_get_all(context)412 groups = db.security_group_get_all(context)
362 else:413 else:
@@ -409,7 +460,7 @@
409 elif cidr_ip:460 elif cidr_ip:
410 # If this fails, it throws an exception. This is what we want.461 # If this fails, it throws an exception. This is what we want.
411 cidr_ip = urllib.unquote(cidr_ip).decode()462 cidr_ip = urllib.unquote(cidr_ip).decode()
412 IPy.IP(cidr_ip)463 netaddr.IPNetwork(cidr_ip)
413 values['cidr'] = cidr_ip464 values['cidr'] = cidr_ip
414 else:465 else:
415 values['cidr'] = '0.0.0.0/0'466 values['cidr'] = '0.0.0.0/0'
@@ -454,13 +505,26 @@
454 return True505 return True
455 return False506 return False
456507
457 def revoke_security_group_ingress(self, context, group_name, **kwargs):508 def revoke_security_group_ingress(self, context, group_name=None,
458 LOG.audit(_("Revoke security group ingress %s"), group_name,509 group_id=None, **kwargs):
459 context=context)510 if not group_name and not group_id:
511 err = "Not enough parameters, need group_name or group_id"
512 raise exception.ApiError(_(err))
460 self.compute_api.ensure_default_security_group(context)513 self.compute_api.ensure_default_security_group(context)
461 security_group = db.security_group_get_by_name(context,514 notfound = exception.SecurityGroupNotFound
462 context.project_id,515 if group_name:
463 group_name)516 security_group = db.security_group_get_by_name(context,
517 context.project_id,
518 group_name)
519 if not security_group:
520 raise notfound(security_group_id=group_name)
521 if group_id:
522 security_group = db.security_group_get(context, group_id)
523 if not security_group:
524 raise notfound(security_group_id=group_id)
525
526 msg = "Revoke security group ingress %s"
527 LOG.audit(_(msg), security_group['name'], context=context)
464528
465 criteria = self._revoke_rule_args_to_dict(context, **kwargs)529 criteria = self._revoke_rule_args_to_dict(context, **kwargs)
466 if criteria is None:530 if criteria is None:
@@ -475,7 +539,7 @@
475 if match:539 if match:
476 db.security_group_rule_destroy(context, rule['id'])540 db.security_group_rule_destroy(context, rule['id'])
477 self.compute_api.trigger_security_group_rules_refresh(context,541 self.compute_api.trigger_security_group_rules_refresh(context,
478 security_group['id'])542 security_group_id=security_group['id'])
479 return True543 return True
480 raise exception.ApiError(_("No rule for the specified parameters."))544 raise exception.ApiError(_("No rule for the specified parameters."))
481545
@@ -483,14 +547,26 @@
483 # Unfortunately, it seems Boto is using an old API547 # Unfortunately, it seems Boto is using an old API
484 # for these operations, so support for newer API versions548 # for these operations, so support for newer API versions
485 # is sketchy.549 # is sketchy.
486 def authorize_security_group_ingress(self, context, group_name, **kwargs):550 def authorize_security_group_ingress(self, context, group_name=None,
487 LOG.audit(_("Authorize security group ingress %s"), group_name,551 group_id=None, **kwargs):
488 context=context)552 if not group_name and not group_id:
553 err = "Not enough parameters, need group_name or group_id"
554 raise exception.ApiError(_(err))
489 self.compute_api.ensure_default_security_group(context)555 self.compute_api.ensure_default_security_group(context)
490 security_group = db.security_group_get_by_name(context,556 notfound = exception.SecurityGroupNotFound
491 context.project_id,557 if group_name:
492 group_name)558 security_group = db.security_group_get_by_name(context,
559 context.project_id,
560 group_name)
561 if not security_group:
562 raise notfound(security_group_id=group_name)
563 if group_id:
564 security_group = db.security_group_get(context, group_id)
565 if not security_group:
566 raise notfound(security_group_id=group_id)
493567
568 msg = "Authorize security group ingress %s"
569 LOG.audit(_(msg), security_group['name'], context=context)
494 values = self._revoke_rule_args_to_dict(context, **kwargs)570 values = self._revoke_rule_args_to_dict(context, **kwargs)
495 if values is None:571 if values is None:
496 raise exception.ApiError(_("Not enough parameters to build a "572 raise exception.ApiError(_("Not enough parameters to build a "
@@ -504,7 +580,7 @@
504 security_group_rule = db.security_group_rule_create(context, values)580 security_group_rule = db.security_group_rule_create(context, values)
505581
506 self.compute_api.trigger_security_group_rules_refresh(context,582 self.compute_api.trigger_security_group_rules_refresh(context,
507 security_group['id'])583 security_group_id=security_group['id'])
508584
509 return True585 return True
510586
@@ -540,11 +616,23 @@
540 return {'securityGroupSet': [self._format_security_group(context,616 return {'securityGroupSet': [self._format_security_group(context,
541 group_ref)]}617 group_ref)]}
542618
543 def delete_security_group(self, context, group_name, **kwargs):619 def delete_security_group(self, context, group_name=None, group_id=None,
620 **kwargs):
621 if not group_name and not group_id:
622 err = "Not enough parameters, need group_name or group_id"
623 raise exception.ApiError(_(err))
624 notfound = exception.SecurityGroupNotFound
625 if group_name:
626 security_group = db.security_group_get_by_name(context,
627 context.project_id,
628 group_name)
629 if not security_group:
630 raise notfound(security_group_id=group_name)
631 elif group_id:
632 security_group = db.security_group_get(context, group_id)
633 if not security_group:
634 raise notfound(security_group_id=group_id)
544 LOG.audit(_("Delete security group %s"), group_name, context=context)635 LOG.audit(_("Delete security group %s"), group_name, context=context)
545 security_group = db.security_group_get_by_name(context,
546 context.project_id,
547 group_name)
548 db.security_group_destroy(context, security_group.id)636 db.security_group_destroy(context, security_group.id)
549 return True637 return True
550638
@@ -559,7 +647,7 @@
559 instance_id = ec2utils.ec2_id_to_id(ec2_id)647 instance_id = ec2utils.ec2_id_to_id(ec2_id)
560 output = self.compute_api.get_console_output(648 output = self.compute_api.get_console_output(
561 context, instance_id=instance_id)649 context, instance_id=instance_id)
562 now = datetime.datetime.utcnow()650 now = utils.utcnow()
563 return {"InstanceId": ec2_id,651 return {"InstanceId": ec2_id,
564 "Timestamp": now,652 "Timestamp": now,
565 "output": base64.b64encode(output)}653 "output": base64.b64encode(output)}
@@ -619,16 +707,30 @@
619 'volumeId': v['volumeId']}]707 'volumeId': v['volumeId']}]
620 else:708 else:
621 v['attachmentSet'] = [{}]709 v['attachmentSet'] = [{}]
710 if volume.get('snapshot_id') != None:
711 v['snapshotId'] = ec2utils.id_to_ec2_id(volume['snapshot_id'],
712 'snap-%08x')
713 else:
714 v['snapshotId'] = None
622715
623 v['display_name'] = volume['display_name']716 v['display_name'] = volume['display_name']
624 v['display_description'] = volume['display_description']717 v['display_description'] = volume['display_description']
625 return v718 return v
626719
627 def create_volume(self, context, size, **kwargs):720 def create_volume(self, context, **kwargs):
628 LOG.audit(_("Create volume of %s GB"), size, context=context)721 size = kwargs.get('size')
722 if kwargs.get('snapshot_id') != None:
723 snapshot_id = ec2utils.ec2_id_to_id(kwargs['snapshot_id'])
724 LOG.audit(_("Create volume from snapshot %s"), snapshot_id,
725 context=context)
726 else:
727 snapshot_id = None
728 LOG.audit(_("Create volume of %s GB"), size, context=context)
729
629 volume = self.volume_api.create(730 volume = self.volume_api.create(
630 context,731 context,
631 size=size,732 size=size,
733 snapshot_id=snapshot_id,
632 name=kwargs.get('display_name'),734 name=kwargs.get('display_name'),
633 description=kwargs.get('display_description'))735 description=kwargs.get('display_description'))
634 # TODO(vish): Instance should be None at db layer instead of736 # TODO(vish): Instance should be None at db layer instead of
@@ -724,27 +826,27 @@
724 instances = self.compute_api.get_all(context, **kwargs)826 instances = self.compute_api.get_all(context, **kwargs)
725 for instance in instances:827 for instance in instances:
726 if not context.is_admin:828 if not context.is_admin:
727 if instance['image_id'] == str(FLAGS.vpn_image_id):829 if instance['image_ref'] == str(FLAGS.vpn_image_id):
728 continue830 continue
729 i = {}831 i = {}
730 instance_id = instance['id']832 instance_id = instance['id']
731 ec2_id = ec2utils.id_to_ec2_id(instance_id)833 ec2_id = ec2utils.id_to_ec2_id(instance_id)
732 i['instanceId'] = ec2_id834 i['instanceId'] = ec2_id
733 i['imageId'] = self.image_ec2_id(instance['image_id'])835 i['imageId'] = self.image_ec2_id(instance['image_ref'])
734 i['instanceState'] = {836 i['instanceState'] = {
735 'code': instance['state'],837 'code': instance['state'],
736 'name': instance['state_description']}838 'name': instance['state_description']}
737 fixed_addr = None839 fixed_addr = None
738 floating_addr = None840 floating_addr = None
739 if instance['fixed_ip']:841 if instance['fixed_ips']:
740 fixed_addr = instance['fixed_ip']['address']842 fixed = instance['fixed_ips'][0]
741 if instance['fixed_ip']['floating_ips']:843 fixed_addr = fixed['address']
742 fixed = instance['fixed_ip']844 if fixed['floating_ips']:
743 floating_addr = fixed['floating_ips'][0]['address']845 floating_addr = fixed['floating_ips'][0]['address']
744 if instance['fixed_ip']['network'] and 'use_v6' in kwargs:846 if fixed['network'] and 'use_v6' in kwargs:
745 i['dnsNameV6'] = ipv6.to_global(847 i['dnsNameV6'] = ipv6.to_global(
746 instance['fixed_ip']['network']['cidr_v6'],848 fixed['network']['cidr_v6'],
747 instance['mac_address'],849 fixed['virtual_interface']['address'],
748 instance['project_id'])850 instance['project_id'])
749851
750 i['privateDnsName'] = fixed_addr852 i['privateDnsName'] = fixed_addr
@@ -816,8 +918,15 @@
816918
817 def allocate_address(self, context, **kwargs):919 def allocate_address(self, context, **kwargs):
818 LOG.audit(_("Allocate address"), context=context)920 LOG.audit(_("Allocate address"), context=context)
819 public_ip = self.network_api.allocate_floating_ip(context)921 try:
820 return {'publicIp': public_ip}922 public_ip = self.network_api.allocate_floating_ip(context)
923 return {'publicIp': public_ip}
924 except rpc.RemoteError as ex:
925 # NOTE(tr3buchet) - why does this block exist?
926 if ex.exc_type == 'NoMoreFloatingIps':
927 raise exception.NoMoreFloatingIps()
928 else:
929 raise
821930
822 def release_address(self, context, public_ip, **kwargs):931 def release_address(self, context, public_ip, **kwargs):
823 LOG.audit(_("Release address %s"), public_ip, context=context)932 LOG.audit(_("Release address %s"), public_ip, context=context)
@@ -846,10 +955,39 @@
846 if kwargs.get('ramdisk_id'):955 if kwargs.get('ramdisk_id'):
847 ramdisk = self._get_image(context, kwargs['ramdisk_id'])956 ramdisk = self._get_image(context, kwargs['ramdisk_id'])
848 kwargs['ramdisk_id'] = ramdisk['id']957 kwargs['ramdisk_id'] = ramdisk['id']
958 for bdm in kwargs.get('block_device_mapping', []):
959 # NOTE(yamahata)
960 # BlockDevicedMapping.<N>.DeviceName
961 # BlockDevicedMapping.<N>.Ebs.SnapshotId
962 # BlockDevicedMapping.<N>.Ebs.VolumeSize
963 # BlockDevicedMapping.<N>.Ebs.DeleteOnTermination
964 # BlockDevicedMapping.<N>.VirtualName
965 # => remove .Ebs and allow volume id in SnapshotId
966 ebs = bdm.pop('ebs', None)
967 if ebs:
968 ec2_id = ebs.pop('snapshot_id')
969 id = ec2utils.ec2_id_to_id(ec2_id)
970 if ec2_id.startswith('snap-'):
971 bdm['snapshot_id'] = id
972 elif ec2_id.startswith('vol-'):
973 bdm['volume_id'] = id
974 ebs.setdefault('delete_on_termination', True)
975 bdm.update(ebs)
976
977 image = self._get_image(context, kwargs['image_id'])
978
979 if image:
980 image_state = self._get_image_state(image)
981 else:
982 raise exception.ImageNotFound(image_id=kwargs['image_id'])
983
984 if image_state != 'available':
985 raise exception.ApiError(_('Image must be available'))
986
849 instances = self.compute_api.create(context,987 instances = self.compute_api.create(context,
850 instance_type=instance_types.get_instance_type_by_name(988 instance_type=instance_types.get_instance_type_by_name(
851 kwargs.get('instance_type', None)),989 kwargs.get('instance_type', None)),
852 image_id=self._get_image(context, kwargs['image_id'])['id'],990 image_href=self._get_image(context, kwargs['image_id'])['id'],
853 min_count=int(kwargs.get('min_count', max_count)),991 min_count=int(kwargs.get('min_count', max_count)),
854 max_count=max_count,992 max_count=max_count,
855 kernel_id=kwargs.get('kernel_id'),993 kernel_id=kwargs.get('kernel_id'),
@@ -860,37 +998,54 @@
860 user_data=kwargs.get('user_data'),998 user_data=kwargs.get('user_data'),
861 security_group=kwargs.get('security_group'),999 security_group=kwargs.get('security_group'),
862 availability_zone=kwargs.get('placement', {}).get(1000 availability_zone=kwargs.get('placement', {}).get(
863 'AvailabilityZone'))1001 'AvailabilityZone'),
1002 block_device_mapping=kwargs.get('block_device_mapping', {}))
864 return self._format_run_instances(context,1003 return self._format_run_instances(context,
865 instances[0]['reservation_id'])1004 instances[0]['reservation_id'])
8661005
1006 def _do_instance(self, action, context, ec2_id):
1007 instance_id = ec2utils.ec2_id_to_id(ec2_id)
1008 action(context, instance_id=instance_id)
1009
1010 def _do_instances(self, action, context, instance_id):
1011 for ec2_id in instance_id:
1012 self._do_instance(action, context, ec2_id)
1013
867 def terminate_instances(self, context, instance_id, **kwargs):1014 def terminate_instances(self, context, instance_id, **kwargs):
868 """Terminate each instance in instance_id, which is a list of ec2 ids.1015 """Terminate each instance in instance_id, which is a list of ec2 ids.
869 instance_id is a kwarg so its name cannot be modified."""1016 instance_id is a kwarg so its name cannot be modified."""
870 LOG.debug(_("Going to start terminating instances"))1017 LOG.debug(_("Going to start terminating instances"))
871 for ec2_id in instance_id:1018 self._do_instances(self.compute_api.delete, context, instance_id)
872 instance_id = ec2utils.ec2_id_to_id(ec2_id)
873 self.compute_api.delete(context, instance_id=instance_id)
874 return True1019 return True
8751020
876 def reboot_instances(self, context, instance_id, **kwargs):1021 def reboot_instances(self, context, instance_id, **kwargs):
877 """instance_id is a list of instance ids"""1022 """instance_id is a list of instance ids"""
878 LOG.audit(_("Reboot instance %r"), instance_id, context=context)1023 LOG.audit(_("Reboot instance %r"), instance_id, context=context)
879 for ec2_id in instance_id:1024 self._do_instances(self.compute_api.reboot, context, instance_id)
880 instance_id = ec2utils.ec2_id_to_id(ec2_id)1025 return True
881 self.compute_api.reboot(context, instance_id=instance_id)1026
1027 def stop_instances(self, context, instance_id, **kwargs):
1028 """Stop each instances in instance_id.
1029 Here instance_id is a list of instance ids"""
1030 LOG.debug(_("Going to stop instances"))
1031 self._do_instances(self.compute_api.stop, context, instance_id)
1032 return True
1033
1034 def start_instances(self, context, instance_id, **kwargs):
1035 """Start each instances in instance_id.
1036 Here instance_id is a list of instance ids"""
1037 LOG.debug(_("Going to start instances"))
1038 self._do_instances(self.compute_api.start, context, instance_id)
882 return True1039 return True
8831040
884 def rescue_instance(self, context, instance_id, **kwargs):1041 def rescue_instance(self, context, instance_id, **kwargs):
885 """This is an extension to the normal ec2_api"""1042 """This is an extension to the normal ec2_api"""
886 instance_id = ec2utils.ec2_id_to_id(instance_id)1043 self._do_instance(self.compute_api.rescue, contect, instnace_id)
887 self.compute_api.rescue(context, instance_id=instance_id)
888 return True1044 return True
8891045
890 def unrescue_instance(self, context, instance_id, **kwargs):1046 def unrescue_instance(self, context, instance_id, **kwargs):
891 """This is an extension to the normal ec2_api"""1047 """This is an extension to the normal ec2_api"""
892 instance_id = ec2utils.ec2_id_to_id(instance_id)1048 self._do_instance(self.compute_api.unrescue, context, instance_id)
893 self.compute_api.unrescue(context, instance_id=instance_id)
894 return True1049 return True
8951050
896 def update_instance(self, context, instance_id, **kwargs):1051 def update_instance(self, context, instance_id, **kwargs):
@@ -901,7 +1056,8 @@
901 changes[field] = kwargs[field]1056 changes[field] = kwargs[field]
902 if changes:1057 if changes:
903 instance_id = ec2utils.ec2_id_to_id(instance_id)1058 instance_id = ec2utils.ec2_id_to_id(instance_id)
904 self.compute_api.update(context, instance_id=instance_id, **kwargs)1059 self.compute_api.update(context, instance_id=instance_id,
1060 **changes)
905 return True1061 return True
9061062
907 @staticmethod1063 @staticmethod
@@ -925,17 +1081,26 @@
925 def image_ec2_id(image_id, image_type='ami'):1081 def image_ec2_id(image_id, image_type='ami'):
926 """Returns image ec2_id using id and three letter type."""1082 """Returns image ec2_id using id and three letter type."""
927 template = image_type + '-%08x'1083 template = image_type + '-%08x'
928 return ec2utils.id_to_ec2_id(int(image_id), template=template)1084 try:
1085 return ec2utils.id_to_ec2_id(int(image_id), template=template)
1086 except ValueError:
1087 #TODO(wwolf): once we have ec2_id -> glance_id mapping
1088 # in place, this wont be necessary
1089 return "ami-00000000"
9291090
930 def _get_image(self, context, ec2_id):1091 def _get_image(self, context, ec2_id):
931 try:1092 try:
932 internal_id = ec2utils.ec2_id_to_id(ec2_id)1093 internal_id = ec2utils.ec2_id_to_id(ec2_id)
933 return self.image_service.show(context, internal_id)1094 image = self.image_service.show(context, internal_id)
934 except (exception.InvalidEc2Id, exception.ImageNotFound):1095 except (exception.InvalidEc2Id, exception.ImageNotFound):
935 try:1096 try:
936 return self.image_service.show_by_name(context, ec2_id)1097 return self.image_service.show_by_name(context, ec2_id)
937 except exception.NotFound:1098 except exception.NotFound:
938 raise exception.ImageNotFound(image_id=ec2_id)1099 raise exception.ImageNotFound(image_id=ec2_id)
1100 image_type = ec2_id.split('-')[0]
1101 if self._image_type(image.get('container_format')) != image_type:
1102 raise exception.ImageNotFound(image_id=ec2_id)
1103 return image
9391104
940 def _format_image(self, image):1105 def _format_image(self, image):
941 """Convert from format defined by BaseImageService to S3 format."""1106 """Convert from format defined by BaseImageService to S3 format."""
@@ -956,11 +1121,8 @@
956 get('image_location'), name)1121 get('image_location'), name)
957 else:1122 else:
958 i['imageLocation'] = image['properties'].get('image_location')1123 i['imageLocation'] = image['properties'].get('image_location')
959 # NOTE(vish): fallback status if image_state isn't set1124
960 state = image.get('status')1125 i['imageState'] = self._get_image_state(image)
961 if state == 'active':
962 state = 'available'
963 i['imageState'] = image['properties'].get('image_state', state)
964 i['displayName'] = name1126 i['displayName'] = name
965 i['description'] = image.get('description')1127 i['description'] = image.get('description')
966 display_mapping = {'aki': 'kernel',1128 display_mapping = {'aki': 'kernel',
9671129
=== modified file 'nova/api/ec2/ec2utils.py'
--- nova/api/ec2/ec2utils.py 2011-05-11 18:02:01 +0000
+++ nova/api/ec2/ec2utils.py 2011-07-14 20:24:25 +0000
@@ -16,6 +16,8 @@
16# License for the specific language governing permissions and limitations16# License for the specific language governing permissions and limitations
17# under the License.17# under the License.
1818
19import re
20
19from nova import exception21from nova import exception
2022
2123
@@ -30,3 +32,95 @@
30def id_to_ec2_id(instance_id, template='i-%08x'):32def id_to_ec2_id(instance_id, template='i-%08x'):
31 """Convert an instance ID (int) to an ec2 ID (i-[base 16 number])"""33 """Convert an instance ID (int) to an ec2 ID (i-[base 16 number])"""
32 return template % instance_id34 return template % instance_id
35
36
37_c2u = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
38
39
40def camelcase_to_underscore(str):
41 return _c2u.sub(r'_\1', str).lower().strip('_')
42
43
44def _try_convert(value):
45 """Return a non-string from a string or unicode, if possible.
46
47 ============= =====================================================
48 When value is returns
49 ============= =====================================================
50 zero-length ''
51 'None' None
52 'True' True case insensitive
53 'False' False case insensitive
54 '0', '-0' 0
55 0xN, -0xN int from hex (postitive) (N is any number)
56 0bN, -0bN int from binary (positive) (N is any number)
57 * try conversion to int, float, complex, fallback value
58
59 """
60 if len(value) == 0:
61 return ''
62 if value == 'None':
63 return None
64 lowered_value = value.lower()
65 if lowered_value == 'true':
66 return True
67 if lowered_value == 'false':
68 return False
69 valueneg = value[1:] if value[0] == '-' else value
70 if valueneg == '0':
71 return 0
72 if valueneg == '':
73 return value
74 if valueneg[0] == '0':
75 if valueneg[1] in 'xX':
76 return int(value, 16)
77 elif valueneg[1] in 'bB':
78 return int(value, 2)
79 else:
80 try:
81 return int(value, 8)
82 except ValueError:
83 pass
84 try:
85 return int(value)
86 except ValueError:
87 pass
88 try:
89 return float(value)
90 except ValueError:
91 pass
92 try:
93 return complex(value)
94 except ValueError:
95 return value
96
97
98def dict_from_dotted_str(items):
99 """parse multi dot-separated argument into dict.
100 EBS boot uses multi dot-separeted arguments like
101 BlockDeviceMapping.1.DeviceName=snap-id
102 Convert the above into
103 {'block_device_mapping': {'1': {'device_name': snap-id}}}
104 """
105 args = {}
106 for key, value in items:
107 parts = key.split(".")
108 key = camelcase_to_underscore(parts[0])
109 if isinstance(value, str) or isinstance(value, unicode):
110 # NOTE(vish): Automatically convert strings back
111 # into their respective values
112 value = _try_convert(value)
113
114 if len(parts) > 1:
115 d = args.get(key, {})
116 args[key] = d
117 for k in parts[1:-1]:
118 k = camelcase_to_underscore(k)
119 v = d.get(k, {})
120 d[k] = v
121 d = v
122 d[camelcase_to_underscore(parts[-1])] = value
123 else:
124 args[key] = value
125
126 return args
33127
=== modified file 'nova/api/ec2/metadatarequesthandler.py'
--- nova/api/ec2/metadatarequesthandler.py 2011-03-03 16:04:33 +0000
+++ nova/api/ec2/metadatarequesthandler.py 2011-07-14 20:24:25 +0000
@@ -23,6 +23,7 @@
2323
24from nova import log as logging24from nova import log as logging
25from nova import flags25from nova import flags
26from nova import utils
26from nova import wsgi27from nova import wsgi
27from nova.api.ec2 import cloud28from nova.api.ec2 import cloud
2829
@@ -34,6 +35,9 @@
34class MetadataRequestHandler(wsgi.Application):35class MetadataRequestHandler(wsgi.Application):
35 """Serve metadata from the EC2 API."""36 """Serve metadata from the EC2 API."""
3637
38 def __init__(self):
39 self.cc = cloud.CloudController()
40
37 def print_data(self, data):41 def print_data(self, data):
38 if isinstance(data, dict):42 if isinstance(data, dict):
39 output = ''43 output = ''
@@ -67,11 +71,18 @@
6771
68 @webob.dec.wsgify(RequestClass=wsgi.Request)72 @webob.dec.wsgify(RequestClass=wsgi.Request)
69 def __call__(self, req):73 def __call__(self, req):
70 cc = cloud.CloudController()
71 remote_address = req.remote_addr74 remote_address = req.remote_addr
72 if FLAGS.use_forwarded_for:75 if FLAGS.use_forwarded_for:
73 remote_address = req.headers.get('X-Forwarded-For', remote_address)76 remote_address = req.headers.get('X-Forwarded-For', remote_address)
74 meta_data = cc.get_metadata(remote_address)77 try:
78 meta_data = self.cc.get_metadata(remote_address)
79 except Exception:
80 LOG.exception(_('Failed to get metadata for ip: %s'),
81 remote_address)
82 msg = _('An unknown error has occurred. '
83 'Please try your request again.')
84 exc = webob.exc.HTTPInternalServerError(explanation=unicode(msg))
85 return exc
75 if meta_data is None:86 if meta_data is None:
76 LOG.error(_('Failed to get metadata for ip: %s'), remote_address)87 LOG.error(_('Failed to get metadata for ip: %s'), remote_address)
77 raise webob.exc.HTTPNotFound()88 raise webob.exc.HTTPNotFound()
7889
=== modified file 'nova/api/openstack/__init__.py'
--- nova/api/openstack/__init__.py 2011-05-13 15:17:19 +0000
+++ nova/api/openstack/__init__.py 2011-07-14 20:24:25 +0000
@@ -26,7 +26,7 @@
2626
27from nova import flags27from nova import flags
28from nova import log as logging28from nova import log as logging
29from nova import wsgi29from nova import wsgi as base_wsgi
30from nova.api.openstack import accounts30from nova.api.openstack import accounts
31from nova.api.openstack import faults31from nova.api.openstack import faults
32from nova.api.openstack import backup_schedules32from nova.api.openstack import backup_schedules
@@ -40,6 +40,7 @@
40from nova.api.openstack import server_metadata40from nova.api.openstack import server_metadata
41from nova.api.openstack import shared_ip_groups41from nova.api.openstack import shared_ip_groups
42from nova.api.openstack import users42from nova.api.openstack import users
43from nova.api.openstack import wsgi
43from nova.api.openstack import zones44from nova.api.openstack import zones
4445
4546
@@ -50,7 +51,7 @@
50 'When True, this API service will accept admin operations.')51 'When True, this API service will accept admin operations.')
5152
5253
53class FaultWrapper(wsgi.Middleware):54class FaultWrapper(base_wsgi.Middleware):
54 """Calls down the middleware stack, making exceptions into faults."""55 """Calls down the middleware stack, making exceptions into faults."""
5556
56 @webob.dec.wsgify(RequestClass=wsgi.Request)57 @webob.dec.wsgify(RequestClass=wsgi.Request)
@@ -63,7 +64,7 @@
63 return faults.Fault(exc)64 return faults.Fault(exc)
6465
6566
66class APIRouter(wsgi.Router):67class APIRouter(base_wsgi.Router):
67 """68 """
68 Routes requests on the OpenStack API to the appropriate controller69 Routes requests on the OpenStack API to the appropriate controller
69 and method.70 and method.
@@ -80,7 +81,9 @@
80 self._setup_routes(mapper)81 self._setup_routes(mapper)
81 super(APIRouter, self).__init__(mapper)82 super(APIRouter, self).__init__(mapper)
8283
83 def _setup_routes(self, mapper):84 def _setup_routes(self, mapper, version):
85 """Routes common to all versions."""
86
84 server_members = self.server_members87 server_members = self.server_members
85 server_members['action'] = 'POST'88 server_members['action'] = 'POST'
86 if FLAGS.allow_admin_api:89 if FLAGS.allow_admin_api:
@@ -97,21 +100,41 @@
97 server_members['reset_network'] = 'POST'100 server_members['reset_network'] = 'POST'
98 server_members['inject_network_info'] = 'POST'101 server_members['inject_network_info'] = 'POST'
99102
100 mapper.resource("zone", "zones", controller=zones.Controller(),103 mapper.resource("user", "users",
101 collection={'detail': 'GET', 'info': 'GET',104 controller=users.create_resource(),
102 'select': 'GET'})
103
104 mapper.resource("user", "users", controller=users.Controller(),
105 collection={'detail': 'GET'})105 collection={'detail': 'GET'})
106106
107 mapper.resource("account", "accounts",107 mapper.resource("account", "accounts",
108 controller=accounts.Controller(),108 controller=accounts.create_resource(),
109 collection={'detail': 'GET'})109 collection={'detail': 'GET'})
110110
111 mapper.resource("zone", "zones",
112 controller=zones.create_resource(version),
113 collection={'detail': 'GET',
114 'info': 'GET',
115 'select': 'POST',
116 'boot': 'POST'})
117
111 mapper.resource("console", "consoles",118 mapper.resource("console", "consoles",
112 controller=consoles.Controller(),119 controller=consoles.create_resource(),
113 parent_resource=dict(member_name='server',120 parent_resource=dict(member_name='server',
114 collection_name='servers'))121 collection_name='servers'))
122
123 mapper.resource("server", "servers",
124 controller=servers.create_resource(version),
125 collection={'detail': 'GET'},
126 member=self.server_members)
127
128 mapper.resource("image", "images",
129 controller=images.create_resource(version),
130 collection={'detail': 'GET'})
131
132 mapper.resource("limit", "limits",
133 controller=limits.create_resource(version))
134
135 mapper.resource("flavor", "flavors",
136 controller=flavors.create_resource(version),
137 collection={'detail': 'GET'})
115138
116 super(APIRouter, self).__init__(mapper)139 super(APIRouter, self).__init__(mapper)
117140
@@ -120,33 +143,21 @@
120 """Define routes specific to OpenStack API V1.0."""143 """Define routes specific to OpenStack API V1.0."""
121144
122 def _setup_routes(self, mapper):145 def _setup_routes(self, mapper):
123 super(APIRouterV10, self)._setup_routes(mapper)146 super(APIRouterV10, self)._setup_routes(mapper, '1.0')
124 mapper.resource("server", "servers",
125 controller=servers.ControllerV10(),
126 collection={'detail': 'GET'},
127 member=self.server_members)
128
129 mapper.resource("image", "images",147 mapper.resource("image", "images",
130 controller=images.ControllerV10(),148 controller=images.create_resource('1.0'),
131 collection={'detail': 'GET'})
132
133 mapper.resource("flavor", "flavors",
134 controller=flavors.ControllerV10(),
135 collection={'detail': 'GET'})149 collection={'detail': 'GET'})
136150
137 mapper.resource("shared_ip_group", "shared_ip_groups",151 mapper.resource("shared_ip_group", "shared_ip_groups",
138 collection={'detail': 'GET'},152 collection={'detail': 'GET'},
139 controller=shared_ip_groups.Controller())153 controller=shared_ip_groups.create_resource())
140154
141 mapper.resource("backup_schedule", "backup_schedule",155 mapper.resource("backup_schedule", "backup_schedule",
142 controller=backup_schedules.Controller(),156 controller=backup_schedules.create_resource(),
143 parent_resource=dict(member_name='server',157 parent_resource=dict(member_name='server',
144 collection_name='servers'))158 collection_name='servers'))
145159
146 mapper.resource("limit", "limits",160 mapper.resource("ip", "ips", controller=ips.create_resource(),
147 controller=limits.LimitsControllerV10())
148
149 mapper.resource("ip", "ips", controller=ips.Controller(),
150 collection=dict(public='GET', private='GET'),161 collection=dict(public='GET', private='GET'),
151 parent_resource=dict(member_name='server',162 parent_resource=dict(member_name='server',
152 collection_name='servers'))163 collection_name='servers'))
@@ -156,29 +167,13 @@
156 """Define routes specific to OpenStack API V1.1."""167 """Define routes specific to OpenStack API V1.1."""
157168
158 def _setup_routes(self, mapper):169 def _setup_routes(self, mapper):
159 super(APIRouterV11, self)._setup_routes(mapper)170 super(APIRouterV11, self)._setup_routes(mapper, '1.1')
160 mapper.resource("server", "servers",
161 controller=servers.ControllerV11(),
162 collection={'detail': 'GET'},
163 member=self.server_members)
164
165 mapper.resource("image", "images",
166 controller=images.ControllerV11(),
167 collection={'detail': 'GET'})
168
169 mapper.resource("image_meta", "meta",171 mapper.resource("image_meta", "meta",
170 controller=image_metadata.Controller(),172 controller=image_metadata.create_resource(),
171 parent_resource=dict(member_name='image',173 parent_resource=dict(member_name='image',
172 collection_name='images'))174 collection_name='images'))
173175
174 mapper.resource("server_meta", "meta",176 mapper.resource("server_meta", "meta",
175 controller=server_metadata.Controller(),177 controller=server_metadata.create_resource(),
176 parent_resource=dict(member_name='server',178 parent_resource=dict(member_name='server',
177 collection_name='servers'))179 collection_name='servers'))
178
179 mapper.resource("flavor", "flavors",
180 controller=flavors.ControllerV11(),
181 collection={'detail': 'GET'})
182
183 mapper.resource("limit", "limits",
184 controller=limits.LimitsControllerV11())
185180
=== modified file 'nova/api/openstack/accounts.py'
--- nova/api/openstack/accounts.py 2011-04-27 21:03:05 +0000
+++ nova/api/openstack/accounts.py 2011-07-14 20:24:25 +0000
@@ -20,8 +20,9 @@
20from nova import log as logging20from nova import log as logging
2121
22from nova.auth import manager22from nova.auth import manager
23from nova.api.openstack import common
24from nova.api.openstack import faults23from nova.api.openstack import faults
24from nova.api.openstack import wsgi
25
2526
26FLAGS = flags.FLAGS27FLAGS = flags.FLAGS
27LOG = logging.getLogger('nova.api.openstack')28LOG = logging.getLogger('nova.api.openstack')
@@ -34,12 +35,7 @@
34 manager=account.project_manager_id)35 manager=account.project_manager_id)
3536
3637
37class Controller(common.OpenstackController):38class Controller(object):
38
39 _serialization_metadata = {
40 'application/xml': {
41 "attributes": {
42 "account": ["id", "name", "description", "manager"]}}}
4339
44 def __init__(self):40 def __init__(self):
45 self.manager = manager.AuthManager()41 self.manager = manager.AuthManager()
@@ -66,20 +62,33 @@
66 self.manager.delete_project(id)62 self.manager.delete_project(id)
67 return {}63 return {}
6864
69 def create(self, req):65 def create(self, req, body):
70 """We use update with create-or-update semantics66 """We use update with create-or-update semantics
71 because the id comes from an external source"""67 because the id comes from an external source"""
72 raise faults.Fault(webob.exc.HTTPNotImplemented())68 raise faults.Fault(webob.exc.HTTPNotImplemented())
7369
74 def update(self, req, id):70 def update(self, req, id, body):
75 """This is really create or update."""71 """This is really create or update."""
76 self._check_admin(req.environ['nova.context'])72 self._check_admin(req.environ['nova.context'])
77 env = self._deserialize(req.body, req.get_content_type())73 description = body['account'].get('description')
78 description = env['account'].get('description')74 manager = body['account'].get('manager')
79 manager = env['account'].get('manager')
80 try:75 try:
81 account = self.manager.get_project(id)76 account = self.manager.get_project(id)
82 self.manager.modify_project(id, manager, description)77 self.manager.modify_project(id, manager, description)
83 except exception.NotFound:78 except exception.NotFound:
84 account = self.manager.create_project(id, manager, description)79 account = self.manager.create_project(id, manager, description)
85 return dict(account=_translate_keys(account))80 return dict(account=_translate_keys(account))
81
82
83def create_resource():
84 metadata = {
85 "attributes": {
86 "account": ["id", "name", "description", "manager"],
87 },
88 }
89
90 body_serializers = {
91 'application/xml': wsgi.XMLDictSerializer(metadata=metadata),
92 }
93 serializer = wsgi.ResponseSerializer(body_serializers)
94 return wsgi.Resource(Controller(), serializer=serializer)
8695
=== modified file 'nova/api/openstack/auth.py'
--- nova/api/openstack/auth.py 2011-05-17 19:12:48 +0000
+++ nova/api/openstack/auth.py 2011-07-14 20:24:25 +0000
@@ -13,9 +13,8 @@
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations15# License for the specific language governing permissions and limitations
16# under the License.import datetime16# under the License.
1717
18import datetime
19import hashlib18import hashlib
20import time19import time
2120
@@ -50,19 +49,22 @@
50 if not self.has_authentication(req):49 if not self.has_authentication(req):
51 return self.authenticate(req)50 return self.authenticate(req)
52 user = self.get_user_by_authentication(req)51 user = self.get_user_by_authentication(req)
53 accounts = self.auth.get_projects(user=user)
54 if not user:52 if not user:
55 token = req.headers["X-Auth-Token"]53 token = req.headers["X-Auth-Token"]
56 msg = _("%(user)s could not be found with token '%(token)s'")54 msg = _("%(user)s could not be found with token '%(token)s'")
57 LOG.warn(msg % locals())55 LOG.warn(msg % locals())
58 return faults.Fault(webob.exc.HTTPUnauthorized())56 return faults.Fault(webob.exc.HTTPUnauthorized())
5957
60 if accounts:58 try:
61 #we are punting on this til auth is settled,59 account = req.headers["X-Auth-Project-Id"]
62 #and possibly til api v1.1 (mdragon)60 except KeyError:
63 account = accounts[0]61 # FIXME(usrleon): It needed only for compatibility
64 else:62 # while osapi clients don't use this header
65 return faults.Fault(webob.exc.HTTPUnauthorized())63 accounts = self.auth.get_projects(user=user)
64 if accounts:
65 account = accounts[0]
66 else:
67 return faults.Fault(webob.exc.HTTPUnauthorized())
6668
67 if not self.auth.is_admin(user) and \69 if not self.auth.is_admin(user) and \
68 not self.auth.is_project_member(user, account):70 not self.auth.is_project_member(user, account):
@@ -127,7 +129,7 @@
127 except exception.NotFound:129 except exception.NotFound:
128 return None130 return None
129 if token:131 if token:
130 delta = datetime.datetime.utcnow() - token['created_at']132 delta = utils.utcnow() - token['created_at']
131 if delta.days >= 2:133 if delta.days >= 2:
132 self.db.auth_token_destroy(ctxt, token['token_hash'])134 self.db.auth_token_destroy(ctxt, token['token_hash'])
133 else:135 else:
134136
=== modified file 'nova/api/openstack/backup_schedules.py'
--- nova/api/openstack/backup_schedules.py 2011-03-30 17:05:06 +0000
+++ nova/api/openstack/backup_schedules.py 2011-07-14 20:24:25 +0000
@@ -19,9 +19,8 @@
1919
20from webob import exc20from webob import exc
2121
22from nova.api.openstack import common
23from nova.api.openstack import faults22from nova.api.openstack import faults
24import nova.image.service23from nova.api.openstack import wsgi
2524
2625
27def _translate_keys(inst):26def _translate_keys(inst):
@@ -29,30 +28,41 @@
29 return dict(backupSchedule=inst)28 return dict(backupSchedule=inst)
3029
3130
32class Controller(common.OpenstackController):31class Controller(object):
33 """ The backup schedule API controller for the Openstack API """32 """ The backup schedule API controller for the Openstack API """
3433
35 _serialization_metadata = {
36 'application/xml': {
37 'attributes': {
38 'backupSchedule': []}}}
39
40 def __init__(self):34 def __init__(self):
41 pass35 pass
4236
43 def index(self, req, server_id):37 def index(self, req, server_id, **kwargs):
44 """ Returns the list of backup schedules for a given instance """38 """ Returns the list of backup schedules for a given instance """
45 return faults.Fault(exc.HTTPNotImplemented())39 return faults.Fault(exc.HTTPNotImplemented())
4640
47 def show(self, req, server_id, id):41 def show(self, req, server_id, id, **kwargs):
48 """ Returns a single backup schedule for a given instance """42 """ Returns a single backup schedule for a given instance """
49 return faults.Fault(exc.HTTPNotImplemented())43 return faults.Fault(exc.HTTPNotImplemented())
5044
51 def create(self, req, server_id):45 def create(self, req, server_id, **kwargs):
52 """ No actual update method required, since the existing API allows46 """ No actual update method required, since the existing API allows
53 both create and update through a POST """47 both create and update through a POST """
54 return faults.Fault(exc.HTTPNotImplemented())48 return faults.Fault(exc.HTTPNotImplemented())
5549
56 def delete(self, req, server_id, id):50 def delete(self, req, server_id, id, **kwargs):
57 """ Deletes an existing backup schedule """51 """ Deletes an existing backup schedule """
58 return faults.Fault(exc.HTTPNotImplemented())52 return faults.Fault(exc.HTTPNotImplemented())
53
54
55def create_resource():
56 metadata = {
57 'attributes': {
58 'backupSchedule': [],
59 },
60 }
61
62 body_serializers = {
63 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10,
64 metadata=metadata),
65 }
66
67 serializer = wsgi.ResponseSerializer(body_serializers)
68 return wsgi.Resource(Controller(), serializer=serializer)
5969
=== modified file 'nova/api/openstack/common.py'
--- nova/api/openstack/common.py 2011-05-02 20:14:41 +0000
+++ nova/api/openstack/common.py 2011-07-14 20:24:25 +0000
@@ -23,12 +23,9 @@
23from nova import exception23from nova import exception
24from nova import flags24from nova import flags
25from nova import log as logging25from nova import log as logging
26from nova import wsgi
2726
2827
29LOG = logging.getLogger('nova.api.openstack.common')28LOG = logging.getLogger('nova.api.openstack.common')
30
31
32FLAGS = flags.FLAGS29FLAGS = flags.FLAGS
3330
3431
@@ -36,6 +33,34 @@
36XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1'33XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1'
3734
3835
36def get_pagination_params(request):
37 """Return marker, limit tuple from request.
38
39 :param request: `wsgi.Request` possibly containing 'marker' and 'limit'
40 GET variables. 'marker' is the id of the last element
41 the client has seen, and 'limit' is the maximum number
42 of items to return. If 'limit' is not specified, 0, or
43 > max_limit, we default to max_limit. Negative values
44 for either marker or limit will cause
45 exc.HTTPBadRequest() exceptions to be raised.
46
47 """
48 params = {}
49 for param in ['marker', 'limit']:
50 if not param in request.GET:
51 continue
52 try:
53 params[param] = int(request.GET[param])
54 except ValueError:
55 msg = _('%s param must be an integer') % param
56 raise webob.exc.HTTPBadRequest(msg)
57 if params[param] < 0:
58 msg = _('%s param must be positive') % param
59 raise webob.exc.HTTPBadRequest(msg)
60
61 return params
62
63
39def limited(items, request, max_limit=FLAGS.osapi_max_limit):64def limited(items, request, max_limit=FLAGS.osapi_max_limit):
40 """65 """
41 Return a slice of items according to requested offset and limit.66 Return a slice of items according to requested offset and limit.
@@ -72,19 +97,10 @@
7297
73def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit):98def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit):
74 """Return a slice of items according to the requested marker and limit."""99 """Return a slice of items according to the requested marker and limit."""
75100 params = get_pagination_params(request)
76 try:101
77 marker = int(request.GET.get('marker', 0))102 limit = params.get('limit', max_limit)
78 except ValueError:103 marker = params.get('marker')
79 raise webob.exc.HTTPBadRequest(_('marker param must be an integer'))
80
81 try:
82 limit = int(request.GET.get('limit', max_limit))
83 except ValueError:
84 raise webob.exc.HTTPBadRequest(_('limit param must be an integer'))
85
86 if limit < 0:
87 raise webob.exc.HTTPBadRequest(_('limit param must be positive'))
88104
89 limit = min(max_limit, limit)105 limit = min(max_limit, limit)
90 start_index = 0106 start_index = 0
@@ -100,34 +116,6 @@
100 return items[start_index:range_end]116 return items[start_index:range_end]
101117
102118
103def get_image_id_from_image_hash(image_service, context, image_hash):
104 """Given an Image ID Hash, return an objectstore Image ID.
105
106 image_service - reference to objectstore compatible image service.
107 context - security context for image service requests.
108 image_hash - hash of the image ID.
109 """
110
111 # FIX(sandy): This is terribly inefficient. It pulls all images
112 # from objectstore in order to find the match. ObjectStore
113 # should have a numeric counterpart to the string ID.
114 try:
115 items = image_service.detail(context)
116 except NotImplementedError:
117 items = image_service.index(context)
118 for image in items:
119 image_id = image['id']
120 try:
121 if abs(hash(image_id)) == int(image_hash):
122 return image_id
123 except ValueError:
124 msg = _("Requested image_id has wrong format: %s,"
125 "should have numerical format") % image_id
126 LOG.error(msg)
127 raise Exception(msg)
128 raise exception.ImageNotFound(image_id=image_hash)
129
130
131def get_id_from_href(href):119def get_id_from_href(href):
132 """Return the id portion of a url as an int.120 """Return the id portion of a url as an int.
133121
@@ -145,10 +133,25 @@
145 return int(urlparse(href).path.split('/')[-1])133 return int(urlparse(href).path.split('/')[-1])
146 except:134 except:
147 LOG.debug(_("Error extracting id from href: %s") % href)135 LOG.debug(_("Error extracting id from href: %s") % href)
148 raise webob.exc.HTTPBadRequest(_('could not parse id from href'))136 raise ValueError(_('could not parse id from href'))
149137
150138
151class OpenstackController(wsgi.Controller):139def remove_version_from_href(href):
152 def get_default_xmlns(self, req):140 """Removes the api version from the href.
153 # Use V10 by default141
154 return XML_NS_V10142 Given: 'http://www.nova.com/v1.1/123'
143 Returns: 'http://www.nova.com/123'
144
145 """
146 try:
147 #matches /v#.#
148 new_href = re.sub(r'[/][v][0-9]*.[0-9]*', '', href)
149 except:
150 LOG.debug(_("Error removing version from href: %s") % href)
151 msg = _('could not parse version from href')
152 raise ValueError(msg)
153
154 if new_href == href:
155 msg = _('href does not contain version')
156 raise ValueError(msg)
157 return new_href
155158
=== modified file 'nova/api/openstack/consoles.py'
--- nova/api/openstack/consoles.py 2011-03-30 17:05:06 +0000
+++ nova/api/openstack/consoles.py 2011-07-14 20:24:25 +0000
@@ -19,8 +19,8 @@
1919
20from nova import console20from nova import console
21from nova import exception21from nova import exception
22from nova.api.openstack import common
23from nova.api.openstack import faults22from nova.api.openstack import faults
23from nova.api.openstack import wsgi
2424
2525
26def _translate_keys(cons):26def _translate_keys(cons):
@@ -43,17 +43,11 @@
43 return dict(console=info)43 return dict(console=info)
4444
4545
46class Controller(common.OpenstackController):46class Controller(object):
47 """The Consoles Controller for the Openstack API"""47 """The Consoles controller for the Openstack API"""
48
49 _serialization_metadata = {
50 'application/xml': {
51 'attributes': {
52 'console': []}}}
5348
54 def __init__(self):49 def __init__(self):
55 self.console_api = console.API()50 self.console_api = console.API()
56 super(Controller, self).__init__()
5751
58 def index(self, req, server_id):52 def index(self, req, server_id):
59 """Returns a list of consoles for this instance"""53 """Returns a list of consoles for this instance"""
@@ -63,9 +57,8 @@
63 return dict(consoles=[_translate_keys(console)57 return dict(consoles=[_translate_keys(console)
64 for console in consoles])58 for console in consoles])
6559
66 def create(self, req, server_id):60 def create(self, req, server_id, body):
67 """Creates a new console"""61 """Creates a new console"""
68 #info = self._deserialize(req.body, req.get_content_type())
69 self.console_api.create_console(62 self.console_api.create_console(
70 req.environ['nova.context'],63 req.environ['nova.context'],
71 int(server_id))64 int(server_id))
@@ -94,3 +87,7 @@
94 except exception.NotFound:87 except exception.NotFound:
95 return faults.Fault(exc.HTTPNotFound())88 return faults.Fault(exc.HTTPNotFound())
96 return exc.HTTPAccepted()89 return exc.HTTPAccepted()
90
91
92def create_resource():
93 return wsgi.Resource(Controller())
9794
=== modified file 'nova/api/openstack/contrib/__init__.py'
--- nova/api/openstack/contrib/__init__.py 2011-03-30 01:16:09 +0000
+++ nova/api/openstack/contrib/__init__.py 2011-07-14 20:24:25 +0000
@@ -13,7 +13,7 @@
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations15# License for the specific language governing permissions and limitations
16# under the License.import datetime16# under the License.
1717
18"""Contrib contains extensions that are shipped with nova.18"""Contrib contains extensions that are shipped with nova.
1919
2020
=== added file 'nova/api/openstack/contrib/flavorextraspecs.py'
--- nova/api/openstack/contrib/flavorextraspecs.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/contrib/flavorextraspecs.py 2011-07-14 20:24:25 +0000
@@ -0,0 +1,126 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2011 University of Southern California
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18""" The instance type extra specs extension"""
19
20from webob import exc
21
22from nova import db
23from nova import quota
24from nova.api.openstack import extensions
25from nova.api.openstack import faults
26from nova.api.openstack import wsgi
27
28
29class FlavorExtraSpecsController(object):
30 """ The flavor extra specs API controller for the Openstack API """
31
32 def _get_extra_specs(self, context, flavor_id):
33 extra_specs = db.api.instance_type_extra_specs_get(context, flavor_id)
34 specs_dict = {}
35 for key, value in extra_specs.iteritems():
36 specs_dict[key] = value
37 return dict(extra_specs=specs_dict)
38
39 def _check_body(self, body):
40 if body == None or body == "":
41 expl = _('No Request Body')
42 raise exc.HTTPBadRequest(explanation=expl)
43
44 def index(self, req, flavor_id):
45 """ Returns the list of extra specs for a givenflavor """
46 context = req.environ['nova.context']
47 return self._get_extra_specs(context, flavor_id)
48
49 def create(self, req, flavor_id, body):
50 self._check_body(body)
51 context = req.environ['nova.context']
52 specs = body.get('extra_specs')
53 try:
54 db.api.instance_type_extra_specs_update_or_create(context,
55 flavor_id,
56 specs)
57 except quota.QuotaError as error:
58 self._handle_quota_error(error)
59 return body
60
61 def update(self, req, flavor_id, id, body):
62 self._check_body(body)
63 context = req.environ['nova.context']
64 if not id in body:
65 expl = _('Request body and URI mismatch')
66 raise exc.HTTPBadRequest(explanation=expl)
67 if len(body) > 1:
68 expl = _('Request body contains too many items')
69 raise exc.HTTPBadRequest(explanation=expl)
70 try:
71 db.api.instance_type_extra_specs_update_or_create(context,
72 flavor_id,
73 body)
74 except quota.QuotaError as error:
75 self._handle_quota_error(error)
76
77 return body
78
79 def show(self, req, flavor_id, id):
80 """ Return a single extra spec item """
81 context = req.environ['nova.context']
82 specs = self._get_extra_specs(context, flavor_id)
83 if id in specs['extra_specs']:
84 return {id: specs['extra_specs'][id]}
85 else:
86 return faults.Fault(exc.HTTPNotFound())
87
88 def delete(self, req, flavor_id, id):
89 """ Deletes an existing extra spec """
90 context = req.environ['nova.context']
91 db.api.instance_type_extra_specs_delete(context, flavor_id, id)
92
93 def _handle_quota_error(self, error):
94 """Reraise quota errors as api-specific http exceptions."""
95 if error.code == "MetadataLimitExceeded":
96 raise exc.HTTPBadRequest(explanation=error.message)
97 raise error
98
99
100class Flavorextraspecs(extensions.ExtensionDescriptor):
101
102 def get_name(self):
103 return "FlavorExtraSpecs"
104
105 def get_alias(self):
106 return "os-flavor-extra-specs"
107
108 def get_description(self):
109 return "Instance type (flavor) extra specs"
110
111 def get_namespace(self):
112 return \
113 "http://docs.openstack.org/ext/flavor_extra_specs/api/v1.1"
114
115 def get_updated(self):
116 return "2011-06-23T00:00:00+00:00"
117
118 def get_resources(self):
119 resources = []
120 res = extensions.ResourceExtension(
121 'os-extra_specs',
122 FlavorExtraSpecsController(),
123 parent=dict(member_name='flavor', collection_name='flavors'))
124
125 resources.append(res)
126 return resources
0127
=== added file 'nova/api/openstack/contrib/floating_ips.py'
--- nova/api/openstack/contrib/floating_ips.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/contrib/floating_ips.py 2011-07-14 20:24:25 +0000
@@ -0,0 +1,173 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2011 OpenStack LLC.
4# Copyright 2011 Grid Dynamics
5# Copyright 2011 Eldar Nugaev, Kirill Shileev, Ilya Alekseyev
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License. You may obtain
9# a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations
17# under the License
18from webob import exc
19
20from nova import exception
21from nova import network
22from nova import rpc
23from nova.api.openstack import faults
24from nova.api.openstack import extensions
25
26
27def _translate_floating_ip_view(floating_ip):
28 result = {'id': floating_ip['id'],
29 'ip': floating_ip['address']}
30 if 'fixed_ip' in floating_ip:
31 result['fixed_ip'] = floating_ip['fixed_ip']['address']
32 else:
33 result['fixed_ip'] = None
34 if 'instance' in floating_ip:
35 result['instance_id'] = floating_ip['instance']['id']
36 else:
37 result['instance_id'] = None
38 return {'floating_ip': result}
39
40
41def _translate_floating_ips_view(floating_ips):
42 return {'floating_ips': [_translate_floating_ip_view(floating_ip)
43 for floating_ip in floating_ips]}
44
45
46class FloatingIPController(object):
47 """The Floating IPs API controller for the OpenStack API."""
48
49 _serialization_metadata = {
50 'application/xml': {
51 "attributes": {
52 "floating_ip": [
53 "id",
54 "ip",
55 "instance_id",
56 "fixed_ip",
57 ]}}}
58
59 def __init__(self):
60 self.network_api = network.API()
61 super(FloatingIPController, self).__init__()
62
63 def show(self, req, id):
64 """Return data about the given floating ip."""
65 context = req.environ['nova.context']
66
67 try:
68 floating_ip = self.network_api.get_floating_ip(context, id)
69 except exception.NotFound:
70 return faults.Fault(exc.HTTPNotFound())
71
72 return _translate_floating_ip_view(floating_ip)
73
74 def index(self, req):
75 context = req.environ['nova.context']
76
77 floating_ips = self.network_api.list_floating_ips(context)
78
79 return _translate_floating_ips_view(floating_ips)
80
81 def create(self, req):
82 context = req.environ['nova.context']
83
84 try:
85 address = self.network_api.allocate_floating_ip(context)
86 ip = self.network_api.get_floating_ip_by_ip(context, address)
87 except rpc.RemoteError as ex:
88 # NOTE(tr3buchet) - why does this block exist?
89 if ex.exc_type == 'NoMoreFloatingIps':
90 raise exception.NoMoreFloatingIps()
91 else:
92 raise
93
94 return {'allocated': {
95 "id": ip['id'],
96 "floating_ip": ip['address']}}
97
98 def delete(self, req, id):
99 context = req.environ['nova.context']
100
101 ip = self.network_api.get_floating_ip(context, id)
102 self.network_api.release_floating_ip(context, address=ip)
103
104 return {'released': {
105 "id": ip['id'],
106 "floating_ip": ip['address']}}
107
108 def associate(self, req, id, body):
109 """ /floating_ips/{id}/associate fixed ip in body """
110 context = req.environ['nova.context']
111 floating_ip = self._get_ip_by_id(context, id)
112
113 fixed_ip = body['associate_address']['fixed_ip']
114
115 try:
116 self.network_api.associate_floating_ip(context,
117 floating_ip, fixed_ip)
118 except rpc.RemoteError:
119 raise
120
121 return {'associated':
122 {
123 "floating_ip_id": id,
124 "floating_ip": floating_ip,
125 "fixed_ip": fixed_ip}}
126
127 def disassociate(self, req, id):
128 """ POST /floating_ips/{id}/disassociate """
129 context = req.environ['nova.context']
130 floating_ip = self.network_api.get_floating_ip(context, id)
131 address = floating_ip['address']
132 fixed_ip = floating_ip['fixed_ip']['address']
133
134 try:
135 self.network_api.disassociate_floating_ip(context, address)
136 except rpc.RemoteError:
137 raise
138
139 return {'disassociated': {'floating_ip': address,
140 'fixed_ip': fixed_ip}}
141
142 def _get_ip_by_id(self, context, value):
143 """Checks that value is id and then returns its address."""
144 return self.network_api.get_floating_ip(context, value)['address']
145
146
147class Floating_ips(extensions.ExtensionDescriptor):
148 def get_name(self):
149 return "Floating_ips"
150
151 def get_alias(self):
152 return "os-floating-ips"
153
154 def get_description(self):
155 return "Floating IPs support"
156
157 def get_namespace(self):
158 return "http://docs.openstack.org/ext/floating_ips/api/v1.1"
159
160 def get_updated(self):
161 return "2011-06-16T00:00:00+00:00"
162
163 def get_resources(self):
164 resources = []
165
166 res = extensions.ResourceExtension('os-floating-ips',
167 FloatingIPController(),
168 member_actions={
169 'associate': 'POST',
170 'disassociate': 'POST'})
171 resources.append(res)
172
173 return resources
0174
=== added file 'nova/api/openstack/contrib/hosts.py'
--- nova/api/openstack/contrib/hosts.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/contrib/hosts.py 2011-07-14 20:24:25 +0000
@@ -0,0 +1,114 @@
1# Copyright (c) 2011 Openstack, LLC.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16"""The hosts admin extension."""
17
18import webob.exc
19
20from nova import compute
21from nova import exception
22from nova import flags
23from nova import log as logging
24from nova.api.openstack import common
25from nova.api.openstack import extensions
26from nova.api.openstack import faults
27from nova.scheduler import api as scheduler_api
28
29
30LOG = logging.getLogger("nova.api.hosts")
31FLAGS = flags.FLAGS
32
33
34def _list_hosts(req, service=None):
35 """Returns a summary list of hosts, optionally filtering
36 by service type.
37 """
38 context = req.environ['nova.context']
39 hosts = scheduler_api.get_host_list(context)
40 if service:
41 hosts = [host for host in hosts
42 if host["service"] == service]
43 return hosts
44
45
46def check_host(fn):
47 """Makes sure that the host exists."""
48 def wrapped(self, req, id, service=None, *args, **kwargs):
49 listed_hosts = _list_hosts(req, service)
50 hosts = [h["host_name"] for h in listed_hosts]
51 if id in hosts:
52 return fn(self, req, id, *args, **kwargs)
53 else:
54 raise exception.HostNotFound(host=id)
55 return wrapped
56
57
58class HostController(object):
59 """The Hosts API controller for the OpenStack API."""
60 def __init__(self):
61 self.compute_api = compute.API()
62 super(HostController, self).__init__()
63
64 def index(self, req):
65 return {'hosts': _list_hosts(req)}
66
67 @check_host
68 def update(self, req, id, body):
69 for raw_key, raw_val in body.iteritems():
70 key = raw_key.lower().strip()
71 val = raw_val.lower().strip()
72 # NOTE: (dabo) Right now only 'status' can be set, but other
73 # actions may follow.
74 if key == "status":
75 if val[:6] in ("enable", "disabl"):
76 return self._set_enabled_status(req, id,
77 enabled=(val.startswith("enable")))
78 else:
79 explanation = _("Invalid status: '%s'") % raw_val
80 raise webob.exc.HTTPBadRequest(explanation=explanation)
81 else:
82 explanation = _("Invalid update setting: '%s'") % raw_key
83 raise webob.exc.HTTPBadRequest(explanation=explanation)
84
85 def _set_enabled_status(self, req, host, enabled):
86 """Sets the specified host's ability to accept new instances."""
87 context = req.environ['nova.context']
88 state = "enabled" if enabled else "disabled"
89 LOG.audit(_("Setting host %(host)s to %(state)s.") % locals())
90 result = self.compute_api.set_host_enabled(context, host=host,
91 enabled=enabled)
92 return {"host": host, "status": result}
93
94
95class Hosts(extensions.ExtensionDescriptor):
96 def get_name(self):
97 return "Hosts"
98
99 def get_alias(self):
100 return "os-hosts"
101
102 def get_description(self):
103 return "Host administration"
104
105 def get_namespace(self):
106 return "http://docs.openstack.org/ext/hosts/api/v1.1"
107
108 def get_updated(self):
109 return "2011-06-29T00:00:00+00:00"
110
111 def get_resources(self):
112 resources = [extensions.ResourceExtension('os-hosts', HostController(),
113 collection_actions={'update': 'PUT'}, member_actions={})]
114 return resources
0115
=== added file 'nova/api/openstack/contrib/multinic.py'
--- nova/api/openstack/contrib/multinic.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/contrib/multinic.py 2011-07-14 20:24:25 +0000
@@ -0,0 +1,125 @@
1# Copyright 2011 OpenStack LLC.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16"""The multinic extension."""
17
18from webob import exc
19
20from nova import compute
21from nova import log as logging
22from nova.api.openstack import extensions
23from nova.api.openstack import faults
24
25
26LOG = logging.getLogger("nova.api.multinic")
27
28
29# Note: The class name is as it has to be for this to be loaded as an
30# extension--only first character capitalized.
31class Multinic(extensions.ExtensionDescriptor):
32 """The multinic extension.
33
34 Exposes addFixedIp and removeFixedIp actions on servers.
35
36 """
37
38 def __init__(self, *args, **kwargs):
39 """Initialize the extension.
40
41 Gets a compute.API object so we can call the back-end
42 add_fixed_ip() and remove_fixed_ip() methods.
43 """
44
45 super(Multinic, self).__init__(*args, **kwargs)
46 self.compute_api = compute.API()
47
48 def get_name(self):
49 """Return the extension name, as required by contract."""
50
51 return "Multinic"
52
53 def get_alias(self):
54 """Return the extension alias, as required by contract."""
55
56 return "NMN"
57
58 def get_description(self):
59 """Return the extension description, as required by contract."""
60
61 return "Multiple network support"
62
63 def get_namespace(self):
64 """Return the namespace, as required by contract."""
65
66 return "http://docs.openstack.org/ext/multinic/api/v1.1"
67
68 def get_updated(self):
69 """Return the last updated timestamp, as required by contract."""
70
71 return "2011-06-09T00:00:00+00:00"
72
73 def get_actions(self):
74 """Return the actions the extension adds, as required by contract."""
75
76 actions = []
77
78 # Add the add_fixed_ip action
79 act = extensions.ActionExtension("servers", "addFixedIp",
80 self._add_fixed_ip)
81 actions.append(act)
82
83 # Add the remove_fixed_ip action
84 act = extensions.ActionExtension("servers", "removeFixedIp",
85 self._remove_fixed_ip)
86 actions.append(act)
87
88 return actions
89
90 def _add_fixed_ip(self, input_dict, req, id):
91 """Adds an IP on a given network to an instance."""
92
93 try:
94 # Validate the input entity
95 if 'networkId' not in input_dict['addFixedIp']:
96 LOG.exception(_("Missing 'networkId' argument for addFixedIp"))
97 return faults.Fault(exc.HTTPUnprocessableEntity())
98
99 # Add the fixed IP
100 network_id = input_dict['addFixedIp']['networkId']
101 self.compute_api.add_fixed_ip(req.environ['nova.context'], id,
102 network_id)
103 except Exception, e:
104 LOG.exception(_("Error in addFixedIp %s"), e)
105 return faults.Fault(exc.HTTPBadRequest())
106 return exc.HTTPAccepted()
107
108 def _remove_fixed_ip(self, input_dict, req, id):
109 """Removes an IP from an instance."""
110
111 try:
112 # Validate the input entity
113 if 'address' not in input_dict['removeFixedIp']:
114 LOG.exception(_("Missing 'address' argument for "
115 "removeFixedIp"))
116 return faults.Fault(exc.HTTPUnprocessableEntity())
117
118 # Remove the fixed IP
119 address = input_dict['removeFixedIp']['address']
120 self.compute_api.remove_fixed_ip(req.environ['nova.context'], id,
121 address)
122 except Exception, e:
123 LOG.exception(_("Error in removeFixedIp %s"), e)
124 return faults.Fault(exc.HTTPBadRequest())
125 return exc.HTTPAccepted()
0126
=== modified file 'nova/api/openstack/contrib/volumes.py'
--- nova/api/openstack/contrib/volumes.py 2011-04-19 09:54:47 +0000
+++ nova/api/openstack/contrib/volumes.py 2011-07-14 20:24:25 +0000
@@ -22,7 +22,6 @@
22from nova import flags22from nova import flags
23from nova import log as logging23from nova import log as logging
24from nova import volume24from nova import volume
25from nova import wsgi
26from nova.api.openstack import common25from nova.api.openstack import common
27from nova.api.openstack import extensions26from nova.api.openstack import extensions
28from nova.api.openstack import faults27from nova.api.openstack import faults
@@ -64,7 +63,7 @@
64 return d63 return d
6564
6665
67class VolumeController(wsgi.Controller):66class VolumeController(object):
68 """The Volumes API controller for the OpenStack API."""67 """The Volumes API controller for the OpenStack API."""
6968
70 _serialization_metadata = {69 _serialization_metadata = {
@@ -124,18 +123,17 @@
124 res = [entity_maker(context, vol) for vol in limited_list]123 res = [entity_maker(context, vol) for vol in limited_list]
125 return {'volumes': res}124 return {'volumes': res}
126125
127 def create(self, req):126 def create(self, req, body):
128 """Creates a new volume."""127 """Creates a new volume."""
129 context = req.environ['nova.context']128 context = req.environ['nova.context']
130129
131 env = self._deserialize(req.body, req.get_content_type())130 if not body:
132 if not env:
133 return faults.Fault(exc.HTTPUnprocessableEntity())131 return faults.Fault(exc.HTTPUnprocessableEntity())
134132
135 vol = env['volume']133 vol = body['volume']
136 size = vol['size']134 size = vol['size']
137 LOG.audit(_("Create volume of %s GB"), size, context=context)135 LOG.audit(_("Create volume of %s GB"), size, context=context)
138 new_volume = self.volume_api.create(context, size,136 new_volume = self.volume_api.create(context, size, None,
139 vol.get('display_name'),137 vol.get('display_name'),
140 vol.get('display_description'))138 vol.get('display_description'))
141139
@@ -175,7 +173,7 @@
175 return d173 return d
176174
177175
178class VolumeAttachmentController(wsgi.Controller):176class VolumeAttachmentController(object):
179 """The volume attachment API controller for the Openstack API.177 """The volume attachment API controller for the Openstack API.
180178
181 A child resource of the server. Note that we use the volume id179 A child resource of the server. Note that we use the volume id
@@ -219,17 +217,16 @@
219 return {'volumeAttachment': _translate_attachment_detail_view(context,217 return {'volumeAttachment': _translate_attachment_detail_view(context,
220 vol)}218 vol)}
221219
222 def create(self, req, server_id):220 def create(self, req, server_id, body):
223 """Attach a volume to an instance."""221 """Attach a volume to an instance."""
224 context = req.environ['nova.context']222 context = req.environ['nova.context']
225223
226 env = self._deserialize(req.body, req.get_content_type())224 if not body:
227 if not env:
228 return faults.Fault(exc.HTTPUnprocessableEntity())225 return faults.Fault(exc.HTTPUnprocessableEntity())
229226
230 instance_id = server_id227 instance_id = server_id
231 volume_id = env['volumeAttachment']['volumeId']228 volume_id = body['volumeAttachment']['volumeId']
232 device = env['volumeAttachment']['device']229 device = body['volumeAttachment']['device']
233230
234 msg = _("Attach volume %(volume_id)s to instance %(server_id)s"231 msg = _("Attach volume %(volume_id)s to instance %(server_id)s"
235 " at %(device)s") % locals()232 " at %(device)s") % locals()
@@ -259,7 +256,7 @@
259 # TODO(justinsb): How do I return "accepted" here?256 # TODO(justinsb): How do I return "accepted" here?
260 return {'volumeAttachment': attachment}257 return {'volumeAttachment': attachment}
261258
262 def update(self, _req, _server_id, _id):259 def update(self, req, server_id, id, body):
263 """Update a volume attachment. We don't currently support this."""260 """Update a volume attachment. We don't currently support this."""
264 return faults.Fault(exc.HTTPBadRequest())261 return faults.Fault(exc.HTTPBadRequest())
265262
@@ -304,7 +301,7 @@
304 return "Volumes"301 return "Volumes"
305302
306 def get_alias(self):303 def get_alias(self):
307 return "VOLUMES"304 return "os-volumes"
308305
309 def get_description(self):306 def get_description(self):
310 return "Volumes support"307 return "Volumes support"
@@ -320,12 +317,12 @@
320317
321 # NOTE(justinsb): No way to provide singular name ('volume')318 # NOTE(justinsb): No way to provide singular name ('volume')
322 # Does this matter?319 # Does this matter?
323 res = extensions.ResourceExtension('volumes',320 res = extensions.ResourceExtension('os-volumes',
324 VolumeController(),321 VolumeController(),
325 collection_actions={'detail': 'GET'})322 collection_actions={'detail': 'GET'})
326 resources.append(res)323 resources.append(res)
327324
328 res = extensions.ResourceExtension('volume_attachments',325 res = extensions.ResourceExtension('os-volume_attachments',
329 VolumeAttachmentController(),326 VolumeAttachmentController(),
330 parent=dict(327 parent=dict(
331 member_name='server',328 member_name='server',
332329
=== added file 'nova/api/openstack/create_instance_helper.py'
--- nova/api/openstack/create_instance_helper.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/create_instance_helper.py 2011-07-14 20:24:25 +0000
@@ -0,0 +1,354 @@
1# Copyright 2011 OpenStack LLC.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16import base64
17import re
18import webob
19
20from webob import exc
21from xml.dom import minidom
22
23from nova import exception
24from nova import flags
25from nova import log as logging
26import nova.image
27from nova import quota
28from nova import utils
29
30from nova.compute import instance_types
31from nova.api.openstack import faults
32from nova.api.openstack import wsgi
33from nova.auth import manager as auth_manager
34
35
36LOG = logging.getLogger('nova.api.openstack.create_instance_helper')
37FLAGS = flags.FLAGS
38
39
40class CreateFault(exception.NovaException):
41 message = _("Invalid parameters given to create_instance.")
42
43 def __init__(self, fault):
44 self.fault = fault
45 super(CreateFault, self).__init__()
46
47
48class CreateInstanceHelper(object):
49 """This is the base class for OS API Controllers that
50 are capable of creating instances (currently Servers and Zones).
51
52 Once we stabilize the Zones portion of the API we may be able
53 to move this code back into servers.py
54 """
55
56 def __init__(self, controller):
57 """We need the image service to create an instance."""
58 self.controller = controller
59 self._image_service = utils.import_object(FLAGS.image_service)
60 super(CreateInstanceHelper, self).__init__()
61
62 def create_instance(self, req, body, create_method):
63 """Creates a new server for the given user. The approach
64 used depends on the create_method. For example, the standard
65 POST /server call uses compute.api.create(), while
66 POST /zones/server uses compute.api.create_all_at_once().
67
68 The problem is, both approaches return different values (i.e.
69 [instance dicts] vs. reservation_id). So the handling of the
70 return type from this method is left to the caller.
71 """
72 if not body:
73 raise faults.Fault(exc.HTTPUnprocessableEntity())
74
75 context = req.environ['nova.context']
76
77 password = self.controller._get_server_admin_password(body['server'])
78
79 key_name = None
80 key_data = None
81 key_pairs = auth_manager.AuthManager.get_key_pairs(context)
82 if key_pairs:
83 key_pair = key_pairs[0]
84 key_name = key_pair['name']
85 key_data = key_pair['public_key']
86
87 image_href = self.controller._image_ref_from_req_data(body)
88 try:
89 image_service, image_id = nova.image.get_image_service(image_href)
90 kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
91 req, image_id)
92 images = set([str(x['id']) for x in image_service.index(context)])
93 assert str(image_id) in images
94 except Exception, e:
95 msg = _("Cannot find requested image %(image_href)s: %(e)s" %
96 locals())
97 raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
98
99 personality = body['server'].get('personality')
100
101 injected_files = []
102 if personality:
103 injected_files = self._get_injected_files(personality)
104
105 flavor_id = self.controller._flavor_id_from_req_data(body)
106
107 if not 'name' in body['server']:
108 msg = _("Server name is not defined")
109 raise exc.HTTPBadRequest(explanation=msg)
110
111 zone_blob = body['server'].get('blob')
112 name = body['server']['name']
113 self._validate_server_name(name)
114 name = name.strip()
115
116 reservation_id = body['server'].get('reservation_id')
117 min_count = body['server'].get('min_count')
118 max_count = body['server'].get('max_count')
119 # min_count and max_count are optional. If they exist, they come
120 # in as strings. We want to default 'min_count' to 1, and default
121 # 'max_count' to be 'min_count'.
122 min_count = int(min_count) if min_count else 1
123 max_count = int(max_count) if max_count else min_count
124 if min_count > max_count:
125 min_count = max_count
126
127 try:
128 inst_type = \
129 instance_types.get_instance_type_by_flavor_id(flavor_id)
130 extra_values = {
131 'instance_type': inst_type,
132 'image_ref': image_href,
133 'password': password}
134
135 return (extra_values,
136 create_method(context,
137 inst_type,
138 image_id,
139 kernel_id=kernel_id,
140 ramdisk_id=ramdisk_id,
141 display_name=name,
142 display_description=name,
143 key_name=key_name,
144 key_data=key_data,
145 metadata=body['server'].get('metadata', {}),
146 injected_files=injected_files,
147 admin_password=password,
148 zone_blob=zone_blob,
149 reservation_id=reservation_id,
150 min_count=min_count,
151 max_count=max_count))
152 except quota.QuotaError as error:
153 self._handle_quota_error(error)
154 except exception.ImageNotFound as error:
155 msg = _("Can not find requested image")
156 raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
157
158 # Let the caller deal with unhandled exceptions.
159
160 def _handle_quota_error(self, error):
161 """
162 Reraise quota errors as api-specific http exceptions
163 """
164 if error.code == "OnsetFileLimitExceeded":
165 expl = _("Personality file limit exceeded")
166 raise exc.HTTPBadRequest(explanation=expl)
167 if error.code == "OnsetFilePathLimitExceeded":
168 expl = _("Personality file path too long")
169 raise exc.HTTPBadRequest(explanation=expl)
170 if error.code == "OnsetFileContentLimitExceeded":
171 expl = _("Personality file content too long")
172 raise exc.HTTPBadRequest(explanation=expl)
173 # if the original error is okay, just reraise it
174 raise error
175
176 def _deserialize_create(self, request):
177 """
178 Deserialize a create request
179
180 Overrides normal behavior in the case of xml content
181 """
182 if request.content_type == "application/xml":
183 deserializer = ServerCreateRequestXMLDeserializer()
184 return deserializer.deserialize(request.body)
185 else:
186 return self._deserialize(request.body, request.get_content_type())
187
188 def _validate_server_name(self, value):
189 if not isinstance(value, basestring):
190 msg = _("Server name is not a string or unicode")
191 raise exc.HTTPBadRequest(explanation=msg)
192
193 if value.strip() == '':
194 msg = _("Server name is an empty string")
195 raise exc.HTTPBadRequest(explanation=msg)
196
197 def _get_kernel_ramdisk_from_image(self, req, image_id):
198 """Fetch an image from the ImageService, then if present, return the
199 associated kernel and ramdisk image IDs.
200 """
201 context = req.environ['nova.context']
202 image_meta = self._image_service.show(context, image_id)
203 # NOTE(sirp): extracted to a separate method to aid unit-testing, the
204 # new method doesn't need a request obj or an ImageService stub
205 kernel_id, ramdisk_id = self._do_get_kernel_ramdisk_from_image(
206 image_meta)
207 return kernel_id, ramdisk_id
208
209 @staticmethod
210 def _do_get_kernel_ramdisk_from_image(image_meta):
211 """Given an ImageService image_meta, return kernel and ramdisk image
212 ids if present.
213
214 This is only valid for `ami` style images.
215 """
216 image_id = image_meta['id']
217 if image_meta['status'] != 'active':
218 raise exception.ImageUnacceptable(image_id=image_id,
219 reason=_("status is not active"))
220
221 if image_meta.get('container_format') != 'ami':
222 return None, None
223
224 try:
225 kernel_id = image_meta['properties']['kernel_id']
226 except KeyError:
227 raise exception.KernelNotFoundForImage(image_id=image_id)
228
229 try:
230 ramdisk_id = image_meta['properties']['ramdisk_id']
231 except KeyError:
232 raise exception.RamdiskNotFoundForImage(image_id=image_id)
233
234 return kernel_id, ramdisk_id
235
236 def _get_injected_files(self, personality):
237 """
238 Create a list of injected files from the personality attribute
239
240 At this time, injected_files must be formatted as a list of
241 (file_path, file_content) pairs for compatibility with the
242 underlying compute service.
243 """
244 injected_files = []
245
246 for item in personality:
247 try:
248 path = item['path']
249 contents = item['contents']
250 except KeyError as key:
251 expl = _('Bad personality format: missing %s') % key
252 raise exc.HTTPBadRequest(explanation=expl)
253 except TypeError:
254 expl = _('Bad personality format')
255 raise exc.HTTPBadRequest(explanation=expl)
256 try:
257 contents = base64.b64decode(contents)
258 except TypeError:
259 expl = _('Personality content for %s cannot be decoded') % path
260 raise exc.HTTPBadRequest(explanation=expl)
261 injected_files.append((path, contents))
262 return injected_files
263
264 def _get_server_admin_password_old_style(self, server):
265 """ Determine the admin password for a server on creation """
266 return utils.generate_password(16)
267
268 def _get_server_admin_password_new_style(self, server):
269 """ Determine the admin password for a server on creation """
270 password = server.get('adminPass')
271
272 if password is None:
273 return utils.generate_password(16)
274 if not isinstance(password, basestring) or password == '':
275 msg = _("Invalid adminPass")
276 raise exc.HTTPBadRequest(explanation=msg)
277 return password
278
279
280class ServerXMLDeserializer(wsgi.XMLDeserializer):
281 """
282 Deserializer to handle xml-formatted server create requests.
283
284 Handles standard server attributes as well as optional metadata
285 and personality attributes
286 """
287
288 def create(self, string):
289 """Deserialize an xml-formatted server create request"""
290 dom = minidom.parseString(string)
291 server = self._extract_server(dom)
292 return {'body': {'server': server}}
293
294 def _extract_server(self, node):
295 """Marshal the server attribute of a parsed request"""
296 server = {}
297 server_node = self._find_first_child_named(node, 'server')
298 for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]:
299 if server_node.getAttribute(attr):
300 server[attr] = server_node.getAttribute(attr)
301 metadata = self._extract_metadata(server_node)
302 if metadata is not None:
303 server["metadata"] = metadata
304 personality = self._extract_personality(server_node)
305 if personality is not None:
306 server["personality"] = personality
307 return server
308
309 def _extract_metadata(self, server_node):
310 """Marshal the metadata attribute of a parsed request"""
311 metadata_node = self._find_first_child_named(server_node, "metadata")
312 if metadata_node is None:
313 return None
314 metadata = {}
315 for meta_node in self._find_children_named(metadata_node, "meta"):
316 key = meta_node.getAttribute("key")
317 metadata[key] = self._extract_text(meta_node)
318 return metadata
319
320 def _extract_personality(self, server_node):
321 """Marshal the personality attribute of a parsed request"""
322 personality_node = \
323 self._find_first_child_named(server_node, "personality")
324 if personality_node is None:
325 return None
326 personality = []
327 for file_node in self._find_children_named(personality_node, "file"):
328 item = {}
329 if file_node.hasAttribute("path"):
330 item["path"] = file_node.getAttribute("path")
331 item["contents"] = self._extract_text(file_node)
332 personality.append(item)
333 return personality
334
335 def _find_first_child_named(self, parent, name):
336 """Search a nodes children for the first child with a given name"""
337 for node in parent.childNodes:
338 if node.nodeName == name:
339 return node
340 return None
341
342 def _find_children_named(self, parent, name):
343 """Return all of a nodes children who have the given name"""
344 for node in parent.childNodes:
345 if node.nodeName == name:
346 yield node
347
348 def _extract_text(self, node):
349 """Get the text field contained by the given node"""
350 if len(node.childNodes) == 1:
351 child = node.childNodes[0]
352 if child.nodeType == child.TEXT_NODE:
353 return child.nodeValue
354 return ""
0355
=== modified file 'nova/api/openstack/extensions.py'
--- nova/api/openstack/extensions.py 2011-05-17 03:14:51 +0000
+++ nova/api/openstack/extensions.py 2011-07-14 20:24:25 +0000
@@ -27,9 +27,10 @@
27from nova import exception27from nova import exception
28from nova import flags28from nova import flags
29from nova import log as logging29from nova import log as logging
30from nova import wsgi30from nova import wsgi as base_wsgi
31from nova.api.openstack import common31from nova.api.openstack import common
32from nova.api.openstack import faults32from nova.api.openstack import faults
33from nova.api.openstack import wsgi
3334
3435
35LOG = logging.getLogger('extensions')36LOG = logging.getLogger('extensions')
@@ -115,28 +116,34 @@
115 return request_exts116 return request_exts
116117
117118
118class ActionExtensionController(common.OpenstackController):119class ActionExtensionController(object):
119
120 def __init__(self, application):120 def __init__(self, application):
121
122 self.application = application121 self.application = application
123 self.action_handlers = {}122 self.action_handlers = {}
124123
125 def add_action(self, action_name, handler):124 def add_action(self, action_name, handler):
126 self.action_handlers[action_name] = handler125 self.action_handlers[action_name] = handler
127126
128 def action(self, req, id):127 def action(self, req, id, body):
129
130 input_dict = self._deserialize(req.body, req.get_content_type())
131 for action_name, handler in self.action_handlers.iteritems():128 for action_name, handler in self.action_handlers.iteritems():
132 if action_name in input_dict:129 if action_name in body:
133 return handler(input_dict, req, id)130 return handler(body, req, id)
134 # no action handler found (bump to downstream application)131 # no action handler found (bump to downstream application)
135 res = self.application132 res = self.application
136 return res133 return res
137134
138135
139class RequestExtensionController(common.OpenstackController):136class ActionExtensionResource(wsgi.Resource):
137
138 def __init__(self, application):
139 controller = ActionExtensionController(application)
140 wsgi.Resource.__init__(self, controller)
141
142 def add_action(self, action_name, handler):
143 self.controller.add_action(action_name, handler)
144
145
146class RequestExtensionController(object):
140147
141 def __init__(self, application):148 def __init__(self, application):
142 self.application = application149 self.application = application
@@ -153,7 +160,17 @@
153 return res160 return res
154161
155162
156class ExtensionController(common.OpenstackController):163class RequestExtensionResource(wsgi.Resource):
164
165 def __init__(self, application):
166 controller = RequestExtensionController(application)
167 wsgi.Resource.__init__(self, controller)
168
169 def add_handler(self, handler):
170 self.controller.add_handler(handler)
171
172
173class ExtensionsResource(wsgi.Resource):
157174
158 def __init__(self, extension_manager):175 def __init__(self, extension_manager):
159 self.extension_manager = extension_manager176 self.extension_manager = extension_manager
@@ -186,7 +203,7 @@
186 raise faults.Fault(webob.exc.HTTPNotFound())203 raise faults.Fault(webob.exc.HTTPNotFound())
187204
188205
189class ExtensionMiddleware(wsgi.Middleware):206class ExtensionMiddleware(base_wsgi.Middleware):
190 """Extensions middleware for WSGI."""207 """Extensions middleware for WSGI."""
191 @classmethod208 @classmethod
192 def factory(cls, global_config, **local_config):209 def factory(cls, global_config, **local_config):
@@ -195,43 +212,43 @@
195 return cls(app, **local_config)212 return cls(app, **local_config)
196 return _factory213 return _factory
197214
198 def _action_ext_controllers(self, application, ext_mgr, mapper):215 def _action_ext_resources(self, application, ext_mgr, mapper):
199 """Return a dict of ActionExtensionController-s by collection."""216 """Return a dict of ActionExtensionResource-s by collection."""
200 action_controllers = {}217 action_resources = {}
201 for action in ext_mgr.get_actions():218 for action in ext_mgr.get_actions():
202 if not action.collection in action_controllers.keys():219 if not action.collection in action_resources.keys():
203 controller = ActionExtensionController(application)220 resource = ActionExtensionResource(application)
204 mapper.connect("/%s/:(id)/action.:(format)" %221 mapper.connect("/%s/:(id)/action.:(format)" %
205 action.collection,222 action.collection,
206 action='action',223 action='action',
207 controller=controller,224 controller=resource,
208 conditions=dict(method=['POST']))225 conditions=dict(method=['POST']))
209 mapper.connect("/%s/:(id)/action" % action.collection,226 mapper.connect("/%s/:(id)/action" % action.collection,
210 action='action',227 action='action',
211 controller=controller,228 controller=resource,
212 conditions=dict(method=['POST']))229 conditions=dict(method=['POST']))
213 action_controllers[action.collection] = controller230 action_resources[action.collection] = resource
214231
215 return action_controllers232 return action_resources
216233
217 def _request_ext_controllers(self, application, ext_mgr, mapper):234 def _request_ext_resources(self, application, ext_mgr, mapper):
218 """Returns a dict of RequestExtensionController-s by collection."""235 """Returns a dict of RequestExtensionResource-s by collection."""
219 request_ext_controllers = {}236 request_ext_resources = {}
220 for req_ext in ext_mgr.get_request_extensions():237 for req_ext in ext_mgr.get_request_extensions():
221 if not req_ext.key in request_ext_controllers.keys():238 if not req_ext.key in request_ext_resources.keys():
222 controller = RequestExtensionController(application)239 resource = RequestExtensionResource(application)
223 mapper.connect(req_ext.url_route + '.:(format)',240 mapper.connect(req_ext.url_route + '.:(format)',
224 action='process',241 action='process',
225 controller=controller,242 controller=resource,
226 conditions=req_ext.conditions)243 conditions=req_ext.conditions)
227244
228 mapper.connect(req_ext.url_route,245 mapper.connect(req_ext.url_route,
229 action='process',246 action='process',
230 controller=controller,247 controller=resource,
231 conditions=req_ext.conditions)248 conditions=req_ext.conditions)
232 request_ext_controllers[req_ext.key] = controller249 request_ext_resources[req_ext.key] = resource
233250
234 return request_ext_controllers251 return request_ext_resources
235252
236 def __init__(self, application, ext_mgr=None):253 def __init__(self, application, ext_mgr=None):
237254
@@ -246,22 +263,22 @@
246 LOG.debug(_('Extended resource: %s'),263 LOG.debug(_('Extended resource: %s'),
247 resource.collection)264 resource.collection)
248 mapper.resource(resource.collection, resource.collection,265 mapper.resource(resource.collection, resource.collection,
249 controller=resource.controller,266 controller=wsgi.Resource(resource.controller),
250 collection=resource.collection_actions,267 collection=resource.collection_actions,
251 member=resource.member_actions,268 member=resource.member_actions,
252 parent_resource=resource.parent)269 parent_resource=resource.parent)
253270
254 # extended actions271 # extended actions
255 action_controllers = self._action_ext_controllers(application, ext_mgr,272 action_resources = self._action_ext_resources(application, ext_mgr,
256 mapper)273 mapper)
257 for action in ext_mgr.get_actions():274 for action in ext_mgr.get_actions():
258 LOG.debug(_('Extended action: %s'), action.action_name)275 LOG.debug(_('Extended action: %s'), action.action_name)
259 controller = action_controllers[action.collection]276 resource = action_resources[action.collection]
260 controller.add_action(action.action_name, action.handler)277 resource.add_action(action.action_name, action.handler)
261278
262 # extended requests279 # extended requests
263 req_controllers = self._request_ext_controllers(application, ext_mgr,280 req_controllers = self._request_ext_resources(application, ext_mgr,
264 mapper)281 mapper)
265 for request_ext in ext_mgr.get_request_extensions():282 for request_ext in ext_mgr.get_request_extensions():
266 LOG.debug(_('Extended request: %s'), request_ext.key)283 LOG.debug(_('Extended request: %s'), request_ext.key)
267 controller = req_controllers[request_ext.key]284 controller = req_controllers[request_ext.key]
@@ -313,7 +330,7 @@
313 """Returns a list of ResourceExtension objects."""330 """Returns a list of ResourceExtension objects."""
314 resources = []331 resources = []
315 resources.append(ResourceExtension('extensions',332 resources.append(ResourceExtension('extensions',
316 ExtensionController(self)))333 ExtensionsResource(self)))
317 for alias, ext in self.extensions.iteritems():334 for alias, ext in self.extensions.iteritems():
318 try:335 try:
319 resources.extend(ext.get_resources())336 resources.extend(ext.get_resources())
@@ -357,6 +374,8 @@
357 LOG.debug(_('Ext updated: %s'), extension.get_updated())374 LOG.debug(_('Ext updated: %s'), extension.get_updated())
358 except AttributeError as ex:375 except AttributeError as ex:
359 LOG.exception(_("Exception loading extension: %s"), unicode(ex))376 LOG.exception(_("Exception loading extension: %s"), unicode(ex))
377 return False
378 return True
360379
361 def _load_all_extensions(self):380 def _load_all_extensions(self):
362 """Load extensions from the configured path.381 """Load extensions from the configured path.
@@ -395,22 +414,23 @@
395 'file': ext_path})414 'file': ext_path})
396 continue415 continue
397 new_ext = new_ext_class()416 new_ext = new_ext_class()
398 self._check_extension(new_ext)417 self.add_extension(new_ext)
399 self._add_extension(new_ext)418
400419 def add_extension(self, ext):
401 def _add_extension(self, ext):420 # Do nothing if the extension doesn't check out
421 if not self._check_extension(ext):
422 return
423
402 alias = ext.get_alias()424 alias = ext.get_alias()
403 LOG.audit(_('Loaded extension: %s'), alias)425 LOG.audit(_('Loaded extension: %s'), alias)
404426
405 self._check_extension(ext)
406
407 if alias in self.extensions:427 if alias in self.extensions:
408 raise exception.Error("Found duplicate extension: %s" % alias)428 raise exception.Error("Found duplicate extension: %s" % alias)
409 self.extensions[alias] = ext429 self.extensions[alias] = ext
410430
411431
412class RequestExtension(object):432class RequestExtension(object):
413 """Extend requests and responses of core nova OpenStack API controllers.433 """Extend requests and responses of core nova OpenStack API resources.
414434
415 Provide a way to add data to responses and handle custom request data435 Provide a way to add data to responses and handle custom request data
416 that is sent to core nova OpenStack API controllers.436 that is sent to core nova OpenStack API controllers.
@@ -424,7 +444,7 @@
424444
425445
426class ActionExtension(object):446class ActionExtension(object):
427 """Add custom actions to core nova OpenStack API controllers."""447 """Add custom actions to core nova OpenStack API resources."""
428448
429 def __init__(self, collection, action_name, handler):449 def __init__(self, collection, action_name, handler):
430 self.collection = collection450 self.collection = collection
431451
=== modified file 'nova/api/openstack/faults.py'
--- nova/api/openstack/faults.py 2011-04-07 18:55:42 +0000
+++ nova/api/openstack/faults.py 2011-07-14 20:24:25 +0000
@@ -19,8 +19,7 @@
19import webob.dec19import webob.dec
20import webob.exc20import webob.exc
2121
22from nova import wsgi22from nova.api.openstack import wsgi
23from nova.api.openstack import common
2423
2524
26class Fault(webob.exc.HTTPException):25class Fault(webob.exc.HTTPException):
@@ -55,13 +54,21 @@
55 if code == 413:54 if code == 413:
56 retry = self.wrapped_exc.headers['Retry-After']55 retry = self.wrapped_exc.headers['Retry-After']
57 fault_data[fault_name]['retryAfter'] = retry56 fault_data[fault_name]['retryAfter'] = retry
57
58 # 'code' is an attribute on the fault tag itself58 # 'code' is an attribute on the fault tag itself
59 metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}59 metadata = {'attributes': {fault_name: 'code'}}
60 default_xmlns = common.XML_NS_V1060
61 serializer = wsgi.Serializer(metadata, default_xmlns)
62 content_type = req.best_match_content_type()61 content_type = req.best_match_content_type()
63 self.wrapped_exc.body = serializer.serialize(fault_data, content_type)62
63 serializer = {
64 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
65 xmlns=wsgi.XMLNS_V10),
66 'application/json': wsgi.JSONDictSerializer(),
67 }[content_type]
68
69 self.wrapped_exc.body = serializer.serialize(fault_data)
64 self.wrapped_exc.content_type = content_type70 self.wrapped_exc.content_type = content_type
71
65 return self.wrapped_exc72 return self.wrapped_exc
6673
6774
@@ -70,14 +77,6 @@
70 Rate-limited request response.77 Rate-limited request response.
71 """78 """
7279
73 _serialization_metadata = {
74 "application/xml": {
75 "attributes": {
76 "overLimitFault": "code",
77 },
78 },
79 }
80
81 def __init__(self, message, details, retry_time):80 def __init__(self, message, details, retry_time):
82 """81 """
83 Initialize new `OverLimitFault` with relevant information.82 Initialize new `OverLimitFault` with relevant information.
@@ -97,8 +96,16 @@
97 Return the wrapped exception with a serialized body conforming to our96 Return the wrapped exception with a serialized body conforming to our
98 error format.97 error format.
99 """98 """
100 serializer = wsgi.Serializer(self._serialization_metadata)
101 content_type = request.best_match_content_type()99 content_type = request.best_match_content_type()
102 content = serializer.serialize(self.content, content_type)100 metadata = {"attributes": {"overLimitFault": "code"}}
101
102 serializer = {
103 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
104 xmlns=wsgi.XMLNS_V10),
105 'application/json': wsgi.JSONDictSerializer(),
106 }[content_type]
107
108 content = serializer.serialize(self.content)
103 self.wrapped_exc.body = content109 self.wrapped_exc.body = content
110
104 return self.wrapped_exc111 return self.wrapped_exc
105112
=== modified file 'nova/api/openstack/flavors.py'
--- nova/api/openstack/flavors.py 2011-05-06 20:13:35 +0000
+++ nova/api/openstack/flavors.py 2011-07-14 20:24:25 +0000
@@ -19,22 +19,13 @@
1919
20from nova import db20from nova import db
21from nova import exception21from nova import exception
22from nova.api.openstack import common
23from nova.api.openstack import views22from nova.api.openstack import views
2423from nova.api.openstack import wsgi
2524
26class Controller(common.OpenstackController):25
26class Controller(object):
27 """Flavor controller for the OpenStack API."""27 """Flavor controller for the OpenStack API."""
2828
29 _serialization_metadata = {
30 'application/xml': {
31 "attributes": {
32 "flavor": ["id", "name", "ram", "disk"],
33 "link": ["rel", "type", "href"],
34 }
35 }
36 }
37
38 def index(self, req):29 def index(self, req):
39 """Return all flavors in brief."""30 """Return all flavors in brief."""
40 items = self._get_flavors(req, is_detail=False)31 items = self._get_flavors(req, is_detail=False)
@@ -71,14 +62,33 @@
7162
7263
73class ControllerV10(Controller):64class ControllerV10(Controller):
65
74 def _get_view_builder(self, req):66 def _get_view_builder(self, req):
75 return views.flavors.ViewBuilder()67 return views.flavors.ViewBuilder()
7668
7769
78class ControllerV11(Controller):70class ControllerV11(Controller):
71
79 def _get_view_builder(self, req):72 def _get_view_builder(self, req):
80 base_url = req.application_url73 base_url = req.application_url
81 return views.flavors.ViewBuilderV11(base_url)74 return views.flavors.ViewBuilderV11(base_url)
8275
83 def get_default_xmlns(self, req):76
84 return common.XML_NS_V1177def create_resource(version='1.0'):
78 controller = {
79 '1.0': ControllerV10,
80 '1.1': ControllerV11,
81 }[version]()
82
83 xmlns = {
84 '1.0': wsgi.XMLNS_V10,
85 '1.1': wsgi.XMLNS_V11,
86 }[version]
87
88 body_serializers = {
89 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns),
90 }
91
92 serializer = wsgi.ResponseSerializer(body_serializers)
93
94 return wsgi.Resource(controller, serializer=serializer)
8595
=== modified file 'nova/api/openstack/image_metadata.py'
--- nova/api/openstack/image_metadata.py 2011-04-08 07:58:48 +0000
+++ nova/api/openstack/image_metadata.py 2011-07-14 20:24:25 +0000
@@ -16,24 +16,24 @@
16# under the License.16# under the License.
1717
18from webob import exc18from webob import exc
19from xml.dom import minidom
1920
20from nova import flags21from nova import flags
22from nova import image
21from nova import quota23from nova import quota
22from nova import utils24from nova import utils
23from nova import wsgi
24from nova.api.openstack import common
25from nova.api.openstack import faults25from nova.api.openstack import faults
26from nova.api.openstack import wsgi
2627
2728
28FLAGS = flags.FLAGS29FLAGS = flags.FLAGS
2930
3031
31class Controller(common.OpenstackController):32class Controller(object):
32 """The image metadata API controller for the Openstack API"""33 """The image metadata API controller for the Openstack API"""
3334
34 def __init__(self):35 def __init__(self):
35 self.image_service = utils.import_object(FLAGS.image_service)36 self.image_service = image.get_default_image_service()
36 super(Controller, self).__init__()
3737
38 def _get_metadata(self, context, image_id, image=None):38 def _get_metadata(self, context, image_id, image=None):
39 if not image:39 if not image:
@@ -60,13 +60,12 @@
60 context = req.environ['nova.context']60 context = req.environ['nova.context']
61 metadata = self._get_metadata(context, image_id)61 metadata = self._get_metadata(context, image_id)
62 if id in metadata:62 if id in metadata:
63 return {id: metadata[id]}63 return {'meta': {id: metadata[id]}}
64 else:64 else:
65 return faults.Fault(exc.HTTPNotFound())65 return faults.Fault(exc.HTTPNotFound())
6666
67 def create(self, req, image_id):67 def create(self, req, image_id, body):
68 context = req.environ['nova.context']68 context = req.environ['nova.context']
69 body = self._deserialize(req.body, req.get_content_type())
70 img = self.image_service.show(context, image_id)69 img = self.image_service.show(context, image_id)
71 metadata = self._get_metadata(context, image_id, img)70 metadata = self._get_metadata(context, image_id, img)
72 if 'metadata' in body:71 if 'metadata' in body:
@@ -77,18 +76,24 @@
77 self.image_service.update(context, image_id, img, None)76 self.image_service.update(context, image_id, img, None)
78 return dict(metadata=metadata)77 return dict(metadata=metadata)
7978
80 def update(self, req, image_id, id):79 def update(self, req, image_id, id, body):
81 context = req.environ['nova.context']80 context = req.environ['nova.context']
82 body = self._deserialize(req.body, req.get_content_type())81
83 if not id in body:82 try:
83 meta = body['meta']
84 except KeyError:
85 expl = _('Incorrect request body format')
86 raise exc.HTTPBadRequest(explanation=expl)
87
88 if not id in meta:
84 expl = _('Request body and URI mismatch')89 expl = _('Request body and URI mismatch')
85 raise exc.HTTPBadRequest(explanation=expl)90 raise exc.HTTPBadRequest(explanation=expl)
86 if len(body) > 1:91 if len(meta) > 1:
87 expl = _('Request body contains too many items')92 expl = _('Request body contains too many items')
88 raise exc.HTTPBadRequest(explanation=expl)93 raise exc.HTTPBadRequest(explanation=expl)
89 img = self.image_service.show(context, image_id)94 img = self.image_service.show(context, image_id)
90 metadata = self._get_metadata(context, image_id, img)95 metadata = self._get_metadata(context, image_id, img)
91 metadata[id] = body[id]96 metadata[id] = meta[id]
92 self._check_quota_limit(context, metadata)97 self._check_quota_limit(context, metadata)
93 img['properties'] = metadata98 img['properties'] = metadata
94 self.image_service.update(context, image_id, img, None)99 self.image_service.update(context, image_id, img, None)
@@ -104,3 +109,60 @@
104 metadata.pop(id)109 metadata.pop(id)
105 img['properties'] = metadata110 img['properties'] = metadata
106 self.image_service.update(context, image_id, img, None)111 self.image_service.update(context, image_id, img, None)
112
113
114class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer):
115 def __init__(self, xmlns=wsgi.XMLNS_V11):
116 super(ImageMetadataXMLSerializer, self).__init__(xmlns=xmlns)
117
118 def _meta_item_to_xml(self, doc, key, value):
119 node = doc.createElement('meta')
120 doc.appendChild(node)
121 node.setAttribute('key', '%s' % key)
122 text = doc.createTextNode('%s' % value)
123 node.appendChild(text)
124 return node
125
126 def meta_list_to_xml(self, xml_doc, meta_items):
127 container_node = xml_doc.createElement('metadata')
128 for (key, value) in meta_items:
129 item_node = self._meta_item_to_xml(xml_doc, key, value)
130 container_node.appendChild(item_node)
131 return container_node
132
133 def _meta_list_to_xml_string(self, metadata_dict):
134 xml_doc = minidom.Document()
135 items = metadata_dict['metadata'].items()
136 container_node = self.meta_list_to_xml(xml_doc, items)
137 xml_doc.appendChild(container_node)
138 self._add_xmlns(container_node)
139 return xml_doc.toprettyxml(indent=' ', encoding='UTF-8')
140
141 def index(self, metadata_dict):
142 return self._meta_list_to_xml_string(metadata_dict)
143
144 def create(self, metadata_dict):
145 return self._meta_list_to_xml_string(metadata_dict)
146
147 def _meta_item_to_xml_string(self, meta_item_dict):
148 xml_doc = minidom.Document()
149 item_key, item_value = meta_item_dict.items()[0]
150 item_node = self._meta_item_to_xml(xml_doc, item_key, item_value)
151 xml_doc.appendChild(item_node)
152 self._add_xmlns(item_node)
153 return xml_doc.toprettyxml(indent=' ', encoding='UTF-8')
154
155 def show(self, meta_item_dict):
156 return self._meta_item_to_xml_string(meta_item_dict['meta'])
157
158 def update(self, meta_item_dict):
159 return self._meta_item_to_xml_string(meta_item_dict['meta'])
160
161
162def create_resource():
163 body_serializers = {
164 'application/xml': ImageMetadataXMLSerializer(),
165 }
166 serializer = wsgi.ResponseSerializer(body_serializers)
167
168 return wsgi.Resource(Controller(), serializer=serializer)
107169
=== modified file 'nova/api/openstack/images.py'
--- nova/api/openstack/images.py 2011-04-21 17:29:11 +0000
+++ nova/api/openstack/images.py 2011-07-14 20:24:25 +0000
@@ -13,88 +13,73 @@
13# License for the specific language governing permissions and limitations13# License for the specific language governing permissions and limitations
14# under the License.14# under the License.
1515
16import urlparse
17import os.path
18
16import webob.exc19import webob.exc
20from xml.dom import minidom
1721
18from nova import compute22from nova import compute
19from nova import exception23from nova import exception
20from nova import flags24from nova import flags
25import nova.image
21from nova import log26from nova import log
22from nova import utils
23from nova.api.openstack import common27from nova.api.openstack import common
24from nova.api.openstack import faults28from nova.api.openstack import faults
29from nova.api.openstack import image_metadata
30from nova.api.openstack import servers
25from nova.api.openstack.views import images as images_view31from nova.api.openstack.views import images as images_view
32from nova.api.openstack import wsgi
2633
2734
28LOG = log.getLogger('nova.api.openstack.images')35LOG = log.getLogger('nova.api.openstack.images')
29FLAGS = flags.FLAGS36FLAGS = flags.FLAGS
3037
3138SUPPORTED_FILTERS = ['name', 'status']
32class Controller(common.OpenstackController):39
33 """Base `wsgi.Controller` for retrieving/displaying images."""40
3441class Controller(object):
35 _serialization_metadata = {42 """Base controller for retrieving/displaying images."""
36 'application/xml': {
37 "attributes": {
38 "image": ["id", "name", "updated", "created", "status",
39 "serverId", "progress"],
40 "link": ["rel", "type", "href"],
41 },
42 },
43 }
4443
45 def __init__(self, image_service=None, compute_service=None):44 def __init__(self, image_service=None, compute_service=None):
46 """Initialize new `ImageController`.45 """Initialize new `ImageController`.
4746
48 :param compute_service: `nova.compute.api:API`47 :param compute_service: `nova.compute.api:API`
49 :param image_service: `nova.image.service:BaseImageService`48 :param image_service: `nova.image.service:BaseImageService`
49
50 """50 """
51 _default_service = utils.import_object(flags.FLAGS.image_service)
52
53 self._compute_service = compute_service or compute.API()51 self._compute_service = compute_service or compute.API()
54 self._image_service = image_service or _default_service52 self._image_service = image_service or \
5553 nova.image.get_default_image_service()
56 def index(self, req):54
57 """Return an index listing of images available to the request.55 def _get_filters(self, req):
5856 """
59 :param req: `wsgi.Request` object57 Return a dictionary of query param filters from the request
60 """58
61 context = req.environ['nova.context']59 :param req: the Request object coming from the wsgi layer
62 images = self._image_service.index(context)60 :retval a dict of key/value filters
63 images = common.limited(images, req)61 """
64 builder = self.get_builder(req).build62 filters = {}
65 return dict(images=[builder(image, detail=False) for image in images])63 for param in req.str_params:
6664 if param in SUPPORTED_FILTERS or param.startswith('property-'):
67 def detail(self, req):65 filters[param] = req.str_params.get(param)
68 """Return a detailed index listing of images available to the request.66
6967 return filters
70 :param req: `wsgi.Request` object.
71 """
72 context = req.environ['nova.context']
73 images = self._image_service.detail(context)
74 images = common.limited(images, req)
75 builder = self.get_builder(req).build
76 return dict(images=[builder(image, detail=True) for image in images])
7768
78 def show(self, req, id):69 def show(self, req, id):
79 """Return detailed information about a specific image.70 """Return detailed information about a specific image.
8071
81 :param req: `wsgi.Request` object72 :param req: `wsgi.Request` object
82 :param id: Image identifier (integer)73 :param id: Image identifier
83 """74 """
84 context = req.environ['nova.context']75 context = req.environ['nova.context']
8576
86 try:77 try:
87 image_id = int(id)78 image = self._image_service.show(context, id)
88 except ValueError:79 except (exception.NotFound, exception.InvalidImageRef):
89 explanation = _("Image not found.")80 explanation = _("Image not found.")
90 raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation))81 raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation))
9182
92 try:
93 image = self._image_service.show(context, image_id)
94 except exception.NotFound:
95 explanation = _("Image '%d' not found.") % (image_id)
96 raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation))
97
98 return dict(image=self.get_builder(req).build(image, detail=True))83 return dict(image=self.get_builder(req).build(image, detail=True))
9984
100 def delete(self, req, id):85 def delete(self, req, id):
@@ -103,35 +88,78 @@
103 :param req: `wsgi.Request` object88 :param req: `wsgi.Request` object
104 :param id: Image identifier (integer)89 :param id: Image identifier (integer)
105 """90 """
106 image_id = id
107 context = req.environ['nova.context']91 context = req.environ['nova.context']
108 self._image_service.delete(context, image_id)92 self._image_service.delete(context, id)
109 return webob.exc.HTTPNoContent()93 return webob.exc.HTTPNoContent()
11094
111 def create(self, req):95 def create(self, req, body):
112 """Snapshot a server instance and save the image.96 """Snapshot or backup a server instance and save the image.
97
98 Images now have an `image_type` associated with them, which can be
99 'snapshot' or the backup type, like 'daily' or 'weekly'.
100
101 If the image_type is backup-like, then the rotation factor can be
102 included and that will cause the oldest backups that exceed the
103 rotation factor to be deleted.
113104
114 :param req: `wsgi.Request` object105 :param req: `wsgi.Request` object
115 """106 """
107 def get_param(param):
108 try:
109 return body["image"][param]
110 except KeyError:
111 raise webob.exc.HTTPBadRequest(explanation="Missing required "
112 "param: %s" % param)
113
116 context = req.environ['nova.context']114 context = req.environ['nova.context']
117 content_type = req.get_content_type()115 content_type = req.get_content_type()
118 image = self._deserialize(req.body, content_type)
119116
120 if not image:117 if not body:
121 raise webob.exc.HTTPBadRequest()118 raise webob.exc.HTTPBadRequest()
122119
120 image_type = body["image"].get("image_type", "snapshot")
121
123 try:122 try:
124 server_id = image["image"]["serverId"]123 server_id = self._server_id_from_req(req, body)
125 image_name = image["image"]["name"]
126 except KeyError:124 except KeyError:
127 raise webob.exc.HTTPBadRequest()125 raise webob.exc.HTTPBadRequest()
128126
129 image = self._compute_service.snapshot(context, server_id, image_name)127 image_name = get_param("name")
128 props = self._get_extra_properties(req, body)
129
130 if image_type == "snapshot":
131 image = self._compute_service.snapshot(
132 context, server_id, image_name,
133 extra_properties=props)
134 elif image_type == "backup":
135 # NOTE(sirp): Unlike snapshot, backup is not a customer facing
136 # API call; rather, it's used by the internal backup scheduler
137 if not FLAGS.allow_admin_api:
138 raise webob.exc.HTTPBadRequest(
139 explanation="Admin API Required")
140
141 backup_type = get_param("backup_type")
142 rotation = int(get_param("rotation"))
143
144 image = self._compute_service.backup(
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: