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

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

Commit message

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

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

Description of the change

Proposing a new approach for _get_vm_opaqueref:

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

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

merge trunk

now using refactored _get_vm_opaqueref

698. By Salvatore Orlando

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

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

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

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

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

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

699. By Salvatore Orlando

Merge trunk
Conflicts solved

700. By Salvatore Orlando

Merged with trunk

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

Fixed pep8 errors

701. By Salvatore Orlando

merge trunk

702. By Salvatore Orlando

Removed excess LOG.debug line

703. By Salvatore Orlando

merge trunk

704. By Salvatore Orlando

merge trunk

705. By Salvatore Orlando

Merge trunk

706. By Salvatore Orlando

merge trunk

707. By Salvatore Orlando

merge trunk

708. By Salvatore Orlando

merge trunk

709. By Salvatore Orlando

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

710. By Salvatore Orlando

merge trunk

711. By Salvatore Orlando

merge trunk

712. By Salvatore Orlando

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

713. By Salvatore Orlando

Addressing Rick Clark's comments.

714. By Salvatore Orlando

merge trunk

715. By Salvatore Orlando

merge trunk

716. By Salvatore Orlando

Sorted out a problem occurred with units tests for VM migration

717. By Salvatore Orlando

merge trunk

718. By Salvatore Orlando

Stubbing out utils.execute for migrate tests

719. By Salvatore Orlando

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

720. By Salvatore Orlando

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

721. By Salvatore Orlando

merge trunk

722. By Salvatore Orlando

minor pep8 fix in db/fakes.py

723. By Salvatore Orlando

merge trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.mailmap'
--- .mailmap 2011-03-03 05:10:42 +0000
+++ .mailmap 2011-03-25 13:43:55 +0000
@@ -28,7 +28,12 @@
28<matt.dietz@rackspace.com> <matthewdietz@Matthew-Dietzs-MacBook-Pro.local>28<matt.dietz@rackspace.com> <matthewdietz@Matthew-Dietzs-MacBook-Pro.local>
29<matt.dietz@rackspace.com> <mdietz@openstack>29<matt.dietz@rackspace.com> <mdietz@openstack>
30<mordred@inaugust.com> <mordred@hudson>30<mordred@inaugust.com> <mordred@hudson>
31<paul@openstack.org> <paul.voccio@rackspace.com>31<<<<<<< TREE
32<paul@openstack.org> <paul.voccio@rackspace.com>
33=======
34<nirmal.ranganathan@rackspace.com> <nirmal.ranganathan@rackspace.coom>
35<paul@openstack.org> <paul.voccio@rackspace.com>
36>>>>>>> MERGE-SOURCE
32<paul@openstack.org> <pvoccio@castor.local>37<paul@openstack.org> <pvoccio@castor.local>
33<rconradharris@gmail.com> <rick.harris@rackspace.com> 38<rconradharris@gmail.com> <rick.harris@rackspace.com>
34<rlane@wikimedia.org> <laner@controller>39<rlane@wikimedia.org> <laner@controller>
3540
=== modified file 'Authors'
--- Authors 2011-03-03 05:10:42 +0000
+++ Authors 2011-03-25 13:43:55 +0000
@@ -1,4 +1,5 @@
1Andy Smith <code@term.ie>1Andy Smith <code@term.ie>
2Andy Southgate <andy.southgate@citrix.com>
2Anne Gentle <anne@openstack.org>3Anne Gentle <anne@openstack.org>
3Anthony Young <sleepsonthefloor@gmail.com>4Anthony Young <sleepsonthefloor@gmail.com>
4Antony Messerli <ant@openstack.org>5Antony Messerli <ant@openstack.org>
@@ -19,7 +20,9 @@
19Ed Leafe <ed@leafe.com>20Ed Leafe <ed@leafe.com>
20Eldar Nugaev <enugaev@griddynamics.com>21Eldar Nugaev <enugaev@griddynamics.com>
21Eric Day <eday@oddments.org>22Eric Day <eday@oddments.org>
23Eric Windisch <eric@cloudscaling.com>
22Ewan Mellor <ewan.mellor@citrix.com>24Ewan Mellor <ewan.mellor@citrix.com>
25Gabe Westmaas <gabe.westmaas@rackspace.com>
23Hisaharu Ishii <ishii.hisaharu@lab.ntt.co.jp>26Hisaharu Ishii <ishii.hisaharu@lab.ntt.co.jp>
24Hisaki Ohara <hisaki.ohara@intel.com>27Hisaki Ohara <hisaki.ohara@intel.com>
25Ilya Alekseyev <ialekseev@griddynamics.com>28Ilya Alekseyev <ialekseev@griddynamics.com>
@@ -31,7 +34,12 @@
31Jonathan Bryce <jbryce@jbryce.com>34Jonathan Bryce <jbryce@jbryce.com>
32Jordan Rinke <jordan@openstack.org>35Jordan Rinke <jordan@openstack.org>
33Josh Durgin <joshd@hq.newdream.net>36Josh Durgin <joshd@hq.newdream.net>
34Josh Kearney <josh@jk0.org>37<<<<<<< TREE
38Josh Kearney <josh@jk0.org>
39=======
40Josh Kearney <josh@jk0.org>
41Josh Kleinpeter <josh@kleinpeter.org>
42>>>>>>> MERGE-SOURCE
35Joshua McKenty <jmckenty@gmail.com>43Joshua McKenty <jmckenty@gmail.com>
36Justin Santa Barbara <justin@fathomdb.com>44Justin Santa Barbara <justin@fathomdb.com>
37Kei Masumoto <masumotok@nttdata.co.jp>45Kei Masumoto <masumotok@nttdata.co.jp>
@@ -39,7 +47,12 @@
39Kevin L. Mitchell <kevin.mitchell@rackspace.com>47Kevin L. Mitchell <kevin.mitchell@rackspace.com>
40Koji Iida <iida.koji@lab.ntt.co.jp>48Koji Iida <iida.koji@lab.ntt.co.jp>
41Lorin Hochstein <lorin@isi.edu>49Lorin Hochstein <lorin@isi.edu>
42Masanori Itoh <itoumsn@nttdata.co.jp>50<<<<<<< TREE
51Masanori Itoh <itoumsn@nttdata.co.jp>
52=======
53Mark Washenberger <mark.washenberger@rackspace.com>
54Masanori Itoh <itoumsn@nttdata.co.jp>
55>>>>>>> MERGE-SOURCE
43Matt Dietz <matt.dietz@rackspace.com>56Matt Dietz <matt.dietz@rackspace.com>
44Michael Gundlach <michael.gundlach@rackspace.com>57Michael Gundlach <michael.gundlach@rackspace.com>
45Monsyne Dragon <mdragon@rackspace.com>58Monsyne Dragon <mdragon@rackspace.com>
@@ -58,6 +71,7 @@
58Ryan Lucio <rlucio@internap.com>71Ryan Lucio <rlucio@internap.com>
59Salvatore Orlando <salvatore.orlando@eu.citrix.com>72Salvatore Orlando <salvatore.orlando@eu.citrix.com>
60Sandy Walsh <sandy.walsh@rackspace.com>73Sandy Walsh <sandy.walsh@rackspace.com>
74Sateesh Chodapuneedi <sateesh.chodapuneedi@citrix.com>
61Soren Hansen <soren.hansen@rackspace.com>75Soren Hansen <soren.hansen@rackspace.com>
62Thierry Carrez <thierry@openstack.org>76Thierry Carrez <thierry@openstack.org>
63Todd Willey <todd@ansolabs.com>77Todd Willey <todd@ansolabs.com>
6478
=== modified file 'MANIFEST.in'
--- MANIFEST.in 2011-02-24 14:19:29 +0000
+++ MANIFEST.in 2011-03-25 13:43:55 +0000
@@ -25,6 +25,7 @@
25include nova/db/sqlalchemy/migrate_repo/README25include nova/db/sqlalchemy/migrate_repo/README
26include nova/virt/interfaces.template26include nova/virt/interfaces.template
27include nova/virt/libvirt*.xml.template27include nova/virt/libvirt*.xml.template
28include nova/virt/cpuinfo.xml.template
28include nova/tests/CA/29include nova/tests/CA/
29include nova/tests/CA/cacert.pem30include nova/tests/CA/cacert.pem
30include nova/tests/CA/private/31include nova/tests/CA/private/
3132
=== modified file 'bin/nova-ajax-console-proxy'
--- bin/nova-ajax-console-proxy 2011-02-25 21:15:51 +0000
+++ bin/nova-ajax-console-proxy 2011-03-25 13:43:55 +0000
@@ -1,5 +1,5 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# pylint: disable-msg=C01032# pylint: disable=C0103
3# vim: tabstop=4 shiftwidth=4 softtabstop=43# vim: tabstop=4 shiftwidth=4 softtabstop=4
44
5# Copyright 2010 United States Government as represented by the5# Copyright 2010 United States Government as represented by the
66
=== modified file 'bin/nova-api'
--- bin/nova-api 2011-03-09 00:45:20 +0000
+++ bin/nova-api 2011-03-25 13:43:55 +0000
@@ -1,5 +1,5 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# pylint: disable-msg=C01032# pylint: disable=C0103
3# vim: tabstop=4 shiftwidth=4 softtabstop=43# vim: tabstop=4 shiftwidth=4 softtabstop=4
44
5# Copyright 2010 United States Government as represented by the5# Copyright 2010 United States Government as represented by the
66
=== modified file 'bin/nova-dhcpbridge'
--- bin/nova-dhcpbridge 2011-02-23 19:20:52 +0000
+++ bin/nova-dhcpbridge 2011-03-25 13:43:55 +0000
@@ -94,7 +94,7 @@
94 """Get the list of hosts for an interface."""94 """Get the list of hosts for an interface."""
95 ctxt = context.get_admin_context()95 ctxt = context.get_admin_context()
96 network_ref = db.network_get_by_bridge(ctxt, interface)96 network_ref = db.network_get_by_bridge(ctxt, interface)
97 return linux_net.get_dhcp_hosts(ctxt, network_ref['id'])97 return linux_net.get_dhcp_leases(ctxt, network_ref['id'])
9898
9999
100def main():100def main():
101101
=== modified file 'bin/nova-direct-api'
--- bin/nova-direct-api 2011-02-23 23:26:52 +0000
+++ bin/nova-direct-api 2011-03-25 13:43:55 +0000
@@ -1,5 +1,5 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2# pylint: disable-msg=C01032# pylint: disable=C0103
3# vim: tabstop=4 shiftwidth=4 softtabstop=43# vim: tabstop=4 shiftwidth=4 softtabstop=4
44
5# Copyright 2010 United States Government as represented by the5# Copyright 2010 United States Government as represented by the
@@ -34,29 +34,71 @@
3434
35gettext.install('nova', unicode=1)35gettext.install('nova', unicode=1)
3636
37from nova import compute
37from nova import flags38from nova import flags
38from nova import log as logging39<<<<<<< TREE
40from nova import log as logging
41=======
42from nova import log as logging
43from nova import network
44>>>>>>> MERGE-SOURCE
39from nova import utils45from nova import utils
46from nova import volume
40from nova import wsgi47from nova import wsgi
41from nova.api import direct48from nova.api import direct
42from nova.compute import api as compute_api
4349
4450
45FLAGS = flags.FLAGS51FLAGS = flags.FLAGS
46flags.DEFINE_integer('direct_port', 8001, 'Direct API port')52flags.DEFINE_integer('direct_port', 8001, 'Direct API port')
47flags.DEFINE_string('direct_host', '0.0.0.0', 'Direct API host')53flags.DEFINE_string('direct_host', '0.0.0.0', 'Direct API host')
48flags.DEFINE_flag(flags.HelpFlag())54<<<<<<< TREE
49flags.DEFINE_flag(flags.HelpshortFlag())55flags.DEFINE_flag(flags.HelpFlag())
50flags.DEFINE_flag(flags.HelpXMLFlag())56flags.DEFINE_flag(flags.HelpshortFlag())
5157flags.DEFINE_flag(flags.HelpXMLFlag())
58
59=======
60flags.DEFINE_flag(flags.HelpFlag())
61flags.DEFINE_flag(flags.HelpshortFlag())
62flags.DEFINE_flag(flags.HelpXMLFlag())
63
64
65# An example of an API that only exposes read-only methods.
66# In this case we're just limiting which methods are exposed.
67class ReadOnlyCompute(direct.Limited):
68 """Read-only Compute API."""
69
70 _allowed = ['get', 'get_all', 'get_console_output']
71
72
73# An example of an API that provides a backwards compatibility layer.
74# In this case we're overwriting the implementation to ensure
75# compatibility with an older version. In reality we would want the
76# "description=None" to be part of the actual API so that code
77# like this isn't even necessary, but this example shows what one can
78# do if that isn't the situation.
79class VolumeVersionOne(direct.Limited):
80 _allowed = ['create', 'delete', 'update', 'get']
81
82 def create(self, context, size, name):
83 self.proxy.create(context, size, name, description=None)
84
85>>>>>>> MERGE-SOURCE
5286
53if __name__ == '__main__':87if __name__ == '__main__':
54 utils.default_flagfile()88 utils.default_flagfile()
55 FLAGS(sys.argv)89 FLAGS(sys.argv)
56 logging.setup()90 logging.setup()
5791
58 direct.register_service('compute', compute_api.API())92 direct.register_service('compute', compute.API())
93 direct.register_service('volume', volume.API())
94 direct.register_service('network', network.API())
59 direct.register_service('reflect', direct.Reflection())95 direct.register_service('reflect', direct.Reflection())
96
97 # Here is how we could expose the code in the examples above.
98 #direct.register_service('compute-readonly',
99 # ReadOnlyCompute(compute.API()))
100 #direct.register_service('volume-v1', VolumeVersionOne(volume.API()))
101
60 router = direct.Router()102 router = direct.Router()
61 with_json = direct.JsonParamsMiddleware(router)103 with_json = direct.JsonParamsMiddleware(router)
62 with_req = direct.PostParamsMiddleware(with_json)104 with_req = direct.PostParamsMiddleware(with_json)
63105
=== modified file 'bin/nova-instancemonitor'
--- bin/nova-instancemonitor 2011-02-21 21:46:41 +0000
+++ bin/nova-instancemonitor 2011-03-25 13:43:55 +0000
@@ -50,7 +50,7 @@
5050
51if __name__ == '__builtin__':51if __name__ == '__builtin__':
52 LOG.warn(_('Starting instance monitor'))52 LOG.warn(_('Starting instance monitor'))
53 # pylint: disable-msg=C010353 # pylint: disable=C0103
54 monitor = monitor.InstanceMonitor()54 monitor = monitor.InstanceMonitor()
5555
56 # This is the parent service that twistd will be looking for when it56 # This is the parent service that twistd will be looking for when it
5757
=== modified file 'bin/nova-manage'
--- bin/nova-manage 2011-03-13 09:49:56 +0000
+++ bin/nova-manage 2011-03-25 13:43:55 +0000
@@ -96,10 +96,18 @@
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')99<<<<<<< TREE
100flags.DEFINE_flag(flags.HelpFlag())100flags.DECLARE('images_path', 'nova.image.local')
101flags.DEFINE_flag(flags.HelpshortFlag())101flags.DEFINE_flag(flags.HelpFlag())
102flags.DEFINE_flag(flags.HelpXMLFlag())102flags.DEFINE_flag(flags.HelpshortFlag())
103flags.DEFINE_flag(flags.HelpXMLFlag())
104=======
105flags.DECLARE('images_path', 'nova.image.local')
106flags.DECLARE('libvirt_type', 'nova.virt.libvirt_conn')
107flags.DEFINE_flag(flags.HelpFlag())
108flags.DEFINE_flag(flags.HelpshortFlag())
109flags.DEFINE_flag(flags.HelpXMLFlag())
110>>>>>>> MERGE-SOURCE
103111
104112
105def param2id(object_id):113def param2id(object_id):
@@ -437,6 +445,7 @@
437 "been created.\nPlease create a database by running a "445 "been created.\nPlease create a database by running a "
438 "nova-api server on this host.")446 "nova-api server on this host.")
439447
448<<<<<<< TREE
440AccountCommands = ProjectCommands449AccountCommands = ProjectCommands
441450
442451
@@ -470,6 +479,46 @@
470 fixed_ip['address'],479 fixed_ip['address'],
471 mac_address, hostname, host)480 mac_address, hostname, host)
472481
482=======
483AccountCommands = ProjectCommands
484
485
486class FixedIpCommands(object):
487 """Class for managing fixed ip."""
488
489 def list(self, host=None):
490 """Lists all fixed ips (optionally by host) arguments: [host]"""
491 ctxt = context.get_admin_context()
492
493 try:
494 if host == None:
495 fixed_ips = db.fixed_ip_get_all(ctxt)
496 else:
497 fixed_ips = db.fixed_ip_get_all_by_host(ctxt, host)
498 except exception.NotFound as ex:
499 print "error: %s" % ex
500 sys.exit(2)
501
502 print "%-18s\t%-15s\t%-17s\t%-15s\t%s" % (_('network'),
503 _('IP address'),
504 _('MAC address'),
505 _('hostname'),
506 _('host'))
507 for fixed_ip in fixed_ips:
508 hostname = None
509 host = None
510 mac_address = None
511 if fixed_ip['instance']:
512 instance = fixed_ip['instance']
513 hostname = instance['hostname']
514 host = instance['host']
515 mac_address = instance['mac_address']
516 print "%-18s\t%-15s\t%-17s\t%-15s\t%s" % (
517 fixed_ip['network']['cidr'],
518 fixed_ip['address'],
519 mac_address, hostname, host)
520
521>>>>>>> MERGE-SOURCE
473522
474class FloatingIpCommands(object):523class FloatingIpCommands(object):
475 """Class for managing floating ip."""524 """Class for managing floating ip."""
@@ -513,11 +562,12 @@
513 network_size=None, vlan_start=None,562 network_size=None, vlan_start=None,
514 vpn_start=None, fixed_range_v6=None, label='public'):563 vpn_start=None, fixed_range_v6=None, label='public'):
515 """Creates fixed ips for host by range564 """Creates fixed ips for host by range
516 arguments: [fixed_range=FLAG], [num_networks=FLAG],565 arguments: fixed_range=FLAG, [num_networks=FLAG],
517 [network_size=FLAG], [vlan_start=FLAG],566 [network_size=FLAG], [vlan_start=FLAG],
518 [vpn_start=FLAG], [fixed_range_v6=FLAG]"""567 [vpn_start=FLAG], [fixed_range_v6=FLAG]"""
519 if not fixed_range:568 if not fixed_range:
520 fixed_range = FLAGS.fixed_range569 raise TypeError(_('Fixed range in the form of 10.0.0.0/8 is '
570 'required to create networks.'))
521 if not num_networks:571 if not num_networks:
522 num_networks = FLAGS.num_networks572 num_networks = FLAGS.num_networks
523 if not network_size:573 if not network_size:
@@ -536,28 +586,89 @@
536 vlan_start=int(vlan_start),586 vlan_start=int(vlan_start),
537 vpn_start=int(vpn_start),587 vpn_start=int(vpn_start),
538 cidr_v6=fixed_range_v6,588 cidr_v6=fixed_range_v6,
539 label=label)589<<<<<<< TREE
540590 label=label)
541 def list(self):591
542 """List all created networks"""592 def list(self):
543 print "%-18s\t%-15s\t%-15s\t%-15s" % (_('network'),593 """List all created networks"""
544 _('netmask'),594 print "%-18s\t%-15s\t%-15s\t%-15s" % (_('network'),
545 _('start address'),595 _('netmask'),
546 'DNS')596 _('start address'),
547 for network in db.network_get_all(context.get_admin_context()):597 'DNS')
548 print "%-18s\t%-15s\t%-15s\t%-15s" % (network.cidr,598 for network in db.network_get_all(context.get_admin_context()):
549 network.netmask,599 print "%-18s\t%-15s\t%-15s\t%-15s" % (network.cidr,
550 network.dhcp_start,600 network.netmask,
551 network.dns)601 network.dhcp_start,
552602 network.dns)
553 def delete(self, fixed_range):603
554 """Deletes a network"""604 def delete(self, fixed_range):
555 network = db.network_get_by_cidr(context.get_admin_context(), \605 """Deletes a network"""
556 fixed_range)606 network = db.network_get_by_cidr(context.get_admin_context(), \
557 if network.project_id is not None:607 fixed_range)
558 raise ValueError(_('Network must be disassociated from project %s'608 if network.project_id is not None:
559 ' before delete' % network.project_id))609 raise ValueError(_('Network must be disassociated from project %s'
560 db.network_delete_safe(context.get_admin_context(), network.id)610 ' before delete' % network.project_id))
611 db.network_delete_safe(context.get_admin_context(), network.id)
612=======
613 label=label)
614
615 def list(self):
616 """List all created networks"""
617 print "%-18s\t%-15s\t%-15s\t%-15s" % (_('network'),
618 _('netmask'),
619 _('start address'),
620 'DNS')
621 for network in db.network_get_all(context.get_admin_context()):
622 print "%-18s\t%-15s\t%-15s\t%-15s" % (network.cidr,
623 network.netmask,
624 network.dhcp_start,
625 network.dns)
626
627 def delete(self, fixed_range):
628 """Deletes a network"""
629 network = db.network_get_by_cidr(context.get_admin_context(), \
630 fixed_range)
631 if network.project_id is not None:
632 raise ValueError(_('Network must be disassociated from project %s'
633 ' before delete' % network.project_id))
634 db.network_delete_safe(context.get_admin_context(), network.id)
635
636
637class VmCommands(object):
638 """Class for mangaging VM instances."""
639
640 def live_migration(self, ec2_id, dest):
641 """Migrates a running instance to a new machine.
642
643 :param ec2_id: instance id which comes from euca-describe-instance.
644 :param dest: destination host name.
645
646 """
647
648 ctxt = context.get_admin_context()
649 instance_id = ec2utils.ec2_id_to_id(ec2_id)
650
651 if (FLAGS.connection_type != 'libvirt' or
652 (FLAGS.connection_type == 'libvirt' and
653 FLAGS.libvirt_type not in ['kvm', 'qemu'])):
654 msg = _('Only KVM and QEmu are supported for now. Sorry!')
655 raise exception.Error(msg)
656
657 if (FLAGS.volume_driver != 'nova.volume.driver.AOEDriver' and \
658 FLAGS.volume_driver != 'nova.volume.driver.ISCSIDriver'):
659 msg = _("Support only AOEDriver and ISCSIDriver. Sorry!")
660 raise exception.Error(msg)
661
662 rpc.call(ctxt,
663 FLAGS.scheduler_topic,
664 {"method": "live_migration",
665 "args": {"instance_id": instance_id,
666 "dest": dest,
667 "topic": FLAGS.compute_topic}})
668
669 print _('Migration of %s initiated.'
670 'Check its progress using euca-describe-instances.') % ec2_id
671>>>>>>> MERGE-SOURCE
561672
562673
563class ServiceCommands(object):674class ServiceCommands(object):
@@ -604,6 +715,59 @@
604 return715 return
605 db.service_update(ctxt, svc['id'], {'disabled': True})716 db.service_update(ctxt, svc['id'], {'disabled': True})
606717
718 def describe_resource(self, host):
719 """Describes cpu/memory/hdd info for host.
720
721 :param host: hostname.
722
723 """
724
725 result = rpc.call(context.get_admin_context(),
726 FLAGS.scheduler_topic,
727 {"method": "show_host_resources",
728 "args": {"host": host}})
729
730 if type(result) != dict:
731 print _('An unexpected error has occurred.')
732 print _('[Result]'), result
733 else:
734 cpu = result['resource']['vcpus']
735 mem = result['resource']['memory_mb']
736 hdd = result['resource']['local_gb']
737 cpu_u = result['resource']['vcpus_used']
738 mem_u = result['resource']['memory_mb_used']
739 hdd_u = result['resource']['local_gb_used']
740
741 print 'HOST\t\t\tPROJECT\t\tcpu\tmem(mb)\tdisk(gb)'
742 print '%s(total)\t\t\t%s\t%s\t%s' % (host, cpu, mem, hdd)
743 print '%s(used)\t\t\t%s\t%s\t%s' % (host, cpu_u, mem_u, hdd_u)
744 for p_id, val in result['usage'].items():
745 print '%s\t\t%s\t\t%s\t%s\t%s' % (host,
746 p_id,
747 val['vcpus'],
748 val['memory_mb'],
749 val['local_gb'])
750
751 def update_resource(self, host):
752 """Updates available vcpu/memory/disk info for host.
753
754 :param host: hostname.
755
756 """
757
758 ctxt = context.get_admin_context()
759 service_refs = db.service_get_all_by_host(ctxt, host)
760 if len(service_refs) <= 0:
761 raise exception.Invalid(_('%s does not exist.') % host)
762
763 service_refs = [s for s in service_refs if s['topic'] == 'compute']
764 if len(service_refs) <= 0:
765 raise exception.Invalid(_('%s is not compute node.') % host)
766
767 rpc.call(ctxt,
768 db.queue_get_for(ctxt, FLAGS.compute_topic, host),
769 {"method": "update_available_resource"})
770
607771
608class LogCommands(object):772class LogCommands(object):
609 def request(self, request_id, logfile='/var/log/nova.log'):773 def request(self, request_id, logfile='/var/log/nova.log'):
@@ -629,6 +793,49 @@
629 print migration.db_version()793 print migration.db_version()
630794
631795
796class InstanceCommands(object):
797 """Class for managing instances."""
798
799 def list(self, host=None, instance=None):
800 """Show a list of all instances"""
801 print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
802 " %-10s %-10s %-10s %-5s" % (
803 _('instance'),
804 _('node'),
805 _('type'),
806 _('state'),
807 _('launched'),
808 _('image'),
809 _('kernel'),
810 _('ramdisk'),
811 _('project'),
812 _('user'),
813 _('zone'),
814 _('index'))
815
816 if host == None:
817 instances = db.instance_get_all(context.get_admin_context())
818 else:
819 instances = db.instance_get_all_by_host(
820 context.get_admin_context(), host)
821
822 for instance in instances:
823 print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
824 " %-10s %-10s %-10s %-5d" % (
825 instance['hostname'],
826 instance['host'],
827 instance['instance_type'],
828 instance['state_description'],
829 instance['launched_at'],
830 instance['image_id'],
831 instance['kernel_id'],
832 instance['ramdisk_id'],
833 instance['project_id'],
834 instance['user_id'],
835 instance['availability_zone'],
836 instance['launch_index'])
837
838
632class VolumeCommands(object):839class VolumeCommands(object):
633 """Methods for dealing with a cloud in an odd state"""840 """Methods for dealing with a cloud in an odd state"""
634841
@@ -676,6 +883,7 @@
676 "mountpoint": volume['mountpoint']}})883 "mountpoint": volume['mountpoint']}})
677884
678885
886<<<<<<< TREE
679class InstanceTypeCommands(object):887class InstanceTypeCommands(object):
680 """Class for managing instance types / flavors."""888 """Class for managing instance types / flavors."""
681889
@@ -898,6 +1106,230 @@
898 self._convert_images(machine_images)1106 self._convert_images(machine_images)
8991107
9001108
1109=======
1110class InstanceTypeCommands(object):
1111 """Class for managing instance types / flavors."""
1112
1113 def _print_instance_types(self, n, val):
1114 deleted = ('', ', inactive')[val["deleted"] == 1]
1115 print ("%s: Memory: %sMB, VCPUS: %s, Storage: %sGB, FlavorID: %s, "
1116 "Swap: %sGB, RXTX Quota: %sGB, RXTX Cap: %sMB%s") % (
1117 n, val["memory_mb"], val["vcpus"], val["local_gb"],
1118 val["flavorid"], val["swap"], val["rxtx_quota"],
1119 val["rxtx_cap"], deleted)
1120
1121 def create(self, name, memory, vcpus, local_gb, flavorid,
1122 swap=0, rxtx_quota=0, rxtx_cap=0):
1123 """Creates instance types / flavors
1124 arguments: name memory vcpus local_gb flavorid [swap] [rxtx_quota]
1125 [rxtx_cap]
1126 """
1127 try:
1128 instance_types.create(name, memory, vcpus, local_gb,
1129 flavorid, swap, rxtx_quota, rxtx_cap)
1130 except exception.InvalidInputException:
1131 print "Must supply valid parameters to create instance type"
1132 print e
1133 sys.exit(1)
1134 except exception.DBError, e:
1135 print "DB Error: %s" % e
1136 sys.exit(2)
1137 except:
1138 print "Unknown error"
1139 sys.exit(3)
1140 else:
1141 print "%s created" % name
1142
1143 def delete(self, name, purge=None):
1144 """Marks instance types / flavors as deleted
1145 arguments: name"""
1146 try:
1147 if purge == "--purge":
1148 instance_types.purge(name)
1149 verb = "purged"
1150 else:
1151 instance_types.destroy(name)
1152 verb = "deleted"
1153 except exception.ApiError:
1154 print "Valid instance type name is required"
1155 sys.exit(1)
1156 except exception.DBError, e:
1157 print "DB Error: %s" % e
1158 sys.exit(2)
1159 except:
1160 sys.exit(3)
1161 else:
1162 print "%s %s" % (name, verb)
1163
1164 def list(self, name=None):
1165 """Lists all active or specific instance types / flavors
1166 arguments: [name]"""
1167 try:
1168 if name == None:
1169 inst_types = instance_types.get_all_types()
1170 elif name == "--all":
1171 inst_types = instance_types.get_all_types(True)
1172 else:
1173 inst_types = instance_types.get_instance_type(name)
1174 except exception.DBError, e:
1175 _db_error(e)
1176 if isinstance(inst_types.values()[0], dict):
1177 for k, v in inst_types.iteritems():
1178 self._print_instance_types(k, v)
1179 else:
1180 self._print_instance_types(name, inst_types)
1181
1182
1183class ImageCommands(object):
1184 """Methods for dealing with a cloud in an odd state"""
1185
1186 def __init__(self, *args, **kwargs):
1187 self.image_service = utils.import_object(FLAGS.image_service)
1188
1189 def _register(self, image_type, disk_format, container_format,
1190 path, owner, name=None, is_public='T',
1191 architecture='x86_64', kernel_id=None, ramdisk_id=None):
1192 meta = {'is_public': True,
1193 'name': name,
1194 'disk_format': disk_format,
1195 'container_format': container_format,
1196 'properties': {'image_state': 'available',
1197 'owner': owner,
1198 'type': image_type,
1199 'architecture': architecture,
1200 'image_location': 'local',
1201 'is_public': (is_public == 'T')}}
1202 print image_type, meta
1203 if kernel_id:
1204 meta['properties']['kernel_id'] = int(kernel_id)
1205 if ramdisk_id:
1206 meta['properties']['ramdisk_id'] = int(ramdisk_id)
1207 elevated = context.get_admin_context()
1208 try:
1209 with open(path) as ifile:
1210 image = self.image_service.create(elevated, meta, ifile)
1211 new = image['id']
1212 print _("Image registered to %(new)s (%(new)08x).") % locals()
1213 return new
1214 except Exception as exc:
1215 print _("Failed to register %(path)s: %(exc)s") % locals()
1216
1217 def all_register(self, image, kernel, ramdisk, owner, name=None,
1218 is_public='T', architecture='x86_64'):
1219 """Uploads an image, kernel, and ramdisk into the image_service
1220 arguments: image kernel ramdisk owner [name] [is_public='T']
1221 [architecture='x86_64']"""
1222 kernel_id = self.kernel_register(kernel, owner, None,
1223 is_public, architecture)
1224 ramdisk_id = self.ramdisk_register(ramdisk, owner, None,
1225 is_public, architecture)
1226 self.image_register(image, owner, name, is_public,
1227 architecture, kernel_id, ramdisk_id)
1228
1229 def image_register(self, path, owner, name=None, is_public='T',
1230 architecture='x86_64', kernel_id=None, ramdisk_id=None,
1231 disk_format='ami', container_format='ami'):
1232 """Uploads an image into the image_service
1233 arguments: path owner [name] [is_public='T'] [architecture='x86_64']
1234 [kernel_id=None] [ramdisk_id=None]
1235 [disk_format='ami'] [container_format='ami']"""
1236 return self._register('machine', disk_format, container_format, path,
1237 owner, name, is_public, architecture,
1238 kernel_id, ramdisk_id)
1239
1240 def kernel_register(self, path, owner, name=None, is_public='T',
1241 architecture='x86_64'):
1242 """Uploads a kernel into the image_service
1243 arguments: path owner [name] [is_public='T'] [architecture='x86_64']
1244 """
1245 return self._register('kernel', 'aki', 'aki', path, owner, name,
1246 is_public, architecture)
1247
1248 def ramdisk_register(self, path, owner, name=None, is_public='T',
1249 architecture='x86_64'):
1250 """Uploads a ramdisk into the image_service
1251 arguments: path owner [name] [is_public='T'] [architecture='x86_64']
1252 """
1253 return self._register('ramdisk', 'ari', 'ari', path, owner, name,
1254 is_public, architecture)
1255
1256 def _lookup(self, old_image_id):
1257 try:
1258 internal_id = ec2utils.ec2_id_to_id(old_image_id)
1259 image = self.image_service.show(context, internal_id)
1260 except exception.NotFound:
1261 image = self.image_service.show_by_name(context, old_image_id)
1262 return image['id']
1263
1264 def _old_to_new(self, old):
1265 mapping = {'machine': 'ami',
1266 'kernel': 'aki',
1267 'ramdisk': 'ari'}
1268 container_format = mapping[old['type']]
1269 disk_format = container_format
1270 new = {'disk_format': disk_format,
1271 'container_format': container_format,
1272 'is_public': True,
1273 'name': old['imageId'],
1274 'properties': {'image_state': old['imageState'],
1275 'owner': old['imageOwnerId'],
1276 'architecture': old['architecture'],
1277 'type': old['type'],
1278 'image_location': old['imageLocation'],
1279 'is_public': old['isPublic']}}
1280 if old.get('kernelId'):
1281 new['properties']['kernel_id'] = self._lookup(old['kernelId'])
1282 if old.get('ramdiskId'):
1283 new['properties']['ramdisk_id'] = self._lookup(old['ramdiskId'])
1284 return new
1285
1286 def _convert_images(self, images):
1287 elevated = context.get_admin_context()
1288 for image_path, image_metadata in images.iteritems():
1289 meta = self._old_to_new(image_metadata)
1290 old = meta['name']
1291 try:
1292 with open(image_path) as ifile:
1293 image = self.image_service.create(elevated, meta, ifile)
1294 new = image['id']
1295 print _("Image %(old)s converted to " \
1296 "%(new)s (%(new)08x).") % locals()
1297 except Exception as exc:
1298 print _("Failed to convert %(old)s: %(exc)s") % locals()
1299
1300 def convert(self, directory):
1301 """Uploads old objectstore images in directory to new service
1302 arguments: directory"""
1303 machine_images = {}
1304 other_images = {}
1305 directory = os.path.abspath(directory)
1306 # NOTE(vish): If we're importing from the images path dir, attempt
1307 # to move the files out of the way before importing
1308 # so we aren't writing to the same directory. This
1309 # may fail if the dir was a mointpoint.
1310 if (FLAGS.image_service == 'nova.image.local.LocalImageService'
1311 and directory == os.path.abspath(FLAGS.images_path)):
1312 new_dir = "%s_bak" % directory
1313 os.move(directory, new_dir)
1314 os.mkdir(directory)
1315 directory = new_dir
1316 for fn in glob.glob("%s/*/info.json" % directory):
1317 try:
1318 image_path = os.path.join(fn.rpartition('/')[0], 'image')
1319 with open(fn) as metadata_file:
1320 image_metadata = json.load(metadata_file)
1321 if image_metadata['type'] == 'machine':
1322 machine_images[image_path] = image_metadata
1323 else:
1324 other_images[image_path] = image_metadata
1325 except Exception as exc:
1326 print _("Failed to load %(fn)s.") % locals()
1327 # NOTE(vish): do kernels and ramdisks first so images
1328 self._convert_images(other_images)
1329 self._convert_images(machine_images)
1330
1331
1332>>>>>>> MERGE-SOURCE
901CATEGORIES = [1333CATEGORIES = [
902 ('user', UserCommands),1334 ('user', UserCommands),
903 ('account', AccountCommands),1335 ('account', AccountCommands),
@@ -908,13 +1340,22 @@
908 ('fixed', FixedIpCommands),1340 ('fixed', FixedIpCommands),
909 ('floating', FloatingIpCommands),1341 ('floating', FloatingIpCommands),
910 ('network', NetworkCommands),1342 ('network', NetworkCommands),
1343 ('vm', VmCommands),
911 ('service', ServiceCommands),1344 ('service', ServiceCommands),
912 ('log', LogCommands),1345 ('log', LogCommands),
913 ('db', DbCommands),1346 ('db', DbCommands),
1347<<<<<<< TREE
914 ('volume', VolumeCommands),1348 ('volume', VolumeCommands),
915 ('instance_type', InstanceTypeCommands),1349 ('instance_type', InstanceTypeCommands),
916 ('image', ImageCommands),1350 ('image', ImageCommands),
917 ('flavor', InstanceTypeCommands)]1351 ('flavor', InstanceTypeCommands)]
1352=======
1353 ('volume', VolumeCommands),
1354 ('instance_type', InstanceTypeCommands),
1355 ('image', ImageCommands),
1356 ('flavor', InstanceTypeCommands),
1357 ('instance', InstanceCommands)]
1358>>>>>>> MERGE-SOURCE
9181359
9191360
920def lazy_match(name, key_value_tuples):1361def lazy_match(name, key_value_tuples):
9211362
=== modified file 'bin/nova-objectstore'
--- bin/nova-objectstore 2010-12-14 23:22:03 +0000
+++ bin/nova-objectstore 2011-03-25 13:43:55 +0000
@@ -36,9 +36,10 @@
36gettext.install('nova', unicode=1)36gettext.install('nova', unicode=1)
3737
38from nova import flags38from nova import flags
39from nova import log as logging
39from nova import utils40from nova import utils
40from nova import twistd41from nova import wsgi
41from nova.objectstore import handler42from nova.objectstore import s3server
4243
4344
44FLAGS = flags.FLAGS45FLAGS = flags.FLAGS
@@ -46,7 +47,9 @@
4647
47if __name__ == '__main__':48if __name__ == '__main__':
48 utils.default_flagfile()49 utils.default_flagfile()
49 twistd.serve(__file__)50 FLAGS(sys.argv)
5051 logging.setup()
51if __name__ == '__builtin__':52 router = s3server.S3Application(FLAGS.buckets_path)
52 application = handler.get_application() # pylint: disable-msg=C010353 server = wsgi.Server()
54 server.start(router, FLAGS.s3_port, host=FLAGS.s3_host)
55 server.wait()
5356
=== modified file 'bin/stack'
--- bin/stack 2011-01-18 15:24:20 +0000
+++ bin/stack 2011-03-25 13:43:55 +0000
@@ -59,11 +59,21 @@
5959
60def format_help(d):60def format_help(d):
61 """Format help text, keys are labels and values are descriptions."""61 """Format help text, keys are labels and values are descriptions."""
62 MAX_INDENT = 30
62 indent = max([len(k) for k in d])63 indent = max([len(k) for k in d])
64 if indent > MAX_INDENT:
65 indent = MAX_INDENT - 6
66
63 out = []67 out = []
64 for k, v in d.iteritems():68 for k, v in d.iteritems():
65 t = textwrap.TextWrapper(initial_indent=' %s ' % k.ljust(indent),69 if (len(k) + 6) > MAX_INDENT:
66 subsequent_indent=' ' * (indent + 6))70 out.extend([' %s' % k])
71 initial_indent = ' ' * (indent + 6)
72 else:
73 initial_indent = ' %s ' % k.ljust(indent)
74 subsequent_indent = ' ' * (indent + 6)
75 t = textwrap.TextWrapper(initial_indent=initial_indent,
76 subsequent_indent=subsequent_indent)
67 out.extend(t.wrap(v))77 out.extend(t.wrap(v))
68 return out78 return out
6979
7080
=== modified file 'contrib/boto_v6/ec2/connection.py'
--- contrib/boto_v6/ec2/connection.py 2011-01-12 03:05:27 +0000
+++ contrib/boto_v6/ec2/connection.py 2011-03-25 13:43:55 +0000
@@ -4,8 +4,10 @@
4@author: Nachi Ueno <ueno.nachi@lab.ntt.co.jp>4@author: Nachi Ueno <ueno.nachi@lab.ntt.co.jp>
5'''5'''
6import boto6import boto
7import base64
7import boto.ec28import boto.ec2
8from boto_v6.ec2.instance import ReservationV69from boto_v6.ec2.instance import ReservationV6
10from boto.ec2.securitygroup import SecurityGroup
911
1012
11class EC2ConnectionV6(boto.ec2.EC2Connection):13class EC2ConnectionV6(boto.ec2.EC2Connection):
@@ -39,3 +41,101 @@
39 self.build_filter_params(params, filters)41 self.build_filter_params(params, filters)
40 return self.get_list('DescribeInstancesV6', params,42 return self.get_list('DescribeInstancesV6', params,
41 [('item', ReservationV6)])43 [('item', ReservationV6)])
44
45 def run_instances(self, image_id, min_count=1, max_count=1,
46 key_name=None, security_groups=None,
47 user_data=None, addressing_type=None,
48 instance_type='m1.small', placement=None,
49 kernel_id=None, ramdisk_id=None,
50 monitoring_enabled=False, subnet_id=None,
51 block_device_map=None):
52 """
53 Runs an image on EC2.
54
55 :type image_id: string
56 :param image_id: The ID of the image to run
57
58 :type min_count: int
59 :param min_count: The minimum number of instances to launch
60
61 :type max_count: int
62 :param max_count: The maximum number of instances to launch
63
64 :type key_name: string
65 :param key_name: The name of the key pair with which to
66 launch instances
67
68 :type security_groups: list of strings
69 :param security_groups: The names of the security groups with
70 which to associate instances
71
72 :type user_data: string
73 :param user_data: The user data passed to the launched instances
74
75 :type instance_type: string
76 :param instance_type: The type of instance to run
77 (m1.small, m1.large, m1.xlarge)
78
79 :type placement: string
80 :param placement: The availability zone in which to launch
81 the instances
82
83 :type kernel_id: string
84 :param kernel_id: The ID of the kernel with which to
85 launch the instances
86
87 :type ramdisk_id: string
88 :param ramdisk_id: The ID of the RAM disk with which to
89 launch the instances
90
91 :type monitoring_enabled: bool
92 :param monitoring_enabled: Enable CloudWatch monitoring
93 on the instance.
94
95 :type subnet_id: string
96 :param subnet_id: The subnet ID within which to launch
97 the instances for VPC.
98
99 :type block_device_map:
100 :class:`boto.ec2.blockdevicemapping.BlockDeviceMapping`
101 :param block_device_map: A BlockDeviceMapping data structure
102 describing the EBS volumes associated
103 with the Image.
104
105 :rtype: Reservation
106 :return: The :class:`boto.ec2.instance.ReservationV6`
107 associated with the request for machines
108 """
109 params = {'ImageId': image_id,
110 'MinCount': min_count,
111 'MaxCount': max_count}
112 if key_name:
113 params['KeyName'] = key_name
114 if security_groups:
115 l = []
116 for group in security_groups:
117 if isinstance(group, SecurityGroup):
118 l.append(group.name)
119 else:
120 l.append(group)
121 self.build_list_params(params, l, 'SecurityGroup')
122 if user_data:
123 params['UserData'] = base64.b64encode(user_data)
124 if addressing_type:
125 params['AddressingType'] = addressing_type
126 if instance_type:
127 params['InstanceType'] = instance_type
128 if placement:
129 params['Placement.AvailabilityZone'] = placement
130 if kernel_id:
131 params['KernelId'] = kernel_id
132 if ramdisk_id:
133 params['RamdiskId'] = ramdisk_id
134 if monitoring_enabled:
135 params['Monitoring.Enabled'] = 'true'
136 if subnet_id:
137 params['SubnetId'] = subnet_id
138 if block_device_map:
139 block_device_map.build_list_params(params)
140 return self.get_object('RunInstances', params,
141 ReservationV6, verb='POST')
42142
=== modified file 'contrib/nova.sh'
--- contrib/nova.sh 2011-03-08 00:01:43 +0000
+++ contrib/nova.sh 2011-03-25 13:43:55 +0000
@@ -76,6 +76,7 @@
76 sudo apt-get install -y python-migrate python-eventlet python-gflags python-ipy python-tempita76 sudo apt-get install -y python-migrate python-eventlet python-gflags python-ipy python-tempita
77 sudo apt-get install -y python-libvirt python-libxml2 python-routes python-cheetah77 sudo apt-get install -y python-libvirt python-libxml2 python-routes python-cheetah
78 sudo apt-get install -y python-netaddr python-paste python-pastedeploy python-glance78 sudo apt-get install -y python-netaddr python-paste python-pastedeploy python-glance
79 sudo apt-get install -y python-multiprocessing
7980
80 if [ "$USE_IPV6" == 1 ]; then81 if [ "$USE_IPV6" == 1 ]; then
81 sudo apt-get install -y radvd82 sudo apt-get install -y radvd
8283
=== modified file 'doc/source/_static/tweaks.css'
--- doc/source/_static/tweaks.css 2010-11-11 22:32:24 +0000
+++ doc/source/_static/tweaks.css 2011-03-25 13:43:55 +0000
@@ -69,3 +69,150 @@
69.tweet_list li .tweet_avatar {69.tweet_list li .tweet_avatar {
70 float: left;70 float: left;
71}71}
72
73/* ------------------------------------------
74PURE CSS SPEECH BUBBLES
75by Nicolas Gallagher
76- http://nicolasgallagher.com/pure-css-speech-bubbles/
77
78http://nicolasgallagher.com
79http://twitter.com/necolas
80
81Created: 02 March 2010
82Version: 1.1 (21 October 2010)
83
84Dual licensed under MIT and GNU GPLv2 © Nicolas Gallagher
85------------------------------------------ */
86/* THE SPEECH BUBBLE
87------------------------------------------------------------------------------------------------------------------------------- */
88
89/* THE SPEECH BUBBLE
90------------------------------------------------------------------------------------------------------------------------------- */
91
92.triangle-border {
93 position:relative;
94 padding:15px;
95 margin:1em 0 3em;
96 border:5px solid #BC1518;
97 color:#333;
98 background:#fff;
99
100 /* css3 */
101 -moz-border-radius:10px;
102 -webkit-border-radius:10px;
103 border-radius:10px;
104}
105
106/* Variant : for left positioned triangle
107------------------------------------------ */
108
109.triangle-border.left {
110 margin-left:30px;
111}
112
113/* Variant : for right positioned triangle
114------------------------------------------ */
115
116.triangle-border.right {
117 margin-right:30px;
118}
119
120/* THE TRIANGLE
121------------------------------------------------------------------------------------------------------------------------------- */
122
123.triangle-border:before {
124 content:"";
125 display:block; /* reduce the damage in FF3.0 */
126 position:absolute;
127 bottom:-40px; /* value = - border-top-width - border-bottom-width */
128 left:40px; /* controls horizontal position */
129 width:0;
130 height:0;
131 border:20px solid transparent;
132 border-top-color:#BC1518;
133}
134
135/* creates the smaller triangle */
136.triangle-border:after {
137 content:"";
138 display:block; /* reduce the damage in FF3.0 */
139 position:absolute;
140 bottom:-26px; /* value = - border-top-width - border-bottom-width */
141 left:47px; /* value = (:before left) + (:before border-left) - (:after border-left) */
142 width:0;
143 height:0;
144 border:13px solid transparent;
145 border-top-color:#fff;
146}
147
148/* Variant : top
149------------------------------------------ */
150
151/* creates the larger triangle */
152.triangle-border.top:before {
153 top:-40px; /* value = - border-top-width - border-bottom-width */
154 right:40px; /* controls horizontal position */
155 bottom:auto;
156 left:auto;
157 border:20px solid transparent;
158 border-bottom-color:#BC1518;
159}
160
161/* creates the smaller triangle */
162.triangle-border.top:after {
163 top:-26px; /* value = - border-top-width - border-bottom-width */
164 right:47px; /* value = (:before right) + (:before border-right) - (:after border-right) */
165 bottom:auto;
166 left:auto;
167 border:13px solid transparent;
168 border-bottom-color:#fff;
169}
170
171/* Variant : left
172------------------------------------------ */
173
174/* creates the larger triangle */
175.triangle-border.left:before {
176 top:10px; /* controls vertical position */
177 left:-30px; /* value = - border-left-width - border-right-width */
178 bottom:auto;
179 border-width:15px 30px 15px 0;
180 border-style:solid;
181 border-color:transparent #BC1518;
182}
183
184/* creates the smaller triangle */
185.triangle-border.left:after {
186 top:16px; /* value = (:before top) + (:before border-top) - (:after border-top) */
187 left:-21px; /* value = - border-left-width - border-right-width */
188 bottom:auto;
189 border-width:9px 21px 9px 0;
190 border-style:solid;
191 border-color:transparent #fff;
192}
193
194/* Variant : right
195------------------------------------------ */
196
197/* creates the larger triangle */
198.triangle-border.right:before {
199 top:10px; /* controls vertical position */
200 right:-30px; /* value = - border-left-width - border-right-width */
201 bottom:auto;
202 left:auto;
203 border-width:15px 0 15px 30px;
204 border-style:solid;
205 border-color:transparent #BC1518;
206}
207
208/* creates the smaller triangle */
209.triangle-border.right:after {
210 top:16px; /* value = (:before top) + (:before border-top) - (:after border-top) */
211 right:-21px; /* value = - border-left-width - border-right-width */
212 bottom:auto;
213 left:auto;
214 border-width:9px 0 9px 21px;
215 border-style:solid;
216 border-color:transparent #fff;
217}
218
72219
=== modified file 'doc/source/_theme/layout.html'
--- doc/source/_theme/layout.html 2010-11-05 22:52:59 +0000
+++ doc/source/_theme/layout.html 2011-03-25 13:43:55 +0000
@@ -71,12 +71,21 @@
71 </p>71 </p>
72 </div>72 </div>
73 <script type="text/javascript">$('#searchbox').show(0);</script>73 <script type="text/javascript">$('#searchbox').show(0);</script>
74
75 <p class="triangle-border right">
76 Psst... hey. You're reading the latest content, but it might be out of sync with code. You can read <a href="http://nova.openstack.org/2011.1">Nova 2011.1 docs</a> or <a href="http://docs.openstack.org">all OpenStack docs</a> too.
77 </p>
78
74 {%- endif %}79 {%- endif %}
7580
76 {%- if pagename == "index" %}81 {%- if pagename == "index" %}
77 <h3>{{ _('Twitter Feed') }}</h3>82
83
84 <h3>{{ _('Twitter Feed') }}</h3>
78 <div id="twitter_feed" class='twitter_feed'></div>85 <div id="twitter_feed" class='twitter_feed'></div>
79 {%- endif %}86 {%- endif %}
87
88
8089
8190
82 {%- endblock %}91 {%- endblock %}
8392
=== added file 'doc/source/images/vmwareapi_blockdiagram.jpg'
84Binary files doc/source/images/vmwareapi_blockdiagram.jpg 1970-01-01 00:00:00 +0000 and doc/source/images/vmwareapi_blockdiagram.jpg 2011-03-25 13:43:55 +0000 differ93Binary files doc/source/images/vmwareapi_blockdiagram.jpg 1970-01-01 00:00:00 +0000 and doc/source/images/vmwareapi_blockdiagram.jpg 2011-03-25 13:43:55 +0000 differ
=== added file 'doc/source/vmwareapi_readme.rst'
--- doc/source/vmwareapi_readme.rst 1970-01-01 00:00:00 +0000
+++ doc/source/vmwareapi_readme.rst 2011-03-25 13:43:55 +0000
@@ -0,0 +1,218 @@
1..
2 Copyright (c) 2010 Citrix Systems, Inc.
3 Copyright 2010 OpenStack LLC.
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
17VMware ESX/ESXi Server Support for OpenStack Compute
18====================================================
19
20Introduction
21------------
22A module named 'vmwareapi' is added to 'nova.virt' to add support of VMware ESX/ESXi hypervisor to OpenStack compute (Nova). Nova may now use VMware vSphere as a compute provider.
23
24The basic requirement is to support VMware vSphere 4.1 as a compute provider within Nova. As the deployment architecture, support both ESX and ESXi. VM storage is restricted to VMFS volumes on local drives. vCenter is not required by the current design, and is not currently supported. Instead, Nova Compute talks directly to ESX/ESXi.
25
26The 'vmwareapi' module is integrated with Glance, so that VM images can be streamed from there for boot on ESXi using Glance server for image storage & retrieval.
27
28Currently supports Nova's flat networking model (Flat Manager) & VLAN networking model.
29
30.. image:: images/vmwareapi_blockdiagram.jpg
31
32
33System Requirements
34-------------------
35Following software components are required for building the cloud using OpenStack on top of ESX/ESXi Server(s):
36
37* OpenStack
38* Glance Image service
39* VMware ESX v4.1 or VMware ESXi(licensed) v4.1
40
41VMware ESX Requirements
42-----------------------
43* ESX credentials with administration/root privileges
44* Single local hard disk at the ESX host
45* An ESX Virtual Machine Port Group (For Flat Networking)
46* An ESX physical network adapter (For VLAN networking)
47* Need to enable "vSphere Web Access" in "vSphere client" UI at Configuration->Security Profile->Firewall
48
49Python dependencies
50-------------------
51* suds-0.4
52
53* Installation procedure on Ubuntu/Debian
54
55::
56
57 easy_install suds==0.4
58
59
60Configuration flags required for nova-compute
61---------------------------------------------
62::
63
64 --connection_type=vmwareapi
65 --vmwareapi_host_ip=<VMware ESX Host IP>
66 --vmwareapi_host_username=<VMware ESX Username>
67 --vmwareapi_host_password=<VMware ESX Password>
68 --network_driver=nova.network.vmwareapi_net [Optional, only for VLAN Networking]
69 --vlan_interface=<Physical ethernet adapter name in VMware ESX host for vlan networking E.g vmnic0> [Optional, only for VLAN Networking]
70
71
72Configuration flags required for nova-network
73---------------------------------------------
74::
75
76 --network_manager=nova.network.manager.FlatManager [or nova.network.manager.VlanManager]
77 --flat_network_bridge=<ESX Virtual Machine Port Group> [Optional, only for Flat Networking]
78
79
80Configuration flags required for nova-console
81---------------------------------------------
82::
83
84 --console_manager=nova.console.vmrc_manager.ConsoleVMRCManager
85 --console_driver=nova.console.vmrc.VMRCSessionConsole [Optional, only for OTP (One time Passwords) as against host credentials]
86
87
88Other flags
89-----------
90::
91
92 --image_service=nova.image.glance.GlanceImageService
93 --glance_host=<Glance Host>
94 --vmwareapi_wsdl_loc=<http://<WEB SERVER>/vimService.wsdl>
95
96Note:- Due to a faulty wsdl being shipped with ESX vSphere 4.1 we need a working wsdl which can to be mounted on any webserver. Follow the below steps to download the SDK,
97
98* Go to http://www.vmware.com/support/developer/vc-sdk/
99* Go to section VMware vSphere Web Services SDK 4.0
100* Click "Downloads"
101* Enter VMware credentials when prompted for download
102* Unzip the downloaded file vi-sdk-4.0.0-xxx.zip
103* Go to SDK->WSDL->vim25 & host the files "vimService.wsdl" and "vim.wsdl" in a WEB SERVER
104* Set the flag "--vmwareapi_wsdl_loc" with url, "http://<WEB SERVER>/vimService.wsdl"
105
106
107VLAN Network Manager
108--------------------
109VLAN network support is added through a custom network driver in the nova-compute node i.e "nova.network.vmwareapi_net" and it uses a Physical ethernet adapter on the VMware ESX/ESXi host for VLAN Networking (the name of the ethernet adapter is specified as vlan_interface flag in the nova-compute configuration flag) in the nova-compute node.
110
111Using the physical adapter name the associated Virtual Switch will be determined. In VMware ESX there can be only one Virtual Switch associated with a Physical adapter.
112
113When VM Spawn request is issued with a VLAN ID the work flow looks like,
114
1151. Check that a Physical adapter with the given name exists. If no, throw an error.If yes, goto next step.
116
1172. Check if a Virtual Switch is associated with the Physical ethernet adapter with vlan interface name. If no, throw an error. If yes, goto next step.
118
1193. Check if a port group with the network bridge name exists. If no, create a port group in the Virtual switch with the give name and VLAN id and goto step 6. If yes, goto next step.
120
1214. Check if the port group is associated with the Virtual Switch. If no, throw an error. If yes, goto next step.
122
1235. Check if the port group is associated with the given VLAN Id. If no, throw an error. If yes, goto next step.
124
1256. Spawn the VM using this Port Group as the Network Name for the VM.
126
127
128Guest console Support
129---------------------
130| VMware VMRC console is a built-in console method providing graphical control of the VM remotely.
131|
132| VMRC Console types supported:
133| # Host based credentials
134| Not secure (Sends ESX admin credentials in clear text)
135|
136| # OTP (One time passwords)
137| Secure but creates multiple session entries in DB for each OpenStack console create request.
138| Console sessions created is can be used only once.
139|
140| Install browser based VMware ESX plugins/activex on the client machine to connect
141|
142| Windows:-
143| Internet Explorer:
144| https://<VMware ESX Host>/ui/plugin/vmware-vmrc-win32-x86.exe
145|
146| Mozilla Firefox:
147| https://<VMware ESX Host>/ui/plugin/vmware-vmrc-win32-x86.xpi
148|
149| Linux:-
150| Mozilla Firefox
151| 32-Bit Linux:
152| https://<VMware ESX Host>/ui/plugin/vmware-vmrc-linux-x86.xpi
153|
154| 64-Bit Linux:
155| https://<VMware ESX Host>/ui/plugin/vmware-vmrc-linux-x64.xpi
156|
157| OpenStack Console Details:
158| console_type = vmrc+credentials | vmrc+session
159| host = <VMware ESX Host>
160| port = <VMware ESX Port>
161| password = {'vm_id': <VMware VM ID>,'username':<VMware ESX Username>, 'password':<VMware ESX Password>} //base64 + json encoded
162|
163| Instantiate the plugin/activex object
164| # In Internet Explorer
165| <object id='vmrc' classid='CLSID:B94C2238-346E-4C5E-9B36-8CC627F35574'>
166| </object>
167|
168| # Mozilla Firefox and other browsers
169| <object id='vmrc' type='application/x-vmware-vmrc;version=2.5.0.0'>
170| </object>
171|
172| Open vmrc connection
173| # Host based credentials [type=vmrc+credentials]
174| <script type="text/javascript">
175| var MODE_WINDOW = 2;
176| var vmrc = document.getElementById('vmrc');
177| vmrc.connect(<VMware ESX Host> + ':' + <VMware ESX Port>, <VMware ESX Username>, <VMware ESX Password>, '', <VMware VM ID>, MODE_WINDOW);
178| </script>
179|
180| # OTP (One time passwords) [type=vmrc+session]
181| <script type="text/javascript">
182| var MODE_WINDOW = 2;
183| var vmrc = document.getElementById('vmrc');
184| vmrc.connectWithSession(<VMware ESX Host> + ':' + <VMware ESX Port>, <VMware VM ID>, <VMware ESX Password>, MODE_WINDOW);
185| </script>
186
187
188Assumptions
189-----------
1901. The VMware images uploaded to the image repositories have VMware Tools installed.
191
192
193FAQ
194---
195
1961. What type of disk images are supported?
197
198* Only VMware VMDK's are currently supported and of that support is available only for thick disks, thin provisioned disks are not supported.
199
200
2012. How is IP address information injected into the guest?
202
203* IP address information is injected through 'machine.id' vmx parameter (equivalent to XenStore in XenServer). This information can be retrived inside the guest using VMware tools.
204
205
2063. What is the guest tool?
207
208* The guest tool is a small python script that should be run either as a service or added to system startup. This script configures networking on the guest. The guest tool is available at tools/esx/guest_tool.py
209
210
2114. What type of consoles are supported?
212
213* VMware VMRC based consoles are supported. There are 2 options for credentials one is OTP (Secure but creates multiple session entries in DB for each OpenStack console create request.) & other is host based credentials (It may not be secure as ESX credentials are transmitted as clear text).
214
2155. What does 'Vim' refer to as far as vmwareapi module is concerned?
216
217* Vim refers to VMware Virtual Infrastructure Methodology. This is not to be confused with "VIM" editor.
218
0219
=== modified file 'etc/api-paste.ini'
--- etc/api-paste.ini 2011-03-07 19:33:24 +0000
+++ etc/api-paste.ini 2011-03-25 13:43:55 +0000
@@ -67,10 +67,14 @@
67[composite:osapi]67[composite:osapi]
68use = egg:Paste#urlmap68use = egg:Paste#urlmap
69/: osversions69/: osversions
70/v1.0: openstackapi70/v1.0: openstackapi10
7171/v1.1: openstackapi11
72[pipeline:openstackapi]72
73pipeline = faultwrap auth ratelimit osapiapp73[pipeline:openstackapi10]
74pipeline = faultwrap auth ratelimit osapiapp10
75
76[pipeline:openstackapi11]
77pipeline = faultwrap auth ratelimit extensions osapiapp11
7478
75[filter:faultwrap]79[filter:faultwrap]
76paste.filter_factory = nova.api.openstack:FaultWrapper.factory80paste.filter_factory = nova.api.openstack:FaultWrapper.factory
@@ -79,10 +83,16 @@
79paste.filter_factory = nova.api.openstack.auth:AuthMiddleware.factory83paste.filter_factory = nova.api.openstack.auth:AuthMiddleware.factory
8084
81[filter:ratelimit]85[filter:ratelimit]
82paste.filter_factory = nova.api.openstack.ratelimiting:RateLimitingMiddleware.factory86paste.filter_factory = nova.api.openstack.limits:RateLimitingMiddleware.factory
8387
84[app:osapiapp]88[filter:extensions]
85paste.app_factory = nova.api.openstack:APIRouter.factory89paste.filter_factory = nova.api.openstack.extensions:ExtensionMiddleware.factory
90
91[app:osapiapp10]
92paste.app_factory = nova.api.openstack:APIRouterV10.factory
93
94[app:osapiapp11]
95paste.app_factory = nova.api.openstack:APIRouterV11.factory
8696
87[pipeline:osversions]97[pipeline:osversions]
88pipeline = faultwrap osversionapp98pipeline = faultwrap osversionapp
8999
=== modified file 'nova/api/direct.py'
--- nova/api/direct.py 2011-03-09 20:08:11 +0000
+++ nova/api/direct.py 2011-03-25 13:43:55 +0000
@@ -38,6 +38,7 @@
38import webob38import webob
3939
40from nova import context40from nova import context
41from nova import exception
41from nova import flags42from nova import flags
42from nova import utils43from nova import utils
43from nova import wsgi44from nova import wsgi
@@ -205,10 +206,59 @@
205 # NOTE(vish): make sure we have no unicode keys for py2.6.206 # NOTE(vish): make sure we have no unicode keys for py2.6.
206 params = dict([(str(k), v) for (k, v) in params.iteritems()])207 params = dict([(str(k), v) for (k, v) in params.iteritems()])
207 result = method(context, **params)208 result = method(context, **params)
209<<<<<<< TREE
208 if type(result) is dict or type(result) is list:210 if type(result) is dict or type(result) is list:
209 return self._serialize(result, req.best_match_content_type())211 return self._serialize(result, req.best_match_content_type())
210 else:212 else:
213=======
214 if result is None or type(result) is str or type(result) is unicode:
215>>>>>>> MERGE-SOURCE
211 return result216 return result
217 try:
218 return self._serialize(result, req.best_match_content_type())
219 except:
220 raise exception.Error("returned non-serializable type: %s"
221 % result)
222
223
224class Limited(object):
225 __notdoc = """Limit the available methods on a given object.
226
227 (Not a docstring so that the docstring can be conditionally overriden.)
228
229 Useful when defining a public API that only exposes a subset of an
230 internal API.
231
232 Expected usage of this class is to define a subclass that lists the allowed
233 methods in the 'allowed' variable.
234
235 Additionally where appropriate methods can be added or overwritten, for
236 example to provide backwards compatibility.
237
238 The wrapping approach has been chosen so that the wrapped API can maintain
239 its own internal consistency, for example if it calls "self.create" it
240 should get its own create method rather than anything we do here.
241
242 """
243
244 _allowed = None
245
246 def __init__(self, proxy):
247 self._proxy = proxy
248 if not self.__doc__:
249 self.__doc__ = proxy.__doc__
250 if not self._allowed:
251 self._allowed = []
252
253 def __getattr__(self, key):
254 """Only return methods that are named in self._allowed."""
255 if key not in self._allowed:
256 raise AttributeError()
257 return getattr(self._proxy, key)
258
259 def __dir__(self):
260 """Only return methods that are named in self._allowed."""
261 return [x for x in dir(self._proxy) if x in self._allowed]
212262
213263
214class Proxy(object):264class Proxy(object):
215265
=== modified file 'nova/api/ec2/__init__.py'
--- nova/api/ec2/__init__.py 2011-03-09 19:20:26 +0000
+++ nova/api/ec2/__init__.py 2011-03-25 13:43:55 +0000
@@ -31,7 +31,7 @@
31from nova import utils31from nova import utils
32from nova import wsgi32from nova import wsgi
33from nova.api.ec2 import apirequest33from nova.api.ec2 import apirequest
34from nova.api.ec2 import cloud34from nova.api.ec2 import ec2utils
35from nova.auth import manager35from nova.auth import manager
3636
3737
@@ -60,11 +60,22 @@
60 self.log_request_completion(rv, req, start)60 self.log_request_completion(rv, req, start)
61 return rv61 return rv
6262
63<<<<<<< TREE
63 def log_request_completion(self, response, request, start):64 def log_request_completion(self, response, request, start):
64 controller = request.environ.get('ec2.controller', None)65 controller = request.environ.get('ec2.controller', None)
65 if controller:66 if controller:
66 controller = controller.__class__.__name__67 controller = controller.__class__.__name__
67 action = request.environ.get('ec2.action', None)68 action = request.environ.get('ec2.action', None)
69=======
70 def log_request_completion(self, response, request, start):
71 apireq = request.environ.get('ec2.request', None)
72 if apireq:
73 controller = apireq.controller
74 action = apireq.action
75 else:
76 controller = None
77 action = None
78>>>>>>> MERGE-SOURCE
68 ctxt = request.environ.get('ec2.context', None)79 ctxt = request.environ.get('ec2.context', None)
69 delta = utils.utcnow() - start80 delta = utils.utcnow() - start
70 seconds = delta.seconds81 seconds = delta.seconds
@@ -75,7 +86,7 @@
75 microseconds,86 microseconds,
76 request.remote_addr,87 request.remote_addr,
77 request.method,88 request.method,
78 request.path_info,89 "%s%s" % (request.script_name, request.path_info),
79 controller,90 controller,
80 action,91 action,
81 response.status_int,92 response.status_int,
@@ -319,13 +330,13 @@
319 except exception.InstanceNotFound as ex:330 except exception.InstanceNotFound as ex:
320 LOG.info(_('InstanceNotFound raised: %s'), unicode(ex),331 LOG.info(_('InstanceNotFound raised: %s'), unicode(ex),
321 context=context)332 context=context)
322 ec2_id = cloud.id_to_ec2_id(ex.instance_id)333 ec2_id = ec2utils.id_to_ec2_id(ex.instance_id)
323 message = _('Instance %s not found') % ec2_id334 message = _('Instance %s not found') % ec2_id
324 return self._error(req, context, type(ex).__name__, message)335 return self._error(req, context, type(ex).__name__, message)
325 except exception.VolumeNotFound as ex:336 except exception.VolumeNotFound as ex:
326 LOG.info(_('VolumeNotFound raised: %s'), unicode(ex),337 LOG.info(_('VolumeNotFound raised: %s'), unicode(ex),
327 context=context)338 context=context)
328 ec2_id = cloud.id_to_ec2_id(ex.volume_id, 'vol-%08x')339 ec2_id = ec2utils.id_to_ec2_id(ex.volume_id, 'vol-%08x')
329 message = _('Volume %s not found') % ec2_id340 message = _('Volume %s not found') % ec2_id
330 return self._error(req, context, type(ex).__name__, message)341 return self._error(req, context, type(ex).__name__, message)
331 except exception.NotFound as ex:342 except exception.NotFound as ex:
332343
=== modified file 'nova/api/ec2/admin.py'
--- nova/api/ec2/admin.py 2011-03-03 00:12:22 +0000
+++ nova/api/ec2/admin.py 2011-03-25 13:43:55 +0000
@@ -27,7 +27,12 @@
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 utils30<<<<<<< TREE
31from nova import utils
32=======
33from nova import utils
34from nova.api.ec2 import ec2utils
35>>>>>>> MERGE-SOURCE
31from nova.auth import manager36from nova.auth import manager
3237
3338
@@ -60,6 +65,7 @@
6065
61def host_dict(host, compute_service, instances, volume_service, volumes, now):66def host_dict(host, compute_service, instances, volume_service, volumes, now):
62 """Convert a host model object to a result dict"""67 """Convert a host model object to a result dict"""
68<<<<<<< TREE
63 rv = {'hostanme': host, 'instance_count': len(instances),69 rv = {'hostanme': host, 'instance_count': len(instances),
64 'volume_count': len(volumes)}70 'volume_count': len(volumes)}
65 if compute_service:71 if compute_service:
@@ -81,12 +87,36 @@
8187
82def instance_dict(inst):88def instance_dict(inst):
83 return {'name': inst['name'],89 return {'name': inst['name'],
90=======
91 rv = {'hostname': host, 'instance_count': len(instances),
92 'volume_count': len(volumes)}
93 if compute_service:
94 latest = compute_service['updated_at'] or compute_service['created_at']
95 delta = now - latest
96 if delta.seconds <= FLAGS.service_down_time:
97 rv['compute'] = 'up'
98 else:
99 rv['compute'] = 'down'
100 if volume_service:
101 latest = volume_service['updated_at'] or volume_service['created_at']
102 delta = now - latest
103 if delta.seconds <= FLAGS.service_down_time:
104 rv['volume'] = 'up'
105 else:
106 rv['volume'] = 'down'
107 return rv
108
109
110def instance_dict(inst):
111 return {'name': inst['name'],
112>>>>>>> MERGE-SOURCE
84 'memory_mb': inst['memory_mb'],113 'memory_mb': inst['memory_mb'],
85 'vcpus': inst['vcpus'],114 'vcpus': inst['vcpus'],
86 'disk_gb': inst['local_gb'],115 'disk_gb': inst['local_gb'],
87 'flavor_id': inst['flavorid']}116 'flavor_id': inst['flavorid']}
88117
89118
119<<<<<<< TREE
90def vpn_dict(project, vpn_instance):120def vpn_dict(project, vpn_instance):
91 rv = {'project_id': project.id,121 rv = {'project_id': project.id,
92 'public_ip': project.vpn_ip,122 'public_ip': project.vpn_ip,
@@ -106,6 +136,30 @@
106 return rv136 return rv
107137
108138
139=======
140def vpn_dict(project, vpn_instance):
141 rv = {'project_id': project.id,
142 'public_ip': project.vpn_ip,
143 'public_port': project.vpn_port}
144 if vpn_instance:
145 rv['instance_id'] = ec2utils.id_to_ec2_id(vpn_instance['id'])
146 rv['created_at'] = utils.isotime(vpn_instance['created_at'])
147 address = vpn_instance.get('fixed_ip', None)
148 if address:
149 rv['internal_ip'] = address['address']
150 if project.vpn_ip and project.vpn_port:
151 if utils.vpn_ping(project.vpn_ip, project.vpn_port):
152 rv['state'] = 'running'
153 else:
154 rv['state'] = 'down'
155 else:
156 rv['state'] = 'down - invalid project vpn config'
157 else:
158 rv['state'] = 'pending'
159 return rv
160
161
162>>>>>>> MERGE-SOURCE
109class AdminController(object):163class AdminController(object):
110 """164 """
111 API Controller for users, hosts, nodes, and workers.165 API Controller for users, hosts, nodes, and workers.
@@ -114,9 +168,16 @@
114 def __str__(self):168 def __str__(self):
115 return 'AdminController'169 return 'AdminController'
116170
171<<<<<<< TREE
117 def describe_instance_types(self, context, **_kwargs):172 def describe_instance_types(self, context, **_kwargs):
118 """Returns all active instance types data (vcpus, memory, etc.)"""173 """Returns all active instance types data (vcpus, memory, etc.)"""
119 return {'instanceTypeSet': [db.instance_type_get_all(context)]}174 return {'instanceTypeSet': [db.instance_type_get_all(context)]}
175=======
176 def describe_instance_types(self, context, **_kwargs):
177 """Returns all active instance types data (vcpus, memory, etc.)"""
178 return {'instanceTypeSet': [instance_dict(v) for v in
179 db.instance_type_get_all(context).values()]}
180>>>>>>> MERGE-SOURCE
120181
121 def describe_user(self, _context, name, **_kwargs):182 def describe_user(self, _context, name, **_kwargs):
122 """Returns user data, including access and secret keys."""183 """Returns user data, including access and secret keys."""
@@ -258,6 +319,7 @@
258 raise exception.ApiError(_('operation must be add or remove'))319 raise exception.ApiError(_('operation must be add or remove'))
259 return True320 return True
260321
322<<<<<<< TREE
261 def _vpn_for(self, context, project_id):323 def _vpn_for(self, context, project_id):
262 """Get the VPN instance for a project ID."""324 """Get the VPN instance for a project ID."""
263 for instance in db.instance_get_all_by_project(context, project_id):325 for instance in db.instance_get_all_by_project(context, project_id):
@@ -288,6 +350,38 @@
288 vpns.append(vpn_dict(project, instance))350 vpns.append(vpn_dict(project, instance))
289 return {'items': vpns}351 return {'items': vpns}
290352
353=======
354 def _vpn_for(self, context, project_id):
355 """Get the VPN instance for a project ID."""
356 for instance in db.instance_get_all_by_project(context, project_id):
357 if (instance['image_id'] == FLAGS.vpn_image_id
358 and not instance['state_description'] in
359 ['shutting_down', 'shutdown']):
360 return instance
361
362 def start_vpn(self, context, project):
363 instance = self._vpn_for(context, project)
364 if not instance:
365 # NOTE(vish) import delayed because of __init__.py
366 from nova.cloudpipe import pipelib
367 pipe = pipelib.CloudPipe()
368 try:
369 pipe.launch_vpn_instance(project)
370 except db.NoMoreNetworks:
371 raise exception.ApiError("Unable to claim IP for VPN instance"
372 ", ensure it isn't running, and try "
373 "again in a few minutes")
374 instance = self._vpn_for(context, project)
375 return {'instance_id': ec2utils.id_to_ec2_id(instance['id'])}
376
377 def describe_vpns(self, context):
378 vpns = []
379 for project in manager.AuthManager().get_projects():
380 instance = self._vpn_for(context, project.id)
381 vpns.append(vpn_dict(project, instance))
382 return {'items': vpns}
383
384>>>>>>> MERGE-SOURCE
291 # FIXME(vish): these host commands don't work yet, perhaps some of the385 # FIXME(vish): these host commands don't work yet, perhaps some of the
292 # required data can be retrieved from service objects?386 # required data can be retrieved from service objects?
293387
@@ -299,6 +393,7 @@
299 * Volume (up, down, None)393 * Volume (up, down, None)
300 * Volume Count394 * Volume Count
301 """395 """
396<<<<<<< TREE
302 services = db.service_get_all(context)397 services = db.service_get_all(context)
303 now = datetime.datetime.utcnow()398 now = datetime.datetime.utcnow()
304 hosts = []399 hosts = []
@@ -320,6 +415,29 @@
320 rv.append(host_dict(host, compute, instances, volume, volumes,415 rv.append(host_dict(host, compute, instances, volume, volumes,
321 now))416 now))
322 return {'hosts': rv}417 return {'hosts': rv}
418=======
419 services = db.service_get_all(context, False)
420 now = datetime.datetime.utcnow()
421 hosts = []
422 rv = []
423 for host in [service['host'] for service in services]:
424 if not host in hosts:
425 hosts.append(host)
426 for host in hosts:
427 compute = [s for s in services if s['host'] == host \
428 and s['binary'] == 'nova-compute']
429 if compute:
430 compute = compute[0]
431 instances = db.instance_get_all_by_host(context, host)
432 volume = [s for s in services if s['host'] == host \
433 and s['binary'] == 'nova-volume']
434 if volume:
435 volume = volume[0]
436 volumes = db.volume_get_all_by_host(context, host)
437 rv.append(host_dict(host, compute, instances, volume, volumes,
438 now))
439 return {'hosts': rv}
440>>>>>>> MERGE-SOURCE
323441
324 def describe_host(self, _context, name, **_kwargs):442 def describe_host(self, _context, name, **_kwargs):
325 """Returns status info for single node."""443 """Returns status info for single node."""
326444
=== modified file 'nova/api/ec2/cloud.py'
--- nova/api/ec2/cloud.py 2011-03-03 23:05:00 +0000
+++ nova/api/ec2/cloud.py 2011-03-25 13:43:55 +0000
@@ -145,10 +145,15 @@
145 availability_zone = self._get_availability_zone_by_host(ctxt, host)145 availability_zone = self._get_availability_zone_by_host(ctxt, host)
146 floating_ip = db.instance_get_floating_address(ctxt,146 floating_ip = db.instance_get_floating_address(ctxt,
147 instance_ref['id'])147 instance_ref['id'])
148<<<<<<< TREE
148 ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])149 ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])
149 image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'machine')150 image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'machine')
150 k_ec2_id = self._image_ec2_id(instance_ref['kernel_id'], 'kernel')151 k_ec2_id = self._image_ec2_id(instance_ref['kernel_id'], 'kernel')
151 r_ec2_id = self._image_ec2_id(instance_ref['ramdisk_id'], 'ramdisk')152 r_ec2_id = self._image_ec2_id(instance_ref['ramdisk_id'], 'ramdisk')
153=======
154 ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])
155 image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'machine')
156>>>>>>> MERGE-SOURCE
152 data = {157 data = {
153 'user-data': base64.b64decode(instance_ref['user_data']),158 'user-data': base64.b64decode(instance_ref['user_data']),
154 'meta-data': {159 'meta-data': {
@@ -167,8 +172,11 @@
167 'instance-type': instance_ref['instance_type'],172 'instance-type': instance_ref['instance_type'],
168 'local-hostname': hostname,173 'local-hostname': hostname,
169 'local-ipv4': address,174 'local-ipv4': address,
175<<<<<<< TREE
170 'kernel-id': k_ec2_id,176 'kernel-id': k_ec2_id,
171 'ramdisk-id': r_ec2_id,177 'ramdisk-id': r_ec2_id,
178=======
179>>>>>>> MERGE-SOURCE
172 'placement': {'availability-zone': availability_zone},180 'placement': {'availability-zone': availability_zone},
173 'public-hostname': hostname,181 'public-hostname': hostname,
174 'public-ipv4': floating_ip or '',182 'public-ipv4': floating_ip or '',
@@ -176,6 +184,13 @@
176 'reservation-id': instance_ref['reservation_id'],184 'reservation-id': instance_ref['reservation_id'],
177 'security-groups': '',185 'security-groups': '',
178 'mpi': mpi}}186 'mpi': mpi}}
187
188 for image_type in ['kernel', 'ramdisk']:
189 if '%s_id' % image_type in instance_ref:
190 ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type],
191 image_type)
192 data['meta-data']['%s-id' % image_type] = ec2_id
193
179 if False: # TODO(vish): store ancestor ids194 if False: # TODO(vish): store ancestor ids
180 data['ancestor-ami-ids'] = []195 data['ancestor-ami-ids'] = []
181 if False: # TODO(vish): store product codes196 if False: # TODO(vish): store product codes
@@ -192,9 +207,15 @@
192 return self._describe_availability_zones(context, **kwargs)207 return self._describe_availability_zones(context, **kwargs)
193208
194 def _describe_availability_zones(self, context, **kwargs):209 def _describe_availability_zones(self, context, **kwargs):
210<<<<<<< TREE
195 ctxt = context.elevated()211 ctxt = context.elevated()
196 enabled_services = db.service_get_all(ctxt)212 enabled_services = db.service_get_all(ctxt)
197 disabled_services = db.service_get_all(ctxt, True)213 disabled_services = db.service_get_all(ctxt, True)
214=======
215 ctxt = context.elevated()
216 enabled_services = db.service_get_all(ctxt, False)
217 disabled_services = db.service_get_all(ctxt, True)
218>>>>>>> MERGE-SOURCE
198 available_zones = []219 available_zones = []
199 for zone in [service.availability_zone for service220 for zone in [service.availability_zone for service
200 in enabled_services]:221 in enabled_services]:
@@ -218,7 +239,7 @@
218 rv = {'availabilityZoneInfo': [{'zoneName': 'nova',239 rv = {'availabilityZoneInfo': [{'zoneName': 'nova',
219 'zoneState': 'available'}]}240 'zoneState': 'available'}]}
220241
221 services = db.service_get_all(context)242 services = db.service_get_all(context, False)
222 now = datetime.datetime.utcnow()243 now = datetime.datetime.utcnow()
223 hosts = []244 hosts = []
224 for host in [service['host'] for service in services]:245 for host in [service['host'] for service in services]:
@@ -537,8 +558,13 @@
537 if volume_id:558 if volume_id:
538 volumes = []559 volumes = []
539 for ec2_id in volume_id:560 for ec2_id in volume_id:
561<<<<<<< TREE
540 internal_id = ec2utils.ec2_id_to_id(ec2_id)562 internal_id = ec2utils.ec2_id_to_id(ec2_id)
541 volume = self.volume_api.get(context, internal_id)563 volume = self.volume_api.get(context, internal_id)
564=======
565 internal_id = ec2utils.ec2_id_to_id(ec2_id)
566 volume = self.volume_api.get(context, volume_id=internal_id)
567>>>>>>> MERGE-SOURCE
542 volumes.append(volume)568 volumes.append(volume)
543 else:569 else:
544 volumes = self.volume_api.get_all(context)570 volumes = self.volume_api.get_all(context)
@@ -582,9 +608,11 @@
582608
583 def create_volume(self, context, size, **kwargs):609 def create_volume(self, context, size, **kwargs):
584 LOG.audit(_("Create volume of %s GB"), size, context=context)610 LOG.audit(_("Create volume of %s GB"), size, context=context)
585 volume = self.volume_api.create(context, size,611 volume = self.volume_api.create(
586 kwargs.get('display_name'),612 context,
587 kwargs.get('display_description'))613 size=size,
614 name=kwargs.get('display_name'),
615 description=kwargs.get('display_description'))
588 # TODO(vish): Instance should be None at db layer instead of616 # TODO(vish): Instance should be None at db layer instead of
589 # trying to lazy load, but for now we turn it into617 # trying to lazy load, but for now we turn it into
590 # a dict to avoid an error.618 # a dict to avoid an error.
@@ -603,7 +631,9 @@
603 if field in kwargs:631 if field in kwargs:
604 changes[field] = kwargs[field]632 changes[field] = kwargs[field]
605 if changes:633 if changes:
606 self.volume_api.update(context, volume_id, kwargs)634 self.volume_api.update(context,
635 volume_id=volume_id,
636 fields=changes)
607 return True637 return True
608638
609 def attach_volume(self, context, volume_id, instance_id, device, **kwargs):639 def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
@@ -616,7 +646,7 @@
616 instance_id=instance_id,646 instance_id=instance_id,
617 volume_id=volume_id,647 volume_id=volume_id,
618 device=device)648 device=device)
619 volume = self.volume_api.get(context, volume_id)649 volume = self.volume_api.get(context, volume_id=volume_id)
620 return {'attachTime': volume['attach_time'],650 return {'attachTime': volume['attach_time'],
621 'device': volume['mountpoint'],651 'device': volume['mountpoint'],
622 'instanceId': ec2utils.id_to_ec2_id(instance_id),652 'instanceId': ec2utils.id_to_ec2_id(instance_id),
@@ -627,7 +657,7 @@
627 def detach_volume(self, context, volume_id, **kwargs):657 def detach_volume(self, context, volume_id, **kwargs):
628 volume_id = ec2utils.ec2_id_to_id(volume_id)658 volume_id = ec2utils.ec2_id_to_id(volume_id)
629 LOG.audit(_("Detach volume %s"), volume_id, context=context)659 LOG.audit(_("Detach volume %s"), volume_id, context=context)
630 volume = self.volume_api.get(context, volume_id)660 volume = self.volume_api.get(context, volume_id=volume_id)
631 instance = self.compute_api.detach_volume(context, volume_id=volume_id)661 instance = self.compute_api.detach_volume(context, volume_id=volume_id)
632 return {'attachTime': volume['attach_time'],662 return {'attachTime': volume['attach_time'],
633 'device': volume['mountpoint'],663 'device': volume['mountpoint'],
@@ -765,7 +795,7 @@
765795
766 def release_address(self, context, public_ip, **kwargs):796 def release_address(self, context, public_ip, **kwargs):
767 LOG.audit(_("Release address %s"), public_ip, context=context)797 LOG.audit(_("Release address %s"), public_ip, context=context)
768 self.network_api.release_floating_ip(context, public_ip)798 self.network_api.release_floating_ip(context, address=public_ip)
769 return {'releaseResponse': ["Address released."]}799 return {'releaseResponse': ["Address released."]}
770800
771 def associate_address(self, context, instance_id, public_ip, **kwargs):801 def associate_address(self, context, instance_id, public_ip, **kwargs):
@@ -779,7 +809,7 @@
779809
780 def disassociate_address(self, context, public_ip, **kwargs):810 def disassociate_address(self, context, public_ip, **kwargs):
781 LOG.audit(_("Disassociate address %s"), public_ip, context=context)811 LOG.audit(_("Disassociate address %s"), public_ip, context=context)
782 self.network_api.disassociate_floating_ip(context, public_ip)812 self.network_api.disassociate_floating_ip(context, address=public_ip)
783 return {'disassociateResponse': ["Address disassociated."]}813 return {'disassociateResponse': ["Address disassociated."]}
784814
785 def run_instances(self, context, **kwargs):815 def run_instances(self, context, **kwargs):
@@ -949,6 +979,7 @@
949 if not operation_type in ['add', 'remove']:979 if not operation_type in ['add', 'remove']:
950 raise exception.ApiError(_('operation_type must be add or remove'))980 raise exception.ApiError(_('operation_type must be add or remove'))
951 LOG.audit(_("Updating image %s publicity"), image_id, context=context)981 LOG.audit(_("Updating image %s publicity"), image_id, context=context)
982<<<<<<< TREE
952983
953 try:984 try:
954 image = self._get_image(context, image_id)985 image = self._get_image(context, image_id)
@@ -959,6 +990,18 @@
959 raise Exception(image)990 raise Exception(image)
960 image['properties']['is_public'] = (operation_type == 'add')991 image['properties']['is_public'] = (operation_type == 'add')
961 return self.image_service.update(context, internal_id, image)992 return self.image_service.update(context, internal_id, image)
993=======
994
995 try:
996 image = self._get_image(context, image_id)
997 except exception.NotFound:
998 raise exception.NotFound(_('Image %s not found') % image_id)
999 internal_id = image['id']
1000 del(image['id'])
1001
1002 image['properties']['is_public'] = (operation_type == 'add')
1003 return self.image_service.update(context, internal_id, image)
1004>>>>>>> MERGE-SOURCE
9621005
963 def update_image(self, context, image_id, **kwargs):1006 def update_image(self, context, image_id, **kwargs):
964 internal_id = ec2utils.ec2_id_to_id(image_id)1007 internal_id = ec2utils.ec2_id_to_id(image_id)
9651008
=== modified file 'nova/api/openstack/__init__.py'
--- nova/api/openstack/__init__.py 2011-03-11 19:49:32 +0000
+++ nova/api/openstack/__init__.py 2011-03-25 13:43:55 +0000
@@ -33,7 +33,9 @@
33from nova.api.openstack import consoles33from nova.api.openstack import consoles
34from nova.api.openstack import flavors34from nova.api.openstack import flavors
35from nova.api.openstack import images35from nova.api.openstack import images
36from nova.api.openstack import limits
36from nova.api.openstack import servers37from nova.api.openstack import servers
38from nova.api.openstack import server_metadata
37from nova.api.openstack import shared_ip_groups39from nova.api.openstack import shared_ip_groups
38from nova.api.openstack import users40from nova.api.openstack import users
39from nova.api.openstack import zones41from nova.api.openstack import zones
@@ -70,10 +72,15 @@
70 """Simple paste factory, :class:`nova.wsgi.Router` doesn't have one"""72 """Simple paste factory, :class:`nova.wsgi.Router` doesn't have one"""
71 return cls()73 return cls()
7274
73 def __init__(self):75 def __init__(self, ext_mgr=None):
76 self.server_members = {}
74 mapper = routes.Mapper()77 mapper = routes.Mapper()
78 self._setup_routes(mapper)
79 super(APIRouter, self).__init__(mapper)
7580
76 server_members = {'action': 'POST'}81 def _setup_routes(self, mapper):
82 server_members = self.server_members
83 server_members['action'] = 'POST'
77 if FLAGS.allow_admin_api:84 if FLAGS.allow_admin_api:
78 LOG.debug(_("Including admin operations in API."))85 LOG.debug(_("Including admin operations in API."))
7986
@@ -83,6 +90,7 @@
83 server_members['actions'] = 'GET'90 server_members['actions'] = 'GET'
84 server_members['suspend'] = 'POST'91 server_members['suspend'] = 'POST'
85 server_members['resume'] = 'POST'92 server_members['resume'] = 'POST'
93<<<<<<< TREE
86 server_members['rescue'] = 'POST'94 server_members['rescue'] = 'POST'
87 server_members['unrescue'] = 'POST'95 server_members['unrescue'] = 'POST'
88 server_members['reset_network'] = 'POST'96 server_members['reset_network'] = 'POST'
@@ -101,6 +109,22 @@
101 mapper.resource("server", "servers", controller=servers.Controller(),109 mapper.resource("server", "servers", controller=servers.Controller(),
102 collection={'detail': 'GET'},110 collection={'detail': 'GET'},
103 member=server_members)111 member=server_members)
112=======
113 server_members['rescue'] = 'POST'
114 server_members['unrescue'] = 'POST'
115 server_members['reset_network'] = 'POST'
116 server_members['inject_network_info'] = 'POST'
117
118 mapper.resource("zone", "zones", controller=zones.Controller(),
119 collection={'detail': 'GET', 'info': 'GET'}),
120
121 mapper.resource("user", "users", controller=users.Controller(),
122 collection={'detail': 'GET'})
123
124 mapper.resource("account", "accounts",
125 controller=accounts.Controller(),
126 collection={'detail': 'GET'})
127>>>>>>> MERGE-SOURCE
104128
105 mapper.resource("backup_schedule", "backup_schedule",129 mapper.resource("backup_schedule", "backup_schedule",
106 controller=backup_schedules.Controller(),130 controller=backup_schedules.Controller(),
@@ -114,13 +138,42 @@
114138
115 mapper.resource("image", "images", controller=images.Controller(),139 mapper.resource("image", "images", controller=images.Controller(),
116 collection={'detail': 'GET'})140 collection={'detail': 'GET'})
141
117 mapper.resource("flavor", "flavors", controller=flavors.Controller(),142 mapper.resource("flavor", "flavors", controller=flavors.Controller(),
118 collection={'detail': 'GET'})143 collection={'detail': 'GET'})
144
119 mapper.resource("shared_ip_group", "shared_ip_groups",145 mapper.resource("shared_ip_group", "shared_ip_groups",
120 collection={'detail': 'GET'},146 collection={'detail': 'GET'},
121 controller=shared_ip_groups.Controller())147 controller=shared_ip_groups.Controller())
122148
123 super(APIRouter, self).__init__(mapper)149 _limits = limits.LimitsController()
150 mapper.resource("limit", "limits", controller=_limits)
151
152
153class APIRouterV10(APIRouter):
154 """Define routes specific to OpenStack API V1.0."""
155
156 def _setup_routes(self, mapper):
157 super(APIRouterV10, self)._setup_routes(mapper)
158 mapper.resource("server", "servers",
159 controller=servers.ControllerV10(),
160 collection={'detail': 'GET'},
161 member=self.server_members)
162
163
164class APIRouterV11(APIRouter):
165 """Define routes specific to OpenStack API V1.1."""
166
167 def _setup_routes(self, mapper):
168 super(APIRouterV11, self)._setup_routes(mapper)
169 mapper.resource("server", "servers",
170 controller=servers.ControllerV11(),
171 collection={'detail': 'GET'},
172 member=self.server_members)
173 mapper.resource("server_meta", "meta",
174 controller=server_metadata.Controller(),
175 parent_resource=dict(member_name='server',
176 collection_name='servers'))
124177
125178
126class Versions(wsgi.Application):179class Versions(wsgi.Application):
@@ -128,8 +181,11 @@
128 def __call__(self, req):181 def __call__(self, req):
129 """Respond to a request for all OpenStack API versions."""182 """Respond to a request for all OpenStack API versions."""
130 response = {183 response = {
131 "versions": [184 "versions": [
132 dict(status="CURRENT", id="v1.0")]}185 dict(status="DEPRECATED", id="v1.0"),
186 dict(status="CURRENT", id="v1.1"),
187 ],
188 }
133 metadata = {189 metadata = {
134 "application/xml": {190 "application/xml": {
135 "attributes": dict(version=["status", "id"])}}191 "attributes": dict(version=["status", "id"])}}
136192
=== modified file 'nova/api/openstack/accounts.py'
--- nova/api/openstack/accounts.py 2011-03-11 19:49:32 +0000
+++ nova/api/openstack/accounts.py 2011-03-25 13:43:55 +0000
@@ -1,85 +1,174 @@
1# Copyright 2011 OpenStack LLC.1<<<<<<< TREE
2# All Rights Reserved.2# Copyright 2011 OpenStack LLC.
3#3# All Rights Reserved.
4# Licensed under the Apache License, Version 2.0 (the "License"); you may4#
5# not use this file except in compliance with the License. You may obtain5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# a copy of the License at6# not use this file except in compliance with the License. You may obtain
7#7# a copy of the License at
8# http://www.apache.org/licenses/LICENSE-2.08#
9#9# http://www.apache.org/licenses/LICENSE-2.0
10# Unless required by applicable law or agreed to in writing, software10#
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT11# Unless required by applicable law or agreed to in writing, software
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# License for the specific language governing permissions and limitations13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# under the License.14# License for the specific language governing permissions and limitations
1515# under the License.
16import common16
1717import common
18from nova import exception18
19from nova import flags19from nova import exception
20from nova import log as logging20from nova import flags
21from nova import wsgi21from nova import log as logging
2222from nova import wsgi
23from nova.auth import manager23
24from nova.api.openstack import faults24from nova.auth import manager
2525from nova.api.openstack import faults
26FLAGS = flags.FLAGS26
27LOG = logging.getLogger('nova.api.openstack')27FLAGS = flags.FLAGS
2828LOG = logging.getLogger('nova.api.openstack')
2929
30def _translate_keys(account):30
31 return dict(id=account.id,31def _translate_keys(account):
32 name=account.name,32 return dict(id=account.id,
33 description=account.description,33 name=account.name,
34 manager=account.project_manager_id)34 description=account.description,
3535 manager=account.project_manager_id)
3636
37class Controller(wsgi.Controller):37
3838class Controller(wsgi.Controller):
39 _serialization_metadata = {39
40 'application/xml': {40 _serialization_metadata = {
41 "attributes": {41 'application/xml': {
42 "account": ["id", "name", "description", "manager"]}}}42 "attributes": {
4343 "account": ["id", "name", "description", "manager"]}}}
44 def __init__(self):44
45 self.manager = manager.AuthManager()45 def __init__(self):
4646 self.manager = manager.AuthManager()
47 def _check_admin(self, context):47
48 """We cannot depend on the db layer to check for admin access48 def _check_admin(self, context):
49 for the auth manager, so we do it here"""49 """We cannot depend on the db layer to check for admin access
50 if not context.is_admin:50 for the auth manager, so we do it here"""
51 raise exception.NotAuthorized(_("Not admin user."))51 if not context.is_admin:
5252 raise exception.NotAuthorized(_("Not admin user."))
53 def index(self, req):53
54 raise faults.Fault(exc.HTTPNotImplemented())54 def index(self, req):
5555 raise faults.Fault(exc.HTTPNotImplemented())
56 def detail(self, req):56
57 raise faults.Fault(exc.HTTPNotImplemented())57 def detail(self, req):
5858 raise faults.Fault(exc.HTTPNotImplemented())
59 def show(self, req, id):59
60 """Return data about the given account id"""60 def show(self, req, id):
61 account = self.manager.get_project(id)61 """Return data about the given account id"""
62 return dict(account=_translate_keys(account))62 account = self.manager.get_project(id)
6363 return dict(account=_translate_keys(account))
64 def delete(self, req, id):64
65 self._check_admin(req.environ['nova.context'])65 def delete(self, req, id):
66 self.manager.delete_project(id)66 self._check_admin(req.environ['nova.context'])
67 return {}67 self.manager.delete_project(id)
6868 return {}
69 def create(self, req):69
70 """We use update with create-or-update semantics70 def create(self, req):
71 because the id comes from an external source"""71 """We use update with create-or-update semantics
72 raise faults.Fault(exc.HTTPNotImplemented())72 because the id comes from an external source"""
7373 raise faults.Fault(exc.HTTPNotImplemented())
74 def update(self, req, id):74
75 """This is really create or update."""75 def update(self, req, id):
76 self._check_admin(req.environ['nova.context'])76 """This is really create or update."""
77 env = self._deserialize(req.body, req.get_content_type())77 self._check_admin(req.environ['nova.context'])
78 description = env['account'].get('description')78 env = self._deserialize(req.body, req.get_content_type())
79 manager = env['account'].get('manager')79 description = env['account'].get('description')
80 try:80 manager = env['account'].get('manager')
81 account = self.manager.get_project(id)81 try:
82 self.manager.modify_project(id, manager, description)82 account = self.manager.get_project(id)
83 except exception.NotFound:83 self.manager.modify_project(id, manager, description)
84 account = self.manager.create_project(id, manager, description)84 except exception.NotFound:
85 return dict(account=_translate_keys(account))85 account = self.manager.create_project(id, manager, description)
86 return dict(account=_translate_keys(account))
87=======
88# Copyright 2011 OpenStack LLC.
89# All Rights Reserved.
90#
91# Licensed under the Apache License, Version 2.0 (the "License"); you may
92# not use this file except in compliance with the License. You may obtain
93# a copy of the License at
94#
95# http://www.apache.org/licenses/LICENSE-2.0
96#
97# Unless required by applicable law or agreed to in writing, software
98# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
99# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
100# License for the specific language governing permissions and limitations
101# under the License.
102
103import common
104import webob.exc
105
106from nova import exception
107from nova import flags
108from nova import log as logging
109from nova import wsgi
110
111from nova.auth import manager
112from nova.api.openstack import faults
113
114FLAGS = flags.FLAGS
115LOG = logging.getLogger('nova.api.openstack')
116
117
118def _translate_keys(account):
119 return dict(id=account.id,
120 name=account.name,
121 description=account.description,
122 manager=account.project_manager_id)
123
124
125class Controller(wsgi.Controller):
126
127 _serialization_metadata = {
128 'application/xml': {
129 "attributes": {
130 "account": ["id", "name", "description", "manager"]}}}
131
132 def __init__(self):
133 self.manager = manager.AuthManager()
134
135 def _check_admin(self, context):
136 """We cannot depend on the db layer to check for admin access
137 for the auth manager, so we do it here"""
138 if not context.is_admin:
139 raise exception.NotAuthorized(_("Not admin user."))
140
141 def index(self, req):
142 raise faults.Fault(webob.exc.HTTPNotImplemented())
143
144 def detail(self, req):
145 raise faults.Fault(webob.exc.HTTPNotImplemented())
146
147 def show(self, req, id):
148 """Return data about the given account id"""
149 account = self.manager.get_project(id)
150 return dict(account=_translate_keys(account))
151
152 def delete(self, req, id):
153 self._check_admin(req.environ['nova.context'])
154 self.manager.delete_project(id)
155 return {}
156
157 def create(self, req):
158 """We use update with create-or-update semantics
159 because the id comes from an external source"""
160 raise faults.Fault(webob.exc.HTTPNotImplemented())
161
162 def update(self, req, id):
163 """This is really create or update."""
164 self._check_admin(req.environ['nova.context'])
165 env = self._deserialize(req.body, req.get_content_type())
166 description = env['account'].get('description')
167 manager = env['account'].get('manager')
168 try:
169 account = self.manager.get_project(id)
170 self.manager.modify_project(id, manager, description)
171 except exception.NotFound:
172 account = self.manager.create_project(id, manager, description)
173 return dict(account=_translate_keys(account))
174>>>>>>> MERGE-SOURCE
86175
=== modified file 'nova/api/openstack/auth.py'
--- nova/api/openstack/auth.py 2011-03-11 19:49:32 +0000
+++ nova/api/openstack/auth.py 2011-03-25 13:43:55 +0000
@@ -135,8 +135,17 @@
135 req - wsgi.Request object135 req - wsgi.Request object
136 """136 """
137 ctxt = context.get_admin_context()137 ctxt = context.get_admin_context()
138<<<<<<< TREE
138 user = self.auth.get_user_from_access_key(key)139 user = self.auth.get_user_from_access_key(key)
139140
141=======
142
143 try:
144 user = self.auth.get_user_from_access_key(key)
145 except exception.NotFound:
146 user = None
147
148>>>>>>> MERGE-SOURCE
140 if user and user.name == username:149 if user and user.name == username:
141 token_hash = hashlib.sha1('%s%s%f' % (username, key,150 token_hash = hashlib.sha1('%s%s%f' % (username, key,
142 time.time())).hexdigest()151 time.time())).hexdigest()
143152
=== modified file 'nova/api/openstack/common.py'
--- nova/api/openstack/common.py 2011-03-09 22:10:24 +0000
+++ nova/api/openstack/common.py 2011-03-25 13:43:55 +0000
@@ -15,9 +15,17 @@
15# License for the specific language governing permissions and limitations15# License for the specific language governing permissions and limitations
16# under the License.16# under the License.
1717
18<<<<<<< TREE
18import webob.exc19import webob.exc
1920
21=======
22from urlparse import urlparse
23
24import webob
25
26>>>>>>> MERGE-SOURCE
20from nova import exception27from nova import exception
28<<<<<<< TREE
2129
2230
23def limited(items, request, max_limit=1000):31def limited(items, request, max_limit=1000):
@@ -50,10 +58,77 @@
50 raise webob.exc.HTTPBadRequest(_('offset param must be positive'))58 raise webob.exc.HTTPBadRequest(_('offset param must be positive'))
5159
52 limit = min(max_limit, limit or max_limit)60 limit = min(max_limit, limit or max_limit)
61=======
62from nova import flags
63
64FLAGS = flags.FLAGS
65
66
67def limited(items, request, max_limit=FLAGS.osapi_max_limit):
68 """
69 Return a slice of items according to requested offset and limit.
70
71 @param items: A sliceable entity
72 @param request: `wsgi.Request` possibly containing 'offset' and 'limit'
73 GET variables. 'offset' is where to start in the list,
74 and 'limit' is the maximum number of items to return. If
75 'limit' is not specified, 0, or > max_limit, we default
76 to max_limit. Negative values for either offset or limit
77 will cause exc.HTTPBadRequest() exceptions to be raised.
78 @kwarg max_limit: The maximum number of items to return from 'items'
79 """
80 try:
81 offset = int(request.GET.get('offset', 0))
82 except ValueError:
83 raise webob.exc.HTTPBadRequest(_('offset param must be an integer'))
84
85 try:
86 limit = int(request.GET.get('limit', max_limit))
87 except ValueError:
88 raise webob.exc.HTTPBadRequest(_('limit param must be an integer'))
89
90 if limit < 0:
91 raise webob.exc.HTTPBadRequest(_('limit param must be positive'))
92
93 if offset < 0:
94 raise webob.exc.HTTPBadRequest(_('offset param must be positive'))
95
96 limit = min(max_limit, limit or max_limit)
97>>>>>>> MERGE-SOURCE
53 range_end = offset + limit98 range_end = offset + limit
54 return items[offset:range_end]99 return items[offset:range_end]
55100
56101
102def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit):
103 """Return a slice of items according to the requested marker and limit."""
104
105 try:
106 marker = int(request.GET.get('marker', 0))
107 except ValueError:
108 raise webob.exc.HTTPBadRequest(_('marker param must be an integer'))
109
110 try:
111 limit = int(request.GET.get('limit', max_limit))
112 except ValueError:
113 raise webob.exc.HTTPBadRequest(_('limit param must be an integer'))
114
115 if limit < 0:
116 raise webob.exc.HTTPBadRequest(_('limit param must be positive'))
117
118 limit = min(max_limit, limit)
119 start_index = 0
120 if marker:
121 start_index = -1
122 for i, item in enumerate(items):
123 if item['id'] == marker:
124 start_index = i + 1
125 break
126 if start_index < 0:
127 raise webob.exc.HTTPBadRequest(_('marker [%s] not found' % marker))
128 range_end = start_index + limit
129 return items[start_index:range_end]
130
131
57def get_image_id_from_image_hash(image_service, context, image_hash):132def get_image_id_from_image_hash(image_service, context, image_hash):
58 """Given an Image ID Hash, return an objectstore Image ID.133 """Given an Image ID Hash, return an objectstore Image ID.
59134
@@ -74,3 +149,16 @@
74 if abs(hash(image_id)) == int(image_hash):149 if abs(hash(image_id)) == int(image_hash):
75 return image_id150 return image_id
76 raise exception.NotFound(image_hash)151 raise exception.NotFound(image_hash)
152
153
154def get_id_from_href(href):
155 """Return the id portion of a url as an int.
156
157 Given: http://www.foo.com/bar/123?q=4
158 Returns: 123
159
160 """
161 try:
162 return int(urlparse(href).path.split('/')[-1])
163 except:
164 raise webob.exc.HTTPBadRequest(_('could not parse id from href'))
77165
=== added file 'nova/api/openstack/extensions.py'
--- nova/api/openstack/extensions.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/extensions.py 2011-03-25 13:43:55 +0000
@@ -0,0 +1,369 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2011 OpenStack LLC.
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
18import imp
19import os
20import sys
21import routes
22import webob.dec
23import webob.exc
24
25from nova import flags
26from nova import log as logging
27from nova import wsgi
28from nova.api.openstack import faults
29
30
31LOG = logging.getLogger('extensions')
32
33
34FLAGS = flags.FLAGS
35
36
37class ActionExtensionController(wsgi.Controller):
38
39 def __init__(self, application):
40
41 self.application = application
42 self.action_handlers = {}
43
44 def add_action(self, action_name, handler):
45 self.action_handlers[action_name] = handler
46
47 def action(self, req, id):
48
49 input_dict = self._deserialize(req.body, req.get_content_type())
50 for action_name, handler in self.action_handlers.iteritems():
51 if action_name in input_dict:
52 return handler(input_dict, req, id)
53 # no action handler found (bump to downstream application)
54 res = self.application
55 return res
56
57
58class ResponseExtensionController(wsgi.Controller):
59
60 def __init__(self, application):
61 self.application = application
62 self.handlers = []
63
64 def add_handler(self, handler):
65 self.handlers.append(handler)
66
67 def process(self, req, *args, **kwargs):
68 res = req.get_response(self.application)
69 content_type = req.best_match_content_type()
70 # currently response handlers are un-ordered
71 for handler in self.handlers:
72 res = handler(res)
73 try:
74 body = res.body
75 headers = res.headers
76 except AttributeError:
77 body = self._serialize(res, content_type)
78 headers = {"Content-Type": content_type}
79 res = webob.Response()
80 res.body = body
81 res.headers = headers
82 return res
83
84
85class ExtensionController(wsgi.Controller):
86
87 def __init__(self, extension_manager):
88 self.extension_manager = extension_manager
89
90 def _translate(self, ext):
91 ext_data = {}
92 ext_data['name'] = ext.get_name()
93 ext_data['alias'] = ext.get_alias()
94 ext_data['description'] = ext.get_description()
95 ext_data['namespace'] = ext.get_namespace()
96 ext_data['updated'] = ext.get_updated()
97 ext_data['links'] = [] # TODO: implement extension links
98 return ext_data
99
100 def index(self, req):
101 extensions = []
102 for alias, ext in self.extension_manager.extensions.iteritems():
103 extensions.append(self._translate(ext))
104 return dict(extensions=extensions)
105
106 def show(self, req, id):
107 # NOTE: the extensions alias is used as the 'id' for show
108 ext = self.extension_manager.extensions[id]
109 return self._translate(ext)
110
111 def delete(self, req, id):
112 raise faults.Fault(exc.HTTPNotFound())
113
114 def create(self, req):
115 raise faults.Fault(exc.HTTPNotFound())
116
117 def delete(self, req, id):
118 raise faults.Fault(exc.HTTPNotFound())
119
120
121class ExtensionMiddleware(wsgi.Middleware):
122 """
123 Extensions middleware that intercepts configured routes for extensions.
124 """
125 @classmethod
126 def factory(cls, global_config, **local_config):
127 """ paste factory """
128 def _factory(app):
129 return cls(app, **local_config)
130 return _factory
131
132 def _action_ext_controllers(self, application, ext_mgr, mapper):
133 """
134 Return a dict of ActionExtensionController objects by collection
135 """
136 action_controllers = {}
137 for action in ext_mgr.get_actions():
138 if not action.collection in action_controllers.keys():
139 controller = ActionExtensionController(application)
140 mapper.connect("/%s/:(id)/action.:(format)" %
141 action.collection,
142 action='action',
143 controller=controller,
144 conditions=dict(method=['POST']))
145 mapper.connect("/%s/:(id)/action" % action.collection,
146 action='action',
147 controller=controller,
148 conditions=dict(method=['POST']))
149 action_controllers[action.collection] = controller
150
151 return action_controllers
152
153 def _response_ext_controllers(self, application, ext_mgr, mapper):
154 """
155 Return a dict of ResponseExtensionController objects by collection
156 """
157 response_ext_controllers = {}
158 for resp_ext in ext_mgr.get_response_extensions():
159 if not resp_ext.key in response_ext_controllers.keys():
160 controller = ResponseExtensionController(application)
161 mapper.connect(resp_ext.url_route + '.:(format)',
162 action='process',
163 controller=controller,
164 conditions=resp_ext.conditions)
165
166 mapper.connect(resp_ext.url_route,
167 action='process',
168 controller=controller,
169 conditions=resp_ext.conditions)
170 response_ext_controllers[resp_ext.key] = controller
171
172 return response_ext_controllers
173
174 def __init__(self, application, ext_mgr=None):
175
176 if ext_mgr is None:
177 ext_mgr = ExtensionManager(FLAGS.osapi_extensions_path)
178 self.ext_mgr = ext_mgr
179
180 mapper = routes.Mapper()
181
182 # extended resources
183 for resource in ext_mgr.get_resources():
184 LOG.debug(_('Extended resource: %s'),
185 resource.collection)
186 mapper.resource(resource.collection, resource.collection,
187 controller=resource.controller,
188 collection=resource.collection_actions,
189 member=resource.member_actions,
190 parent_resource=resource.parent)
191
192 # extended actions
193 action_controllers = self._action_ext_controllers(application, ext_mgr,
194 mapper)
195 for action in ext_mgr.get_actions():
196 LOG.debug(_('Extended action: %s'), action.action_name)
197 controller = action_controllers[action.collection]
198 controller.add_action(action.action_name, action.handler)
199
200 # extended responses
201 resp_controllers = self._response_ext_controllers(application, ext_mgr,
202 mapper)
203 for response_ext in ext_mgr.get_response_extensions():
204 LOG.debug(_('Extended response: %s'), response_ext.key)
205 controller = resp_controllers[response_ext.key]
206 controller.add_handler(response_ext.handler)
207
208 self._router = routes.middleware.RoutesMiddleware(self._dispatch,
209 mapper)
210
211 super(ExtensionMiddleware, self).__init__(application)
212
213 @webob.dec.wsgify(RequestClass=wsgi.Request)
214 def __call__(self, req):
215 """
216 Route the incoming request with router.
217 """
218 req.environ['extended.app'] = self.application
219 return self._router
220
221 @staticmethod
222 @webob.dec.wsgify(RequestClass=wsgi.Request)
223 def _dispatch(req):
224 """
225 Returns the routed WSGI app's response or defers to the extended
226 application.
227 """
228 match = req.environ['wsgiorg.routing_args'][1]
229 if not match:
230 return req.environ['extended.app']
231 app = match['controller']
232 return app
233
234
235class ExtensionManager(object):
236 """
237 Load extensions from the configured extension path.
238 See nova/tests/api/openstack/extensions/foxinsocks.py for an example
239 extension implementation.
240 """
241
242 def __init__(self, path):
243 LOG.audit(_('Initializing extension manager.'))
244
245 self.path = path
246 self.extensions = {}
247 self._load_extensions()
248
249 def get_resources(self):
250 """
251 returns a list of ResourceExtension objects
252 """
253 resources = []
254 resources.append(ResourceExtension('extensions',
255 ExtensionController(self)))
256 for alias, ext in self.extensions.iteritems():
257 try:
258 resources.extend(ext.get_resources())
259 except AttributeError:
260 # NOTE: Extension aren't required to have resource extensions
261 pass
262 return resources
263
264 def get_actions(self):
265 """
266 returns a list of ActionExtension objects
267 """
268 actions = []
269 for alias, ext in self.extensions.iteritems():
270 try:
271 actions.extend(ext.get_actions())
272 except AttributeError:
273 # NOTE: Extension aren't required to have action extensions
274 pass
275 return actions
276
277 def get_response_extensions(self):
278 """
279 returns a list of ResponseExtension objects
280 """
281 response_exts = []
282 for alias, ext in self.extensions.iteritems():
283 try:
284 response_exts.extend(ext.get_response_extensions())
285 except AttributeError:
286 # NOTE: Extension aren't required to have response extensions
287 pass
288 return response_exts
289
290 def _check_extension(self, extension):
291 """
292 Checks for required methods in extension objects.
293 """
294 try:
295 LOG.debug(_('Ext name: %s'), extension.get_name())
296 LOG.debug(_('Ext alias: %s'), extension.get_alias())
297 LOG.debug(_('Ext description: %s'), extension.get_description())
298 LOG.debug(_('Ext namespace: %s'), extension.get_namespace())
299 LOG.debug(_('Ext updated: %s'), extension.get_updated())
300 except AttributeError as ex:
301 LOG.exception(_("Exception loading extension: %s"), unicode(ex))
302
303 def _load_extensions(self):
304 """
305 Load extensions from the configured path. The extension name is
306 constructed from the module_name. If your extension module was named
307 widgets.py the extension class within that module should be
308 'Widgets'.
309
310 See nova/tests/api/openstack/extensions/foxinsocks.py for an example
311 extension implementation.
312 """
313 if not os.path.exists(self.path):
314 return
315
316 for f in os.listdir(self.path):
317 LOG.audit(_('Loading extension file: %s'), f)
318 mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
319 ext_path = os.path.join(self.path, f)
320 if file_ext.lower() == '.py':
321 mod = imp.load_source(mod_name, ext_path)
322 ext_name = mod_name[0].upper() + mod_name[1:]
323 try:
324 new_ext = getattr(mod, ext_name)()
325 self._check_extension(new_ext)
326 self.extensions[new_ext.get_alias()] = new_ext
327 except AttributeError as ex:
328 LOG.exception(_("Exception loading extension: %s"),
329 unicode(ex))
330
331
332class ResponseExtension(object):
333 """
334 ResponseExtension objects can be used to add data to responses from
335 core nova OpenStack API controllers.
336 """
337
338 def __init__(self, method, url_route, handler):
339 self.url_route = url_route
340 self.handler = handler
341 self.conditions = dict(method=[method])
342 self.key = "%s-%s" % (method, url_route)
343
344
345class ActionExtension(object):
346 """
347 ActionExtension objects can be used to add custom actions to core nova
348 nova OpenStack API controllers.
349 """
350
351 def __init__(self, collection, action_name, handler):
352 self.collection = collection
353 self.action_name = action_name
354 self.handler = handler
355
356
357class ResourceExtension(object):
358 """
359 ResourceExtension objects can be used to add top level resources
360 to the OpenStack API in nova.
361 """
362
363 def __init__(self, collection, controller, parent=None,
364 collection_actions={}, member_actions={}):
365 self.collection = collection
366 self.controller = controller
367 self.parent = parent
368 self.collection_actions = collection_actions
369 self.member_actions = member_actions
0370
=== modified file 'nova/api/openstack/faults.py'
--- nova/api/openstack/faults.py 2011-03-09 20:08:11 +0000
+++ nova/api/openstack/faults.py 2011-03-25 13:43:55 +0000
@@ -57,7 +57,52 @@
57 fault_data[fault_name]['retryAfter'] = retry57 fault_data[fault_name]['retryAfter'] = retry
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 = {'application/xml': {'attributes': {fault_name: 'code'}}}
60 serializer = wsgi.Serializer(metadata)60<<<<<<< TREE
61 content_type = req.best_match_content_type()61 serializer = wsgi.Serializer(metadata)
62 self.wrapped_exc.body = serializer.serialize(fault_data, content_type)62 content_type = req.best_match_content_type()
63 self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
64=======
65 serializer = wsgi.Serializer(metadata)
66 content_type = req.best_match_content_type()
67 self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
68 return self.wrapped_exc
69
70
71class OverLimitFault(webob.exc.HTTPException):
72 """
73 Rate-limited request response.
74 """
75
76 _serialization_metadata = {
77 "application/xml": {
78 "attributes": {
79 "overLimitFault": "code",
80 },
81 },
82 }
83
84 def __init__(self, message, details, retry_time):
85 """
86 Initialize new `OverLimitFault` with relevant information.
87 """
88 self.wrapped_exc = webob.exc.HTTPForbidden()
89 self.content = {
90 "overLimitFault": {
91 "code": self.wrapped_exc.status_int,
92 "message": message,
93 "details": details,
94 },
95 }
96
97 @webob.dec.wsgify(RequestClass=wsgi.Request)
98 def __call__(self, request):
99 """
100 Return the wrapped exception with a serialized body conforming to our
101 error format.
102 """
103 serializer = wsgi.Serializer(self._serialization_metadata)
104 content_type = request.best_match_content_type()
105 content = serializer.serialize(self.content, content_type)
106 self.wrapped_exc.body = content
107>>>>>>> MERGE-SOURCE
63 return self.wrapped_exc108 return self.wrapped_exc
64109
=== modified file 'nova/api/openstack/flavors.py'
--- nova/api/openstack/flavors.py 2011-03-09 18:10:45 +0000
+++ nova/api/openstack/flavors.py 2011-03-25 13:43:55 +0000
@@ -22,6 +22,7 @@
22from nova.api.openstack import faults22from nova.api.openstack import faults
23from nova.api.openstack import common23from nova.api.openstack import common
24from nova.compute import instance_types24from nova.compute import instance_types
25from nova.api.openstack.views import flavors as flavors_views
25from nova import wsgi26from nova import wsgi
26import nova.api.openstack27import nova.api.openstack
2728
@@ -46,14 +47,33 @@
4647
47 def show(self, req, id):48 def show(self, req, id):
48 """Return data about the given flavor id."""49 """Return data about the given flavor id."""
50<<<<<<< TREE
49 ctxt = req.environ['nova.context']51 ctxt = req.environ['nova.context']
50 values = db.instance_type_get_by_flavor_id(ctxt, id)52 values = db.instance_type_get_by_flavor_id(ctxt, id)
51 return dict(flavor=values)53 return dict(flavor=values)
52 raise faults.Fault(exc.HTTPNotFound())54 raise faults.Fault(exc.HTTPNotFound())
55=======
56 ctxt = req.environ['nova.context']
57 flavor = db.api.instance_type_get_by_flavor_id(ctxt, id)
58 values = {
59 "id": flavor["flavorid"],
60 "name": flavor["name"],
61 "ram": flavor["memory_mb"],
62 "disk": flavor["local_gb"],
63 }
64 return dict(flavor=values)
65>>>>>>> MERGE-SOURCE
5366
54 def _all_ids(self, req):67 def _all_ids(self, req):
55 """Return the list of all flavorids."""68 """Return the list of all flavorids."""
69<<<<<<< TREE
56 ctxt = req.environ['nova.context']70 ctxt = req.environ['nova.context']
57 inst_types = db.instance_type_get_all(ctxt)71 inst_types = db.instance_type_get_all(ctxt)
58 flavor_ids = [inst_types[i]['flavorid'] for i in inst_types.keys()]72 flavor_ids = [inst_types[i]['flavorid'] for i in inst_types.keys()]
59 return sorted(flavor_ids)73 return sorted(flavor_ids)
74=======
75 ctxt = req.environ['nova.context']
76 inst_types = db.api.instance_type_get_all(ctxt)
77 flavor_ids = [inst_types[i]['flavorid'] for i in inst_types.keys()]
78 return sorted(flavor_ids)
79>>>>>>> MERGE-SOURCE
6080
=== modified file 'nova/api/openstack/images.py'
--- nova/api/openstack/images.py 2011-03-11 19:49:32 +0000
+++ nova/api/openstack/images.py 2011-03-25 13:43:55 +0000
@@ -15,10 +15,17 @@
15# License for the specific language governing permissions and limitations15# License for the specific language governing permissions and limitations
16# under the License.16# under the License.
1717
18<<<<<<< TREE
19=======
20import datetime
21
22>>>>>>> MERGE-SOURCE
18from webob import exc23from webob import exc
1924
20from nova import compute25from nova import compute
26from nova import exception
21from nova import flags27from nova import flags
28from nova import log
22from nova import utils29from nova import utils
23from nova import wsgi30from nova import wsgi
24import nova.api.openstack31import nova.api.openstack
@@ -27,6 +34,8 @@
27import nova.image.service34import nova.image.service
2835
2936
37LOG = log.getLogger('nova.api.openstack.images')
38
30FLAGS = flags.FLAGS39FLAGS = flags.FLAGS
3140
3241
@@ -84,8 +93,6 @@
84 # S3ImageService93 # S3ImageService
85 pass94 pass
8695
87 return item
88
8996
90def _filter_keys(item, keys):97def _filter_keys(item, keys):
91 """98 """
@@ -104,6 +111,100 @@
104 image['id'] = image_id111 image['id'] = image_id
105112
106113
114def _translate_s3_like_images(image_metadata):
115 """Work-around for leaky S3ImageService abstraction"""
116 api_metadata = image_metadata.copy()
117 _convert_image_id_to_hash(api_metadata)
118 api_metadata = _translate_keys(api_metadata)
119 _translate_status(api_metadata)
120 return api_metadata
121
122
123def _translate_from_image_service_to_api(image_metadata):
124 """Translate from ImageService to OpenStack API style attribute names
125
126 This involves 4 steps:
127
128 1. Filter out attributes that the OpenStack API doesn't need
129
130 2. Translate from base image attributes from names used by
131 BaseImageService to names used by OpenStack API
132
133 3. Add in any image properties
134
135 4. Format values according to API spec (for example dates must
136 look like "2010-08-10T12:00:00Z")
137 """
138 service_metadata = image_metadata.copy()
139 properties = service_metadata.pop('properties', {})
140
141 # 1. Filter out unecessary attributes
142 api_keys = ['id', 'name', 'updated_at', 'created_at', 'status']
143 api_metadata = utils.subset_dict(service_metadata, api_keys)
144
145 # 2. Translate base image attributes
146 api_map = {'updated_at': 'updated', 'created_at': 'created'}
147 api_metadata = utils.map_dict_keys(api_metadata, api_map)
148
149 # 3. Add in any image properties
150 # 3a. serverId is used for backups and snapshots
151 try:
152 api_metadata['serverId'] = int(properties['instance_id'])
153 except KeyError:
154 pass # skip if it's not present
155 except ValueError:
156 pass # skip if it's not an integer
157
158 # 3b. Progress special case
159 # TODO(sirp): ImageService doesn't have a notion of progress yet, so for
160 # now just fake it
161 if service_metadata['status'] == 'saving':
162 api_metadata['progress'] = 0
163
164 # 4. Format values
165 # 4a. Format Image Status (API requires uppercase)
166 api_metadata['status'] = _format_status_for_api(api_metadata['status'])
167
168 # 4b. Format timestamps
169 for attr in ('created', 'updated'):
170 if attr in api_metadata:
171 api_metadata[attr] = _format_datetime_for_api(
172 api_metadata[attr])
173
174 return api_metadata
175
176
177def _format_status_for_api(status):
178 """Return status in a format compliant with OpenStack API"""
179 mapping = {'queued': 'QUEUED',
180 'preparing': 'PREPARING',
181 'saving': 'SAVING',
182 'active': 'ACTIVE',
183 'killed': 'FAILED'}
184 return mapping[status]
185
186
187def _format_datetime_for_api(datetime_):
188 """Stringify datetime objects in a format compliant with OpenStack API"""
189 API_DATETIME_FMT = '%Y-%m-%dT%H:%M:%SZ'
190 return datetime_.strftime(API_DATETIME_FMT)
191
192
193def _safe_translate(image_metadata):
194 """Translate attributes for OpenStack API, temporary workaround for
195 S3ImageService attribute leakage.
196 """
197 # FIXME(sirp): The S3ImageService appears to be leaking implementation
198 # details, including its internal attribute names, and internal
199 # `status` values. Working around it for now.
200 s3_like_image = ('imageId' in image_metadata)
201 if s3_like_image:
202 translate = _translate_s3_like_images
203 else:
204 translate = _translate_from_image_service_to_api
205 return translate(image_metadata)
206
207
107class Controller(wsgi.Controller):208class Controller(wsgi.Controller):
108209
109 _serialization_metadata = {210 _serialization_metadata = {
@@ -117,33 +218,32 @@
117218
118 def index(self, req):219 def index(self, req):
119 """Return all public images in brief"""220 """Return all public images in brief"""
120 items = self._service.index(req.environ['nova.context'])221 context = req.environ['nova.context']
121 items = common.limited(items, req)222 image_metas = self._service.index(context)
122 items = [_filter_keys(item, ('id', 'name')) for item in items]223 image_metas = common.limited(image_metas, req)
123 return dict(images=items)224 return dict(images=image_metas)
124225
125 def detail(self, req):226 def detail(self, req):
126 """Return all public images in detail"""227 """Return all public images in detail"""
127 try:228 context = req.environ['nova.context']
128 items = self._service.detail(req.environ['nova.context'])229 image_metas = self._service.detail(context)
129 except NotImplementedError:230 image_metas = common.limited(image_metas, req)
130 items = self._service.index(req.environ['nova.context'])231 api_image_metas = [_safe_translate(image_meta)
131 for image in items:232 for image_meta in image_metas]
132 _convert_image_id_to_hash(image)233 return dict(images=api_image_metas)
133
134 items = common.limited(items, req)
135 items = [_translate_keys(item) for item in items]
136 items = [_translate_status(item) for item in items]
137 return dict(images=items)
138234
139 def show(self, req, id):235 def show(self, req, id):
140 """Return data about the given image id"""236 """Return data about the given image id"""
141 image_id = common.get_image_id_from_image_hash(self._service,237 context = req.environ['nova.context']
142 req.environ['nova.context'], id)238 try:
239 image_id = common.get_image_id_from_image_hash(
240 self._service, context, id)
241 except exception.NotFound:
242 raise faults.Fault(exc.HTTPNotFound())
143243
144 image = self._service.show(req.environ['nova.context'], image_id)244 image_meta = self._service.show(context, image_id)
145 _convert_image_id_to_hash(image)245 api_image_meta = _safe_translate(image_meta)
146 return dict(image=image)246 return dict(image=api_image_meta)
147247
148 def delete(self, req, id):248 def delete(self, req, id):
149 # Only public images are supported for now.249 # Only public images are supported for now.
@@ -154,11 +254,10 @@
154 env = self._deserialize(req.body, req.get_content_type())254 env = self._deserialize(req.body, req.get_content_type())
155 instance_id = env["image"]["serverId"]255 instance_id = env["image"]["serverId"]
156 name = env["image"]["name"]256 name = env["image"]["name"]
157
158 image_meta = compute.API().snapshot(257 image_meta = compute.API().snapshot(
159 context, instance_id, name)258 context, instance_id, name)
160259 api_image_meta = _safe_translate(image_meta)
161 return dict(image=image_meta)260 return dict(image=api_image_meta)
162261
163 def update(self, req, id):262 def update(self, req, id):
164 # Users may not modify public images, and that's all that263 # Users may not modify public images, and that's all that
165264
=== added file 'nova/api/openstack/limits.py'
--- nova/api/openstack/limits.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/limits.py 2011-03-25 13:43:55 +0000
@@ -0,0 +1,358 @@
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.import datetime
15
16"""
17Module dedicated functions/classes dealing with rate limiting requests.
18"""
19
20import copy
21import httplib
22import json
23import math
24import re
25import time
26import urllib
27import webob.exc
28
29from collections import defaultdict
30
31from webob.dec import wsgify
32
33from nova import wsgi
34from nova.api.openstack import faults
35from nova.wsgi import Controller
36from nova.wsgi import Middleware
37
38
39# Convenience constants for the limits dictionary passed to Limiter().
40PER_SECOND = 1
41PER_MINUTE = 60
42PER_HOUR = 60 * 60
43PER_DAY = 60 * 60 * 24
44
45
46class LimitsController(Controller):
47 """
48 Controller for accessing limits in the OpenStack API.
49 """
50
51 _serialization_metadata = {
52 "application/xml": {
53 "attributes": {
54 "limit": ["verb", "URI", "regex", "value", "unit",
55 "resetTime", "remaining", "name"],
56 },
57 "plurals": {
58 "rate": "limit",
59 },
60 },
61 }
62
63 def index(self, req):
64 """
65 Return all global and rate limit information.
66 """
67 abs_limits = {}
68 rate_limits = req.environ.get("nova.limits", [])
69
70 return {
71 "limits": {
72 "rate": rate_limits,
73 "absolute": abs_limits,
74 },
75 }
76
77
78class Limit(object):
79 """
80 Stores information about a limit for HTTP requets.
81 """
82
83 UNITS = {
84 1: "SECOND",
85 60: "MINUTE",
86 60 * 60: "HOUR",
87 60 * 60 * 24: "DAY",
88 }
89
90 def __init__(self, verb, uri, regex, value, unit):
91 """
92 Initialize a new `Limit`.
93
94 @param verb: HTTP verb (POST, PUT, etc.)
95 @param uri: Human-readable URI
96 @param regex: Regular expression format for this limit
97 @param value: Integer number of requests which can be made
98 @param unit: Unit of measure for the value parameter
99 """
100 self.verb = verb
101 self.uri = uri
102 self.regex = regex
103 self.value = int(value)
104 self.unit = unit
105 self.unit_string = self.display_unit().lower()
106 self.remaining = int(value)
107
108 if value <= 0:
109 raise ValueError("Limit value must be > 0")
110
111 self.last_request = None
112 self.next_request = None
113
114 self.water_level = 0
115 self.capacity = self.unit
116 self.request_value = float(self.capacity) / float(self.value)
117 self.error_message = _("Only %(value)s %(verb)s request(s) can be "\
118 "made to %(uri)s every %(unit_string)s." % self.__dict__)
119
120 def __call__(self, verb, url):
121 """
122 Represents a call to this limit from a relevant request.
123
124 @param verb: string http verb (POST, GET, etc.)
125 @param url: string URL
126 """
127 if self.verb != verb or not re.match(self.regex, url):
128 return
129
130 now = self._get_time()
131
132 if self.last_request is None:
133 self.last_request = now
134
135 leak_value = now - self.last_request
136
137 self.water_level -= leak_value
138 self.water_level = max(self.water_level, 0)
139 self.water_level += self.request_value
140
141 difference = self.water_level - self.capacity
142
143 self.last_request = now
144
145 if difference > 0:
146 self.water_level -= self.request_value
147 self.next_request = now + difference
148 return difference
149
150 cap = self.capacity
151 water = self.water_level
152 val = self.value
153
154 self.remaining = math.floor(((cap - water) / cap) * val)
155 self.next_request = now
156
157 def _get_time(self):
158 """Retrieve the current time. Broken out for testability."""
159 return time.time()
160
161 def display_unit(self):
162 """Display the string name of the unit."""
163 return self.UNITS.get(self.unit, "UNKNOWN")
164
165 def display(self):
166 """Return a useful representation of this class."""
167 return {
168 "verb": self.verb,
169 "URI": self.uri,
170 "regex": self.regex,
171 "value": self.value,
172 "remaining": int(self.remaining),
173 "unit": self.display_unit(),
174 "resetTime": int(self.next_request or self._get_time()),
175 }
176
177# "Limit" format is a dictionary with the HTTP verb, human-readable URI,
178# a regular-expression to match, value and unit of measure (PER_DAY, etc.)
179
180DEFAULT_LIMITS = [
181 Limit("POST", "*", ".*", 10, PER_MINUTE),
182 Limit("POST", "*/servers", "^/servers", 50, PER_DAY),
183 Limit("PUT", "*", ".*", 10, PER_MINUTE),
184 Limit("GET", "*changes-since*", ".*changes-since.*", 3, PER_MINUTE),
185 Limit("DELETE", "*", ".*", 100, PER_MINUTE),
186]
187
188
189class RateLimitingMiddleware(Middleware):
190 """
191 Rate-limits requests passing through this middleware. All limit information
192 is stored in memory for this implementation.
193 """
194
195 def __init__(self, application, limits=None):
196 """
197 Initialize new `RateLimitingMiddleware`, which wraps the given WSGI
198 application and sets up the given limits.
199
200 @param application: WSGI application to wrap
201 @param limits: List of dictionaries describing limits
202 """
203 Middleware.__init__(self, application)
204 self._limiter = Limiter(limits or DEFAULT_LIMITS)
205
206 @wsgify(RequestClass=wsgi.Request)
207 def __call__(self, req):
208 """
209 Represents a single call through this middleware. We should record the
210 request if we have a limit relevant to it. If no limit is relevant to
211 the request, ignore it.
212
213 If the request should be rate limited, return a fault telling the user
214 they are over the limit and need to retry later.
215 """
216 verb = req.method
217 url = req.url
218 context = req.environ.get("nova.context")
219
220 if context:
221 username = context.user_id
222 else:
223 username = None
224
225 delay, error = self._limiter.check_for_delay(verb, url, username)
226
227 if delay:
228 msg = _("This request was rate-limited.")
229 retry = time.time() + delay
230 return faults.OverLimitFault(msg, error, retry)
231
232 req.environ["nova.limits"] = self._limiter.get_limits(username)
233
234 return self.application
235
236
237class Limiter(object):
238 """
239 Rate-limit checking class which handles limits in memory.
240 """
241
242 def __init__(self, limits):
243 """
244 Initialize the new `Limiter`.
245
246 @param limits: List of `Limit` objects
247 """
248 self.limits = copy.deepcopy(limits)
249 self.levels = defaultdict(lambda: copy.deepcopy(limits))
250
251 def get_limits(self, username=None):
252 """
253 Return the limits for a given user.
254 """
255 return [limit.display() for limit in self.levels[username]]
256
257 def check_for_delay(self, verb, url, username=None):
258 """
259 Check the given verb/user/user triplet for limit.
260
261 @return: Tuple of delay (in seconds) and error message (or None, None)
262 """
263 delays = []
264
265 for limit in self.levels[username]:
266 delay = limit(verb, url)
267 if delay:
268 delays.append((delay, limit.error_message))
269
270 if delays:
271 delays.sort()
272 return delays[0]
273
274 return None, None
275
276
277class WsgiLimiter(object):
278 """
279 Rate-limit checking from a WSGI application. Uses an in-memory `Limiter`.
280
281 To use:
282 POST /<username> with JSON data such as:
283 {
284 "verb" : GET,
285 "path" : "/servers"
286 }
287
288 and receive a 204 No Content, or a 403 Forbidden with an X-Wait-Seconds
289 header containing the number of seconds to wait before the action would
290 succeed.
291 """
292
293 def __init__(self, limits=None):
294 """
295 Initialize the new `WsgiLimiter`.
296
297 @param limits: List of `Limit` objects
298 """
299 self._limiter = Limiter(limits or DEFAULT_LIMITS)
300
301 @wsgify(RequestClass=wsgi.Request)
302 def __call__(self, request):
303 """
304 Handles a call to this application. Returns 204 if the request is
305 acceptable to the limiter, else a 403 is returned with a relevant
306 header indicating when the request *will* succeed.
307 """
308 if request.method != "POST":
309 raise webob.exc.HTTPMethodNotAllowed()
310
311 try:
312 info = dict(json.loads(request.body))
313 except ValueError:
314 raise webob.exc.HTTPBadRequest()
315
316 username = request.path_info_pop()
317 verb = info.get("verb")
318 path = info.get("path")
319
320 delay, error = self._limiter.check_for_delay(verb, path, username)
321
322 if delay:
323 headers = {"X-Wait-Seconds": "%.2f" % delay}
324 return webob.exc.HTTPForbidden(headers=headers, explanation=error)
325 else:
326 return webob.exc.HTTPNoContent()
327
328
329class WsgiLimiterProxy(object):
330 """
331 Rate-limit requests based on answers from a remote source.
332 """
333
334 def __init__(self, limiter_address):
335 """
336 Initialize the new `WsgiLimiterProxy`.
337
338 @param limiter_address: IP/port combination of where to request limit
339 """
340 self.limiter_address = limiter_address
341
342 def check_for_delay(self, verb, path, username=None):
343 body = json.dumps({"verb": verb, "path": path})
344 headers = {"Content-Type": "application/json"}
345
346 conn = httplib.HTTPConnection(self.limiter_address)
347
348 if username:
349 conn.request("POST", "/%s" % (username), body, headers)
350 else:
351 conn.request("POST", "/", body, headers)
352
353 resp = conn.getresponse()
354
355 if 200 >= resp.status < 300:
356 return None, None
357
358 return resp.getheader("X-Wait-Seconds"), resp.read() or None
0359
=== added file 'nova/api/openstack/server_metadata.py'
--- nova/api/openstack/server_metadata.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/server_metadata.py 2011-03-25 13:43:55 +0000
@@ -0,0 +1,78 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2011 OpenStack LLC.
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
18from webob import exc
19
20from nova import compute
21from nova import wsgi
22from nova.api.openstack import faults
23
24
25class Controller(wsgi.Controller):
26 """ The server metadata API controller for the Openstack API """
27
28 def __init__(self):
29 self.compute_api = compute.API()
30 super(Controller, self).__init__()
31
32 def _get_metadata(self, context, server_id):
33 metadata = self.compute_api.get_instance_metadata(context, server_id)
34 meta_dict = {}
35 for key, value in metadata.iteritems():
36 meta_dict[key] = value
37 return dict(metadata=meta_dict)
38
39 def index(self, req, server_id):
40 """ Returns the list of metadata for a given instance """
41 context = req.environ['nova.context']
42 return self._get_metadata(context, server_id)
43
44 def create(self, req, server_id):
45 context = req.environ['nova.context']
46 body = self._deserialize(req.body, req.get_content_type())
47 self.compute_api.update_or_create_instance_metadata(context,
48 server_id,
49 body['metadata'])
50 return req.body
51
52 def update(self, req, server_id, id):
53 context = req.environ['nova.context']
54 body = self._deserialize(req.body, req.get_content_type())
55 if not id in body:
56 expl = _('Request body and URI mismatch')
57 raise exc.HTTPBadRequest(explanation=expl)
58 if len(body) > 1:
59 expl = _('Request body contains too many items')
60 raise exc.HTTPBadRequest(explanation=expl)
61 self.compute_api.update_or_create_instance_metadata(context,
62 server_id,
63 body)
64 return req.body
65
66 def show(self, req, server_id, id):
67 """ Return a single metadata item """
68 context = req.environ['nova.context']
69 data = self._get_metadata(context, server_id)
70 if id in data['metadata']:
71 return {id: data['metadata'][id]}
72 else:
73 return faults.Fault(exc.HTTPNotFound())
74
75 def delete(self, req, server_id, id):
76 """ Deletes an existing metadata """
77 context = req.environ['nova.context']
78 self.compute_api.delete_instance_metadata(context, server_id, id)
079
=== modified file 'nova/api/openstack/servers.py'
--- nova/api/openstack/servers.py 2011-03-11 19:49:32 +0000
+++ nova/api/openstack/servers.py 2011-03-25 13:43:55 +0000
@@ -13,32 +13,48 @@
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
16<<<<<<< TREE
16import hashlib17import hashlib
17import json18import json
19=======
20import base64
21import hashlib
22>>>>>>> MERGE-SOURCE
18import traceback23import traceback
1924
20from webob import exc25from webob import exc
26from xml.dom import minidom
2127
22from nova import compute28from nova import compute
29from nova import context
23from nova import exception30from nova import exception
24from nova import flags31from nova import flags
25from nova import log as logging32from nova import log as logging
33from nova import quota
34from nova import utils
26from nova import wsgi35from nova import wsgi
27from nova import utils
28from nova.api.openstack import common36from nova.api.openstack import common
29from nova.api.openstack import faults37from nova.api.openstack import faults
38import nova.api.openstack.views.addresses
39import nova.api.openstack.views.flavors
40import nova.api.openstack.views.servers
30from nova.auth import manager as auth_manager41from nova.auth import manager as auth_manager
31from nova.compute import instance_types42from nova.compute import instance_types
32from nova.compute import power_state43from nova.compute import power_state
33import nova.api.openstack44import nova.api.openstack
45from nova.scheduler import api as scheduler_api
3446
3547
36LOG = logging.getLogger('server')48LOG = logging.getLogger('server')
3749<<<<<<< TREE
3850
51
52=======
53>>>>>>> MERGE-SOURCE
39FLAGS = flags.FLAGS54FLAGS = flags.FLAGS
4055
4156
57<<<<<<< TREE
42def _translate_detail_keys(inst):58def _translate_detail_keys(inst):
43 """ Coerces into dictionary format, mapping everything to Rackspace-like59 """ Coerces into dictionary format, mapping everything to Rackspace-like
44 attributes for return"""60 attributes for return"""
@@ -91,6 +107,8 @@
91 return dict(server=dict(id=inst['id'], name=inst['display_name']))107 return dict(server=dict(id=inst['id'], name=inst['display_name']))
92108
93109
110=======
111>>>>>>> MERGE-SOURCE
94class Controller(wsgi.Controller):112class Controller(wsgi.Controller):
95 """ The Server API controller for the OpenStack API """113 """ The Server API controller for the OpenStack API """
96114
@@ -98,39 +116,59 @@
98 'application/xml': {116 'application/xml': {
99 "attributes": {117 "attributes": {
100 "server": ["id", "imageId", "name", "flavorId", "hostId",118 "server": ["id", "imageId", "name", "flavorId", "hostId",
119<<<<<<< TREE
101 "status", "progress", "adminPass"]}}}120 "status", "progress", "adminPass"]}}}
121=======
122 "status", "progress", "adminPass", "flavorRef",
123 "imageRef"]}}}
124>>>>>>> MERGE-SOURCE
102125
103 def __init__(self):126 def __init__(self):
104 self.compute_api = compute.API()127 self.compute_api = compute.API()
105 self._image_service = utils.import_object(FLAGS.image_service)128 self._image_service = utils.import_object(FLAGS.image_service)
106 super(Controller, self).__init__()129 super(Controller, self).__init__()
107130
131 def ips(self, req, id):
132 try:
133 instance = self.compute_api.get(req.environ['nova.context'], id)
134 except exception.NotFound:
135 return faults.Fault(exc.HTTPNotFound())
136
137 builder = self._get_addresses_view_builder(req)
138 return builder.build(instance)
139
108 def index(self, req):140 def index(self, req):
109 """ Returns a list of server names and ids for a given user """141 """ Returns a list of server names and ids for a given user """
110 return self._items(req, entity_maker=_translate_keys)142 return self._items(req, is_detail=False)
111143
112 def detail(self, req):144 def detail(self, req):
113 """ Returns a list of server details for a given user """145 """ Returns a list of server details for a given user """
114 return self._items(req, entity_maker=_translate_detail_keys)146 return self._items(req, is_detail=True)
115147
116 def _items(self, req, entity_maker):148 def _items(self, req, is_detail):
117 """Returns a list of servers for a given user.149 """Returns a list of servers for a given user.
118150
119 entity_maker - either _translate_detail_keys or _translate_keys151 builder - the response model builder
120 """152 """
121 instance_list = self.compute_api.get_all(req.environ['nova.context'])153 instance_list = self.compute_api.get_all(req.environ['nova.context'])
122 limited_list = common.limited(instance_list, req)154 limited_list = self._limit_items(instance_list, req)
123 res = [entity_maker(inst)['server'] for inst in limited_list]155 builder = self._get_view_builder(req)
124 return dict(servers=res)156 servers = [builder.build(inst, is_detail)['server']
157 for inst in limited_list]
158 return dict(servers=servers)
125159
160 @scheduler_api.redirect_handler
126 def show(self, req, id):161 def show(self, req, id):
127 """ Returns server details by server id """162 """ Returns server details by server id """
128 try:163 try:
129 instance = self.compute_api.get(req.environ['nova.context'], id)164 instance = self.compute_api.routing_get(
130 return _translate_detail_keys(instance)165 req.environ['nova.context'], id)
166 builder = self._get_view_builder(req)
167 return builder.build(instance, is_detail=True)
131 except exception.NotFound:168 except exception.NotFound:
132 return faults.Fault(exc.HTTPNotFound())169 return faults.Fault(exc.HTTPNotFound())
133170
171 @scheduler_api.redirect_handler
134 def delete(self, req, id):172 def delete(self, req, id):
135 """ Destroys a server """173 """ Destroys a server """
136 try:174 try:
@@ -141,20 +179,43 @@
141179
142 def create(self, req):180 def create(self, req):
143 """ Creates a new server for a given user """181 """ Creates a new server for a given user """
182<<<<<<< TREE
144 env = self._deserialize(req.body, req.get_content_type())183 env = self._deserialize(req.body, req.get_content_type())
184=======
185 env = self._deserialize_create(req)
186>>>>>>> MERGE-SOURCE
145 if not env:187 if not env:
146 return faults.Fault(exc.HTTPUnprocessableEntity())188 return faults.Fault(exc.HTTPUnprocessableEntity())
147189
190<<<<<<< TREE
148 context = req.environ['nova.context']191 context = req.environ['nova.context']
149 key_pairs = auth_manager.AuthManager.get_key_pairs(context)192 key_pairs = auth_manager.AuthManager.get_key_pairs(context)
150 if not key_pairs:193 if not key_pairs:
151 raise exception.NotFound(_("No keypairs defined"))194 raise exception.NotFound(_("No keypairs defined"))
152 key_pair = key_pairs[0]195 key_pair = key_pairs[0]
153196
197=======
198 context = req.environ['nova.context']
199
200 key_name = None
201 key_data = None
202 key_pairs = auth_manager.AuthManager.get_key_pairs(context)
203 if key_pairs:
204 key_pair = key_pairs[0]
205 key_name = key_pair['name']
206 key_data = key_pair['public_key']
207
208 requested_image_id = self._image_id_from_req_data(env)
209>>>>>>> MERGE-SOURCE
154 image_id = common.get_image_id_from_image_hash(self._image_service,210 image_id = common.get_image_id_from_image_hash(self._image_service,
211<<<<<<< TREE
155 context, env['server']['imageId'])212 context, env['server']['imageId'])
213=======
214 context, requested_image_id)
215>>>>>>> MERGE-SOURCE
156 kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(216 kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
157 req, image_id)217 req, image_id)
218<<<<<<< TREE
158219
159 # Metadata is a list, not a Dictionary, because we allow duplicate keys220 # Metadata is a list, not a Dictionary, because we allow duplicate keys
160 # (even though JSON can't encode this)221 # (even though JSON can't encode this)
@@ -187,6 +248,110 @@
187 password)248 password)
188 return server249 return server
189250
251=======
252
253 # Metadata is a list, not a Dictionary, because we allow duplicate keys
254 # (even though JSON can't encode this)
255 # In future, we may not allow duplicate keys.
256 # However, the CloudServers API is not definitive on this front,
257 # and we want to be compatible.
258 metadata = []
259 if env['server'].get('metadata'):
260 for k, v in env['server']['metadata'].items():
261 metadata.append({'key': k, 'value': v})
262
263 personality = env['server'].get('personality')
264 injected_files = []
265 if personality:
266 injected_files = self._get_injected_files(personality)
267
268 flavor_id = self._flavor_id_from_req_data(env)
269 try:
270 (inst,) = self.compute_api.create(
271 context,
272 instance_types.get_by_flavor_id(flavor_id),
273 image_id,
274 kernel_id=kernel_id,
275 ramdisk_id=ramdisk_id,
276 display_name=env['server']['name'],
277 display_description=env['server']['name'],
278 key_name=key_name,
279 key_data=key_data,
280 metadata=metadata,
281 injected_files=injected_files)
282 except quota.QuotaError as error:
283 self._handle_quota_error(error)
284
285 inst['instance_type'] = flavor_id
286 inst['image_id'] = requested_image_id
287
288 builder = self._get_view_builder(req)
289 server = builder.build(inst, is_detail=True)
290 password = "%s%s" % (server['server']['name'][:4],
291 utils.generate_password(12))
292 server['server']['adminPass'] = password
293 self.compute_api.set_admin_password(context, server['server']['id'],
294 password)
295 return server
296
297 def _deserialize_create(self, request):
298 """
299 Deserialize a create request
300
301 Overrides normal behavior in the case of xml content
302 """
303 if request.content_type == "application/xml":
304 deserializer = ServerCreateRequestXMLDeserializer()
305 return deserializer.deserialize(request.body)
306 else:
307 return self._deserialize(request.body, request.get_content_type())
308
309 def _get_injected_files(self, personality):
310 """
311 Create a list of injected files from the personality attribute
312
313 At this time, injected_files must be formatted as a list of
314 (file_path, file_content) pairs for compatibility with the
315 underlying compute service.
316 """
317 injected_files = []
318
319 for item in personality:
320 try:
321 path = item['path']
322 contents = item['contents']
323 except KeyError as key:
324 expl = _('Bad personality format: missing %s') % key
325 raise exc.HTTPBadRequest(explanation=expl)
326 except TypeError:
327 expl = _('Bad personality format')
328 raise exc.HTTPBadRequest(explanation=expl)
329 try:
330 contents = base64.b64decode(contents)
331 except TypeError:
332 expl = _('Personality content for %s cannot be decoded') % path
333 raise exc.HTTPBadRequest(explanation=expl)
334 injected_files.append((path, contents))
335 return injected_files
336
337 def _handle_quota_error(self, error):
338 """
339 Reraise quota errors as api-specific http exceptions
340 """
341 if error.code == "OnsetFileLimitExceeded":
342 expl = _("Personality file limit exceeded")
343 raise exc.HTTPBadRequest(explanation=expl)
344 if error.code == "OnsetFilePathLimitExceeded":
345 expl = _("Personality file path too long")
346 raise exc.HTTPBadRequest(explanation=expl)
347 if error.code == "OnsetFileContentLimitExceeded":
348 expl = _("Personality file content too long")
349 raise exc.HTTPBadRequest(explanation=expl)
350 # if the original error is okay, just reraise it
351 raise error
352
353 @scheduler_api.redirect_handler
354>>>>>>> MERGE-SOURCE
190 def update(self, req, id):355 def update(self, req, id):
191 """ Updates the server name or password """356 """ Updates the server name or password """
192 if len(req.body) == 0:357 if len(req.body) == 0:
@@ -202,7 +367,7 @@
202 update_dict['admin_pass'] = inst_dict['server']['adminPass']367 update_dict['admin_pass'] = inst_dict['server']['adminPass']
203 try:368 try:
204 self.compute_api.set_admin_password(ctxt, id)369 self.compute_api.set_admin_password(ctxt, id)
205 except exception.TimeoutException, e:370 except exception.TimeoutException:
206 return exc.HTTPRequestTimeout()371 return exc.HTTPRequestTimeout()
207 if 'name' in inst_dict['server']:372 if 'name' in inst_dict['server']:
208 update_dict['display_name'] = inst_dict['server']['name']373 update_dict['display_name'] = inst_dict['server']['name']
@@ -212,6 +377,7 @@
212 return faults.Fault(exc.HTTPNotFound())377 return faults.Fault(exc.HTTPNotFound())
213 return exc.HTTPNoContent()378 return exc.HTTPNoContent()
214379
380 @scheduler_api.redirect_handler
215 def action(self, req, id):381 def action(self, req, id):
216 """Multi-purpose method used to reboot, rebuild, or382 """Multi-purpose method used to reboot, rebuild, or
217 resize a server"""383 resize a server"""
@@ -277,6 +443,7 @@
277 return faults.Fault(exc.HTTPUnprocessableEntity())443 return faults.Fault(exc.HTTPUnprocessableEntity())
278 return exc.HTTPAccepted()444 return exc.HTTPAccepted()
279445
446 @scheduler_api.redirect_handler
280 def lock(self, req, id):447 def lock(self, req, id):
281 """448 """
282 lock the instance with id449 lock the instance with id
@@ -292,6 +459,7 @@
292 return faults.Fault(exc.HTTPUnprocessableEntity())459 return faults.Fault(exc.HTTPUnprocessableEntity())
293 return exc.HTTPAccepted()460 return exc.HTTPAccepted()
294461
462 @scheduler_api.redirect_handler
295 def unlock(self, req, id):463 def unlock(self, req, id):
296 """464 """
297 unlock the instance with id465 unlock the instance with id
@@ -307,6 +475,7 @@
307 return faults.Fault(exc.HTTPUnprocessableEntity())475 return faults.Fault(exc.HTTPUnprocessableEntity())
308 return exc.HTTPAccepted()476 return exc.HTTPAccepted()
309477
478 @scheduler_api.redirect_handler
310 def get_lock(self, req, id):479 def get_lock(self, req, id):
311 """480 """
312 return the boolean state of (instance with id)'s lock481 return the boolean state of (instance with id)'s lock
@@ -321,34 +490,68 @@
321 return faults.Fault(exc.HTTPUnprocessableEntity())490 return faults.Fault(exc.HTTPUnprocessableEntity())
322 return exc.HTTPAccepted()491 return exc.HTTPAccepted()
323492
324 def reset_network(self, req, id):493<<<<<<< TREE
325 """494 def reset_network(self, req, id):
326 Reset networking on an instance (admin only).495 """
327496 Reset networking on an instance (admin only).
328 """497
329 context = req.environ['nova.context']498 """
330 try:499 context = req.environ['nova.context']
331 self.compute_api.reset_network(context, id)500 try:
332 except:501 self.compute_api.reset_network(context, id)
333 readable = traceback.format_exc()502 except:
334 LOG.exception(_("Compute.api::reset_network %s"), readable)503 readable = traceback.format_exc()
335 return faults.Fault(exc.HTTPUnprocessableEntity())504 LOG.exception(_("Compute.api::reset_network %s"), readable)
336 return exc.HTTPAccepted()505 return faults.Fault(exc.HTTPUnprocessableEntity())
337506 return exc.HTTPAccepted()
338 def inject_network_info(self, req, id):507
339 """508 def inject_network_info(self, req, id):
340 Inject network info for an instance (admin only).509 """
341510 Inject network info for an instance (admin only).
342 """511
343 context = req.environ['nova.context']512 """
344 try:513 context = req.environ['nova.context']
345 self.compute_api.inject_network_info(context, id)514 try:
346 except:515 self.compute_api.inject_network_info(context, id)
347 readable = traceback.format_exc()516 except:
348 LOG.exception(_("Compute.api::inject_network_info %s"), readable)517 readable = traceback.format_exc()
349 return faults.Fault(exc.HTTPUnprocessableEntity())518 LOG.exception(_("Compute.api::inject_network_info %s"), readable)
350 return exc.HTTPAccepted()519 return faults.Fault(exc.HTTPUnprocessableEntity())
351520 return exc.HTTPAccepted()
521
522=======
523 @scheduler_api.redirect_handler
524 def reset_network(self, req, id):
525 """
526 Reset networking on an instance (admin only).
527
528 """
529 context = req.environ['nova.context']
530 try:
531 self.compute_api.reset_network(context, id)
532 except:
533 readable = traceback.format_exc()
534 LOG.exception(_("Compute.api::reset_network %s"), readable)
535 return faults.Fault(exc.HTTPUnprocessableEntity())
536 return exc.HTTPAccepted()
537
538 @scheduler_api.redirect_handler
539 def inject_network_info(self, req, id):
540 """
541 Inject network info for an instance (admin only).
542
543 """
544 context = req.environ['nova.context']
545 try:
546 self.compute_api.inject_network_info(context, id)
547 except:
548 readable = traceback.format_exc()
549 LOG.exception(_("Compute.api::inject_network_info %s"), readable)
550 return faults.Fault(exc.HTTPUnprocessableEntity())
551 return exc.HTTPAccepted()
552
553 @scheduler_api.redirect_handler
554>>>>>>> MERGE-SOURCE
352 def pause(self, req, id):555 def pause(self, req, id):
353 """ Permit Admins to Pause the server. """556 """ Permit Admins to Pause the server. """
354 ctxt = req.environ['nova.context']557 ctxt = req.environ['nova.context']
@@ -360,6 +563,7 @@
360 return faults.Fault(exc.HTTPUnprocessableEntity())563 return faults.Fault(exc.HTTPUnprocessableEntity())
361 return exc.HTTPAccepted()564 return exc.HTTPAccepted()
362565
566 @scheduler_api.redirect_handler
363 def unpause(self, req, id):567 def unpause(self, req, id):
364 """ Permit Admins to Unpause the server. """568 """ Permit Admins to Unpause the server. """
365 ctxt = req.environ['nova.context']569 ctxt = req.environ['nova.context']
@@ -371,6 +575,7 @@
371 return faults.Fault(exc.HTTPUnprocessableEntity())575 return faults.Fault(exc.HTTPUnprocessableEntity())
372 return exc.HTTPAccepted()576 return exc.HTTPAccepted()
373577
578 @scheduler_api.redirect_handler
374 def suspend(self, req, id):579 def suspend(self, req, id):
375 """permit admins to suspend the server"""580 """permit admins to suspend the server"""
376 context = req.environ['nova.context']581 context = req.environ['nova.context']
@@ -382,6 +587,7 @@
382 return faults.Fault(exc.HTTPUnprocessableEntity())587 return faults.Fault(exc.HTTPUnprocessableEntity())
383 return exc.HTTPAccepted()588 return exc.HTTPAccepted()
384589
590 @scheduler_api.redirect_handler
385 def resume(self, req, id):591 def resume(self, req, id):
386 """permit admins to resume the server from suspend"""592 """permit admins to resume the server from suspend"""
387 context = req.environ['nova.context']593 context = req.environ['nova.context']
@@ -393,28 +599,56 @@
393 return faults.Fault(exc.HTTPUnprocessableEntity())599 return faults.Fault(exc.HTTPUnprocessableEntity())
394 return exc.HTTPAccepted()600 return exc.HTTPAccepted()
395601
396 def rescue(self, req, id):602<<<<<<< TREE
397 """Permit users to rescue the server."""603 def rescue(self, req, id):
398 context = req.environ["nova.context"]604 """Permit users to rescue the server."""
399 try:605 context = req.environ["nova.context"]
400 self.compute_api.rescue(context, id)606 try:
401 except:607 self.compute_api.rescue(context, id)
402 readable = traceback.format_exc()608 except:
403 LOG.exception(_("compute.api::rescue %s"), readable)609 readable = traceback.format_exc()
404 return faults.Fault(exc.HTTPUnprocessableEntity())610 LOG.exception(_("compute.api::rescue %s"), readable)
405 return exc.HTTPAccepted()611 return faults.Fault(exc.HTTPUnprocessableEntity())
406612 return exc.HTTPAccepted()
407 def unrescue(self, req, id):613
408 """Permit users to unrescue the server."""614 def unrescue(self, req, id):
409 context = req.environ["nova.context"]615 """Permit users to unrescue the server."""
410 try:616 context = req.environ["nova.context"]
411 self.compute_api.unrescue(context, id)617 try:
412 except:618 self.compute_api.unrescue(context, id)
413 readable = traceback.format_exc()619 except:
414 LOG.exception(_("compute.api::unrescue %s"), readable)620 readable = traceback.format_exc()
415 return faults.Fault(exc.HTTPUnprocessableEntity())621 LOG.exception(_("compute.api::unrescue %s"), readable)
416 return exc.HTTPAccepted()622 return faults.Fault(exc.HTTPUnprocessableEntity())
417623 return exc.HTTPAccepted()
624
625=======
626 @scheduler_api.redirect_handler
627 def rescue(self, req, id):
628 """Permit users to rescue the server."""
629 context = req.environ["nova.context"]
630 try:
631 self.compute_api.rescue(context, id)
632 except:
633 readable = traceback.format_exc()
634 LOG.exception(_("compute.api::rescue %s"), readable)
635 return faults.Fault(exc.HTTPUnprocessableEntity())
636 return exc.HTTPAccepted()
637
638 @scheduler_api.redirect_handler
639 def unrescue(self, req, id):
640 """Permit users to unrescue the server."""
641 context = req.environ["nova.context"]
642 try:
643 self.compute_api.unrescue(context, id)
644 except:
645 readable = traceback.format_exc()
646 LOG.exception(_("compute.api::unrescue %s"), readable)
647 return faults.Fault(exc.HTTPUnprocessableEntity())
648 return exc.HTTPAccepted()
649
650 @scheduler_api.redirect_handler
651>>>>>>> MERGE-SOURCE
418 def get_ajax_console(self, req, id):652 def get_ajax_console(self, req, id):
419 """ Returns a url to an instance's ajaxterm console. """653 """ Returns a url to an instance's ajaxterm console. """
420 try:654 try:
@@ -424,6 +658,7 @@
424 return faults.Fault(exc.HTTPNotFound())658 return faults.Fault(exc.HTTPNotFound())
425 return exc.HTTPAccepted()659 return exc.HTTPAccepted()
426660
661 @scheduler_api.redirect_handler
427 def diagnostics(self, req, id):662 def diagnostics(self, req, id):
428 """Permit Admins to retrieve server diagnostics."""663 """Permit Admins to retrieve server diagnostics."""
429 ctxt = req.environ["nova.context"]664 ctxt = req.environ["nova.context"]
@@ -442,37 +677,195 @@
442 action=item.action,677 action=item.action,
443 error=item.error))678 error=item.error))
444 return dict(actions=actions)679 return dict(actions=actions)
445680<<<<<<< TREE
446 def _get_kernel_ramdisk_from_image(self, req, image_id):681
447 """Retrevies kernel and ramdisk IDs from Glance682 def _get_kernel_ramdisk_from_image(self, req, image_id):
448683 """Retrevies kernel and ramdisk IDs from Glance
449 Only 'machine' (ami) type use kernel and ramdisk outside of the684
450 image.685 Only 'machine' (ami) type use kernel and ramdisk outside of the
451 """686 image.
452 # FIXME(sirp): Since we're retrieving the kernel_id from an687 """
453 # image_property, this means only Glance is supported.688 # FIXME(sirp): Since we're retrieving the kernel_id from an
454 # The BaseImageService needs to expose a consistent way of accessing689 # image_property, this means only Glance is supported.
455 # kernel_id and ramdisk_id690 # The BaseImageService needs to expose a consistent way of accessing
456 image = self._image_service.show(req.environ['nova.context'], image_id)691 # kernel_id and ramdisk_id
457692 image = self._image_service.show(req.environ['nova.context'], image_id)
458 if image['status'] != 'active':693
459 raise exception.Invalid(694 if image['status'] != 'active':
460 _("Cannot build from image %(image_id)s, status not active") %695 raise exception.Invalid(
461 locals())696 _("Cannot build from image %(image_id)s, status not active") %
462697 locals())
463 if image['disk_format'] != 'ami':698
464 return None, None699 if image['disk_format'] != 'ami':
465700 return None, None
466 try:701
467 kernel_id = image['properties']['kernel_id']702 try:
468 except KeyError:703 kernel_id = image['properties']['kernel_id']
469 raise exception.NotFound(704 except KeyError:
470 _("Kernel not found for image %(image_id)s") % locals())705 raise exception.NotFound(
471706 _("Kernel not found for image %(image_id)s") % locals())
472 try:707
473 ramdisk_id = image['properties']['ramdisk_id']708 try:
474 except KeyError:709 ramdisk_id = image['properties']['ramdisk_id']
475 raise exception.NotFound(710 except KeyError:
476 _("Ramdisk not found for image %(image_id)s") % locals())711 raise exception.NotFound(
477712 _("Ramdisk not found for image %(image_id)s") % locals())
478 return kernel_id, ramdisk_id713
714 return kernel_id, ramdisk_id
715=======
716
717 def _get_kernel_ramdisk_from_image(self, req, image_id):
718 """Retrevies kernel and ramdisk IDs from Glance
719
720 Only 'machine' (ami) type use kernel and ramdisk outside of the
721 image.
722 """
723 # FIXME(sirp): Since we're retrieving the kernel_id from an
724 # image_property, this means only Glance is supported.
725 # The BaseImageService needs to expose a consistent way of accessing
726 # kernel_id and ramdisk_id
727 image = self._image_service.show(req.environ['nova.context'], image_id)
728
729 if image['status'] != 'active':
730 raise exception.Invalid(
731 _("Cannot build from image %(image_id)s, status not active") %
732 locals())
733
734 if image['disk_format'] != 'ami':
735 return None, None
736
737 try:
738 kernel_id = image['properties']['kernel_id']
739 except KeyError:
740 raise exception.NotFound(
741 _("Kernel not found for image %(image_id)s") % locals())
742
743 try:
744 ramdisk_id = image['properties']['ramdisk_id']
745 except KeyError:
746 raise exception.NotFound(
747 _("Ramdisk not found for image %(image_id)s") % locals())
748
749 return kernel_id, ramdisk_id
750
751
752class ControllerV10(Controller):
753 def _image_id_from_req_data(self, data):
754 return data['server']['imageId']
755
756 def _flavor_id_from_req_data(self, data):
757 return data['server']['flavorId']
758
759 def _get_view_builder(self, req):
760 addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV10()
761 return nova.api.openstack.views.servers.ViewBuilderV10(
762 addresses_builder)
763
764 def _get_addresses_view_builder(self, req):
765 return nova.api.openstack.views.addresses.ViewBuilderV10(req)
766
767 def _limit_items(self, items, req):
768 return common.limited(items, req)
769
770
771class ControllerV11(Controller):
772 def _image_id_from_req_data(self, data):
773 href = data['server']['imageRef']
774 return common.get_id_from_href(href)
775
776 def _flavor_id_from_req_data(self, data):
777 href = data['server']['flavorRef']
778 return common.get_id_from_href(href)
779
780 def _get_view_builder(self, req):
781 base_url = req.application_url
782 flavor_builder = nova.api.openstack.views.flavors.ViewBuilderV11(
783 base_url)
784 image_builder = nova.api.openstack.views.images.ViewBuilderV11(
785 base_url)
786 addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV11()
787 return nova.api.openstack.views.servers.ViewBuilderV11(
788 addresses_builder, flavor_builder, image_builder)
789
790 def _get_addresses_view_builder(self, req):
791 return nova.api.openstack.views.addresses.ViewBuilderV11(req)
792
793 def _limit_items(self, items, req):
794 return common.limited_by_marker(items, req)
795
796
797class ServerCreateRequestXMLDeserializer(object):
798 """
799 Deserializer to handle xml-formatted server create requests.
800
801 Handles standard server attributes as well as optional metadata
802 and personality attributes
803 """
804
805 def deserialize(self, string):
806 """Deserialize an xml-formatted server create request"""
807 dom = minidom.parseString(string)
808 server = self._extract_server(dom)
809 return {'server': server}
810
811 def _extract_server(self, node):
812 """Marshal the server attribute of a parsed request"""
813 server = {}
814 server_node = self._find_first_child_named(node, 'server')
815 for attr in ["name", "imageId", "flavorId"]:
816 server[attr] = server_node.getAttribute(attr)
817 metadata = self._extract_metadata(server_node)
818 if metadata is not None:
819 server["metadata"] = metadata
820 personality = self._extract_personality(server_node)
821 if personality is not None:
822 server["personality"] = personality
823 return server
824
825 def _extract_metadata(self, server_node):
826 """Marshal the metadata attribute of a parsed request"""
827 metadata_node = self._find_first_child_named(server_node, "metadata")
828 if metadata_node is None:
829 return None
830 metadata = {}
831 for meta_node in self._find_children_named(metadata_node, "meta"):
832 key = meta_node.getAttribute("key")
833 metadata[key] = self._extract_text(meta_node)
834 return metadata
835
836 def _extract_personality(self, server_node):
837 """Marshal the personality attribute of a parsed request"""
838 personality_node = \
839 self._find_first_child_named(server_node, "personality")
840 if personality_node is None:
841 return None
842 personality = []
843 for file_node in self._find_children_named(personality_node, "file"):
844 item = {}
845 if file_node.hasAttribute("path"):
846 item["path"] = file_node.getAttribute("path")
847 item["contents"] = self._extract_text(file_node)
848 personality.append(item)
849 return personality
850
851 def _find_first_child_named(self, parent, name):
852 """Search a nodes children for the first child with a given name"""
853 for node in parent.childNodes:
854 if node.nodeName == name:
855 return node
856 return None
857
858 def _find_children_named(self, parent, name):
859 """Return all of a nodes children who have the given name"""
860 for node in parent.childNodes:
861 if node.nodeName == name:
862 yield node
863
864 def _extract_text(self, node):
865 """Get the text field contained by the given node"""
866 if len(node.childNodes) == 1:
867 child = node.childNodes[0]
868 if child.nodeType == child.TEXT_NODE:
869 return child.nodeValue
870 return ""
871>>>>>>> MERGE-SOURCE
479872
=== modified file 'nova/api/openstack/users.py'
--- nova/api/openstack/users.py 2011-03-11 19:49:32 +0000
+++ nova/api/openstack/users.py 2011-03-25 13:43:55 +0000
@@ -1,3 +1,4 @@
1<<<<<<< TREE
1# Copyright 2011 OpenStack LLC.2# Copyright 2011 OpenStack LLC.
2# All Rights Reserved.3# All Rights Reserved.
3#4#
@@ -91,3 +92,109 @@
91 secret = env['user'].get('secret')92 secret = env['user'].get('secret')
92 self.manager.modify_user(id, access, secret, is_admin)93 self.manager.modify_user(id, access, secret, is_admin)
93 return dict(user=_translate_keys(self.manager.get_user(id)))94 return dict(user=_translate_keys(self.manager.get_user(id)))
95=======
96# Copyright 2011 OpenStack LLC.
97# All Rights Reserved.
98#
99# Licensed under the Apache License, Version 2.0 (the "License"); you may
100# not use this file except in compliance with the License. You may obtain
101# a copy of the License at
102#
103# http://www.apache.org/licenses/LICENSE-2.0
104#
105# Unless required by applicable law or agreed to in writing, software
106# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
107# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
108# License for the specific language governing permissions and limitations
109# under the License.
110
111from webob import exc
112
113from nova import exception
114from nova import flags
115from nova import log as logging
116from nova import wsgi
117from nova.api.openstack import common
118from nova.api.openstack import faults
119from nova.auth import manager
120
121FLAGS = flags.FLAGS
122LOG = logging.getLogger('nova.api.openstack')
123
124
125def _translate_keys(user):
126 return dict(id=user.id,
127 name=user.name,
128 access=user.access,
129 secret=user.secret,
130 admin=user.admin)
131
132
133class Controller(wsgi.Controller):
134
135 _serialization_metadata = {
136 'application/xml': {
137 "attributes": {
138 "user": ["id", "name", "access", "secret", "admin"]}}}
139
140 def __init__(self):
141 self.manager = manager.AuthManager()
142
143 def _check_admin(self, context):
144 """We cannot depend on the db layer to check for admin access
145 for the auth manager, so we do it here"""
146 if not context.is_admin:
147 raise exception.NotAuthorized(_("Not admin user"))
148
149 def index(self, req):
150 """Return all users in brief"""
151 users = self.manager.get_users()
152 users = common.limited(users, req)
153 users = [_translate_keys(user) for user in users]
154 return dict(users=users)
155
156 def detail(self, req):
157 """Return all users in detail"""
158 return self.index(req)
159
160 def show(self, req, id):
161 """Return data about the given user id"""
162
163 #NOTE(justinsb): The drivers are a little inconsistent in how they
164 # deal with "NotFound" - some throw, some return None.
165 try:
166 user = self.manager.get_user(id)
167 except exception.NotFound:
168 user = None
169
170 if user is None:
171 raise faults.Fault(exc.HTTPNotFound())
172
173 return dict(user=_translate_keys(user))
174
175 def delete(self, req, id):
176 self._check_admin(req.environ['nova.context'])
177 self.manager.delete_user(id)
178 return {}
179
180 def create(self, req):
181 self._check_admin(req.environ['nova.context'])
182 env = self._deserialize(req.body, req.get_content_type())
183 is_admin = env['user'].get('admin') in ('T', 'True', True)
184 name = env['user'].get('name')
185 access = env['user'].get('access')
186 secret = env['user'].get('secret')
187 user = self.manager.create_user(name, access, secret, is_admin)
188 return dict(user=_translate_keys(user))
189
190 def update(self, req, id):
191 self._check_admin(req.environ['nova.context'])
192 env = self._deserialize(req.body, req.get_content_type())
193 is_admin = env['user'].get('admin')
194 if is_admin is not None:
195 is_admin = is_admin in ('T', 'True', True)
196 access = env['user'].get('access')
197 secret = env['user'].get('secret')
198 self.manager.modify_user(id, access, secret, is_admin)
199 return dict(user=_translate_keys(self.manager.get_user(id)))
200>>>>>>> MERGE-SOURCE
94201
=== added directory 'nova/api/openstack/views'
=== added file 'nova/api/openstack/views/__init__.py'
=== added file 'nova/api/openstack/views/addresses.py'
--- nova/api/openstack/views/addresses.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/views/addresses.py 2011-03-25 13:43:55 +0000
@@ -0,0 +1,42 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010-2011 OpenStack LLC.
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
18from nova import utils
19from nova.api.openstack import common
20
21
22class ViewBuilder(object):
23 ''' Models a server addresses response as a python dictionary.'''
24
25 def build(self, inst):
26 raise NotImplementedError()
27
28
29class ViewBuilderV10(ViewBuilder):
30 def build(self, inst):
31 private_ips = utils.get_from_path(inst, 'fixed_ip/address')
32 public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
33 return dict(public=public_ips, private=private_ips)
34
35
36class ViewBuilderV11(ViewBuilder):
37 def build(self, inst):
38 private_ips = utils.get_from_path(inst, 'fixed_ip/address')
39 private_ips = [dict(version=4, addr=a) for a in private_ips]
40 public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
41 public_ips = [dict(version=4, addr=a) for a in public_ips]
42 return dict(public=public_ips, private=private_ips)
043
=== added file 'nova/api/openstack/views/flavors.py'
--- nova/api/openstack/views/flavors.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/views/flavors.py 2011-03-25 13:43:55 +0000
@@ -0,0 +1,34 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010-2011 OpenStack LLC.
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
18from nova.api.openstack import common
19
20
21class ViewBuilder(object):
22 def __init__(self):
23 pass
24
25 def build(self, flavor_obj):
26 raise NotImplementedError()
27
28
29class ViewBuilderV11(ViewBuilder):
30 def __init__(self, base_url):
31 self.base_url = base_url
32
33 def generate_href(self, flavor_id):
34 return "%s/flavors/%s" % (self.base_url, flavor_id)
035
=== added file 'nova/api/openstack/views/images.py'
--- nova/api/openstack/views/images.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/views/images.py 2011-03-25 13:43:55 +0000
@@ -0,0 +1,34 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010-2011 OpenStack LLC.
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
18from nova.api.openstack import common
19
20
21class ViewBuilder(object):
22 def __init__(self):
23 pass
24
25 def build(self, image_obj):
26 raise NotImplementedError()
27
28
29class ViewBuilderV11(ViewBuilder):
30 def __init__(self, base_url):
31 self.base_url = base_url
32
33 def generate_href(self, image_id):
34 return "%s/images/%s" % (self.base_url, image_id)
035
=== added file 'nova/api/openstack/views/servers.py'
--- nova/api/openstack/views/servers.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/views/servers.py 2011-03-25 13:43:55 +0000
@@ -0,0 +1,125 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010-2011 OpenStack LLC.
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
18import hashlib
19
20from nova.compute import power_state
21import nova.compute
22import nova.context
23from nova.api.openstack import common
24from nova.api.openstack.views import addresses as addresses_view
25from nova.api.openstack.views import flavors as flavors_view
26from nova.api.openstack.views import images as images_view
27from nova import utils
28
29
30class ViewBuilder(object):
31 """Model a server response as a python dictionary.
32
33 Public methods: build
34 Abstract methods: _build_image, _build_flavor
35
36 """
37
38 def __init__(self, addresses_builder):
39 self.addresses_builder = addresses_builder
40
41 def build(self, inst, is_detail):
42 """Return a dict that represenst a server."""
43 if is_detail:
44 return self._build_detail(inst)
45 else:
46 return self._build_simple(inst)
47
48 def _build_simple(self, inst):
49 """Return a simple model of a server."""
50 return dict(server=dict(id=inst['id'], name=inst['display_name']))
51
52 def _build_detail(self, inst):
53 """Returns a detailed model of a server."""
54 power_mapping = {
55 None: 'build',
56 power_state.NOSTATE: 'build',
57 power_state.RUNNING: 'active',
58 power_state.BLOCKED: 'active',
59 power_state.SUSPENDED: 'suspended',
60 power_state.PAUSED: 'paused',
61 power_state.SHUTDOWN: 'active',
62 power_state.SHUTOFF: 'active',
63 power_state.CRASHED: 'error',
64 power_state.FAILED: 'error'}
65
66 inst_dict = {
67 'id': int(inst['id']),
68 'name': inst['display_name'],
69 'addresses': self.addresses_builder.build(inst),
70 'status': power_mapping[inst.get('state')]}
71
72 ctxt = nova.context.get_admin_context()
73 compute_api = nova.compute.API()
74 if compute_api.has_finished_migration(ctxt, inst['id']):
75 inst_dict['status'] = 'resize-confirm'
76
77 # Return the metadata as a dictionary
78 metadata = {}
79 for item in inst.get('metadata', []):
80 metadata[item['key']] = item['value']
81 inst_dict['metadata'] = metadata
82
83 inst_dict['hostId'] = ''
84 if inst.get('host'):
85 inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest()
86
87 self._build_image(inst_dict, inst)
88 self._build_flavor(inst_dict, inst)
89
90 return dict(server=inst_dict)
91
92 def _build_image(self, response, inst):
93 """Return the image sub-resource of a server."""
94 raise NotImplementedError()
95
96 def _build_flavor(self, response, inst):
97 """Return the flavor sub-resource of a server."""
98 raise NotImplementedError()
99
100
101class ViewBuilderV10(ViewBuilder):
102 """Model an Openstack API V1.0 server response."""
103
104 def _build_image(self, response, inst):
105 response['imageId'] = inst['image_id']
106
107 def _build_flavor(self, response, inst):
108 response['flavorId'] = inst['instance_type']
109
110
111class ViewBuilderV11(ViewBuilder):
112 """Model an Openstack API V1.0 server response."""
113
114 def __init__(self, addresses_builder, flavor_builder, image_builder):
115 ViewBuilder.__init__(self, addresses_builder)
116 self.flavor_builder = flavor_builder
117 self.image_builder = image_builder
118
119 def _build_image(self, response, inst):
120 image_id = inst["image_id"]
121 response["imageRef"] = self.image_builder.generate_href(image_id)
122
123 def _build_flavor(self, response, inst):
124 flavor_id = inst["instance_type"]
125 response["flavorRef"] = self.flavor_builder.generate_href(flavor_id)
0126
=== modified file 'nova/api/openstack/zones.py'
--- nova/api/openstack/zones.py 2011-03-11 19:49:32 +0000
+++ nova/api/openstack/zones.py 2011-03-25 13:43:55 +0000
@@ -1,3 +1,4 @@
1<<<<<<< TREE
1# Copyright 2011 OpenStack LLC.2# Copyright 2011 OpenStack LLC.
2# All Rights Reserved.3# All Rights Reserved.
3#4#
@@ -93,3 +94,106 @@
93 zone_id = int(id)94 zone_id = int(id)
94 zone = db.zone_update(context, zone_id, env["zone"])95 zone = db.zone_update(context, zone_id, env["zone"])
95 return dict(zone=_scrub_zone(zone))96 return dict(zone=_scrub_zone(zone))
97=======
98# Copyright 2011 OpenStack LLC.
99# All Rights Reserved.
100#
101# Licensed under the Apache License, Version 2.0 (the "License"); you may
102# not use this file except in compliance with the License. You may obtain
103# a copy of the License at
104#
105# http://www.apache.org/licenses/LICENSE-2.0
106#
107# Unless required by applicable law or agreed to in writing, software
108# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
109# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
110# License for the specific language governing permissions and limitations
111# under the License.
112
113import common
114
115from nova import db
116from nova import flags
117from nova import log as logging
118from nova import wsgi
119from nova.scheduler import api
120
121
122FLAGS = flags.FLAGS
123
124
125def _filter_keys(item, keys):
126 """
127 Filters all model attributes except for keys
128 item is a dict
129
130 """
131 return dict((k, v) for k, v in item.iteritems() if k in keys)
132
133
134def _exclude_keys(item, keys):
135 return dict((k, v) for k, v in item.iteritems() if k not in keys)
136
137
138def _scrub_zone(zone):
139 return _exclude_keys(zone, ('username', 'password', 'created_at',
140 'deleted', 'deleted_at', 'updated_at'))
141
142
143class Controller(wsgi.Controller):
144
145 _serialization_metadata = {
146 'application/xml': {
147 "attributes": {
148 "zone": ["id", "api_url", "name", "capabilities"]}}}
149
150 def index(self, req):
151 """Return all zones in brief"""
152 # Ask the ZoneManager in the Scheduler for most recent data,
153 # or fall-back to the database ...
154 items = api.get_zone_list(req.environ['nova.context'])
155 items = common.limited(items, req)
156 items = [_scrub_zone(item) for item in items]
157 return dict(zones=items)
158
159 def detail(self, req):
160 """Return all zones in detail"""
161 return self.index(req)
162
163 def info(self, req):
164 """Return name and capabilities for this zone."""
165 items = api.get_zone_capabilities(req.environ['nova.context'])
166
167 zone = dict(name=FLAGS.zone_name)
168 caps = FLAGS.zone_capabilities
169 for cap in caps:
170 key, value = cap.split('=')
171 zone[key] = value
172 for item, (min_value, max_value) in items.iteritems():
173 zone[item] = "%s,%s" % (min_value, max_value)
174 return dict(zone=zone)
175
176 def show(self, req, id):
177 """Return data about the given zone id"""
178 zone_id = int(id)
179 zone = api.zone_get(req.environ['nova.context'], zone_id)
180 return dict(zone=_scrub_zone(zone))
181
182 def delete(self, req, id):
183 zone_id = int(id)
184 api.zone_delete(req.environ['nova.context'], zone_id)
185 return {}
186
187 def create(self, req):
188 context = req.environ['nova.context']
189 env = self._deserialize(req.body, req.get_content_type())
190 zone = api.zone_create(context, env["zone"])
191 return dict(zone=_scrub_zone(zone))
192
193 def update(self, req, id):
194 context = req.environ['nova.context']
195 env = self._deserialize(req.body, req.get_content_type())
196 zone_id = int(id)
197 zone = api.zone_update(context, zone_id, env["zone"])
198 return dict(zone=_scrub_zone(zone))
199>>>>>>> MERGE-SOURCE
96200
=== modified file 'nova/auth/dbdriver.py'
--- nova/auth/dbdriver.py 2011-01-20 17:52:02 +0000
+++ nova/auth/dbdriver.py 2011-03-25 13:43:55 +0000
@@ -162,6 +162,8 @@
162 values['description'] = description162 values['description'] = description
163163
164 db.project_update(context.get_admin_context(), project_id, values)164 db.project_update(context.get_admin_context(), project_id, values)
165 if not self.is_in_project(manager_uid, project_id):
166 self.add_to_project(manager_uid, project_id)
165167
166 def add_to_project(self, uid, project_id):168 def add_to_project(self, uid, project_id):
167 """Add user to project"""169 """Add user to project"""
168170
=== modified file 'nova/auth/fakeldap.py'
--- nova/auth/fakeldap.py 2010-12-22 23:47:31 +0000
+++ nova/auth/fakeldap.py 2011-03-25 13:43:55 +0000
@@ -90,12 +90,12 @@
90MOD_REPLACE = 290MOD_REPLACE = 2
9191
9292
93class NO_SUCH_OBJECT(Exception): # pylint: disable-msg=C010393class NO_SUCH_OBJECT(Exception): # pylint: disable=C0103
94 """Duplicate exception class from real LDAP module."""94 """Duplicate exception class from real LDAP module."""
95 pass95 pass
9696
9797
98class OBJECT_CLASS_VIOLATION(Exception): # pylint: disable-msg=C010398class OBJECT_CLASS_VIOLATION(Exception): # pylint: disable=C0103
99 """Duplicate exception class from real LDAP module."""99 """Duplicate exception class from real LDAP module."""
100 pass100 pass
101101
@@ -268,7 +268,7 @@
268 # get the attributes from the store268 # get the attributes from the store
269 attrs = store.hgetall(key)269 attrs = store.hgetall(key)
270 # turn the values from the store into lists270 # turn the values from the store into lists
271 # pylint: disable-msg=E1103271 # pylint: disable=E1103
272 attrs = dict([(k, _from_json(v))272 attrs = dict([(k, _from_json(v))
273 for k, v in attrs.iteritems()])273 for k, v in attrs.iteritems()])
274 # filter the objects by query274 # filter the objects by query
@@ -277,12 +277,12 @@
277 attrs = dict([(k, v) for k, v in attrs.iteritems()277 attrs = dict([(k, v) for k, v in attrs.iteritems()
278 if not fields or k in fields])278 if not fields or k in fields])
279 objects.append((key[len(self.__prefix):], attrs))279 objects.append((key[len(self.__prefix):], attrs))
280 # pylint: enable-msg=E1103280 # pylint: enable=E1103
281 if objects == []:281 if objects == []:
282 raise NO_SUCH_OBJECT()282 raise NO_SUCH_OBJECT()
283 return objects283 return objects
284284
285 @property285 @property
286 def __prefix(self): # pylint: disable-msg=R0201286 def __prefix(self): # pylint: disable=R0201
287 """Get the prefix to use for all keys."""287 """Get the prefix to use for all keys."""
288 return 'ldap:'288 return 'ldap:'
289289
=== modified file 'nova/auth/ldapdriver.py'
--- nova/auth/ldapdriver.py 2011-02-16 20:03:59 +0000
+++ nova/auth/ldapdriver.py 2011-03-25 13:43:55 +0000
@@ -275,6 +275,8 @@
275 attr.append((self.ldap.MOD_REPLACE, 'description', description))275 attr.append((self.ldap.MOD_REPLACE, 'description', description))
276 dn = self.__project_to_dn(project_id)276 dn = self.__project_to_dn(project_id)
277 self.conn.modify_s(dn, attr)277 self.conn.modify_s(dn, attr)
278 if not self.is_in_project(manager_uid, project_id):
279 self.add_to_project(manager_uid, project_id)
278280
279 @sanitize281 @sanitize
280 def add_to_project(self, uid, project_id):282 def add_to_project(self, uid, project_id):
@@ -632,6 +634,6 @@
632class FakeLdapDriver(LdapDriver):634class FakeLdapDriver(LdapDriver):
633 """Fake Ldap Auth driver"""635 """Fake Ldap Auth driver"""
634636
635 def __init__(self): # pylint: disable-msg=W0231637 def __init__(self): # pylint: disable=W0231
636 __import__('nova.auth.fakeldap')638 __import__('nova.auth.fakeldap')
637 self.ldap = sys.modules['nova.auth.fakeldap']639 self.ldap = sys.modules['nova.auth.fakeldap']
638640
=== modified file 'nova/auth/manager.py'
--- nova/auth/manager.py 2011-01-19 02:00:28 +0000
+++ nova/auth/manager.py 2011-03-25 13:43:55 +0000
@@ -22,7 +22,7 @@
2222
23import os23import os
24import shutil24import shutil
25import string # pylint: disable-msg=W040225import string # pylint: disable=W0402
26import tempfile26import tempfile
27import uuid27import uuid
28import zipfile28import zipfile
@@ -96,10 +96,19 @@
9696
9797
98class User(AuthBase):98class User(AuthBase):
99 """Object representing a user"""99 """Object representing a user
100
101 The following attributes are defined:
102 :id: A system identifier for the user. A string (for LDAP)
103 :name: The user name, potentially in some more friendly format
104 :access: The 'username' for EC2 authentication
105 :secret: The 'password' for EC2 authenticatoin
106 :admin: ???
107 """
100108
101 def __init__(self, id, name, access, secret, admin):109 def __init__(self, id, name, access, secret, admin):
102 AuthBase.__init__(self)110 AuthBase.__init__(self)
111 assert isinstance(id, basestring)
103 self.id = id112 self.id = id
104 self.name = name113 self.name = name
105 self.access = access114 self.access = access
106115
=== modified file 'nova/compute/api.py'
--- nova/compute/api.py 2011-03-10 17:30:26 +0000
+++ nova/compute/api.py 2011-03-25 13:43:55 +0000
@@ -34,6 +34,7 @@
34from nova import utils34from nova import utils
35from nova import volume35from nova import volume
36from nova.compute import instance_types36from nova.compute import instance_types
37from nova.scheduler import api as scheduler_api
37from nova.db import base38from nova.db import base
3839
39FLAGS = flags.FLAGS40FLAGS = flags.FLAGS
@@ -80,13 +81,37 @@
80 topic,81 topic,
81 {"method": "get_network_topic", "args": {'fake': 1}})82 {"method": "get_network_topic", "args": {'fake': 1}})
8283
84 def _check_injected_file_quota(self, context, injected_files):
85 """
86 Enforce quota limits on injected files
87
88 Raises a QuotaError if any limit is exceeded
89 """
90 if injected_files is None:
91 return
92 limit = quota.allowed_injected_files(context)
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches