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