Merge lp:~midokura/nova/network-service into lp:~ntt-pf-lab/nova/network-service
- network-service
- Merge into network-service
Proposed by
Ryu Ishimoto
Status: | Merged |
---|---|
Merged at revision: | 773 |
Proposed branch: | lp:~midokura/nova/network-service |
Merge into: | lp:~ntt-pf-lab/nova/network-service |
Diff against target: |
64148 lines (+34518/-21481) 129 files modified
.bzrignore (+6/-7) Authors (+3/-0) MANIFEST.in (+1/-1) bin/nova-ajax-console-proxy (+10/-11) bin/nova-dhcpbridge (+3/-1) bin/nova-manage (+72/-80) bin/nova-vncproxy (+101/-0) doc/source/devref/zone.rst (+127/-0) doc/source/man/novamanage.rst (+10/-0) doc/source/runnova/vncconsole.rst (+76/-0) nova/CA/geninter.sh (+1/-1) nova/CA/genrootca.sh (+2/-1) nova/CA/openssl.cnf.tmpl (+5/-1) nova/adminclient.py (+0/-476) nova/api/direct.py (+5/-1) nova/api/ec2/cloud.py (+80/-27) nova/api/openstack/__init__.py (+23/-12) nova/api/openstack/accounts.py (+2/-3) nova/api/openstack/backup_schedules.py (+7/-3) nova/api/openstack/common.py (+17/-0) nova/api/openstack/consoles.py (+2/-2) nova/api/openstack/contrib/__init__.py (+22/-0) nova/api/openstack/contrib/volumes.py (+336/-0) nova/api/openstack/extensions.py (+156/-73) nova/api/openstack/faults.py (+5/-3) nova/api/openstack/flavors.py (+5/-2) nova/api/openstack/image_metadata.py (+14/-1) nova/api/openstack/images.py (+106/-213) nova/api/openstack/ips.py (+72/-0) nova/api/openstack/limits.py (+2/-2) nova/api/openstack/server_metadata.py (+2/-1) nova/api/openstack/servers.py (+88/-29) nova/api/openstack/shared_ip_groups.py (+5/-5) nova/api/openstack/users.py (+1/-2) nova/api/openstack/versions.py (+8/-2) nova/api/openstack/views/addresses.py (+8/-2) nova/api/openstack/views/images.py (+91/-11) nova/api/openstack/views/servers.py (+14/-14) nova/api/openstack/zones.py (+2/-4) nova/compute/api.py (+39/-10) nova/compute/instance_types.py (+37/-35) nova/compute/manager.py (+49/-16) nova/crypto.py (+10/-2) nova/db/api.py (+5/-0) nova/db/sqlalchemy/api.py (+25/-1) nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py (+84/-0) nova/db/sqlalchemy/migrate_repo/versions/014_diablo.py (+0/-92) nova/db/sqlalchemy/migrate_repo/versions/015_diablo.py (+93/-0) nova/db/sqlalchemy/models.py (+7/-1) nova/image/fake.py (+113/-0) nova/image/glance.py (+15/-33) nova/image/local.py (+13/-3) nova/image/s3.py (+8/-37) nova/image/service.py (+27/-0) nova/network/api.py (+15/-0) nova/network/linux_net.py (+10/-1) nova/network/manager.py (+1/-0) nova/network/xenapi_net.py (+85/-0) nova/rpc.py (+8/-3) nova/scheduler/chance.py (+3/-1) nova/scheduler/simple.py (+9/-3) nova/scheduler/zone.py (+4/-1) nova/tests/api/openstack/extensions/__init__.py (+15/-0) nova/tests/api/openstack/test_api.py (+4/-4) nova/tests/api/openstack/test_faults.py (+106/-23) nova/tests/api/openstack/test_image_metadata.py (+39/-2) nova/tests/api/openstack/test_images.py (+463/-37) nova/tests/api/openstack/test_limits.py (+13/-5) nova/tests/api/openstack/test_servers.py (+371/-66) nova/tests/api/openstack/test_shared_ip_groups.py (+27/-3) nova/tests/api/openstack/test_versions.py (+26/-0) nova/tests/db/fakes.py (+61/-26) nova/tests/fake_utils.py (+10/-7) nova/tests/image/test_glance.py (+54/-9) nova/tests/integrated/api/client.py (+37/-3) nova/tests/integrated/integrated_helpers.py (+99/-24) nova/tests/integrated/test_extensions.py (+44/-0) nova/tests/integrated/test_login.py (+5/-16) nova/tests/integrated/test_servers.py (+184/-0) nova/tests/integrated/test_volumes.py (+295/-0) nova/tests/integrated/test_xml.py (+56/-0) nova/tests/test_auth.py (+4/-4) nova/tests/test_cloud.py (+77/-1) nova/tests/test_compute.py (+22/-9) nova/tests/test_console.py (+1/-1) nova/tests/test_instance_types.py (+5/-1) nova/tests/test_quota.py (+11/-6) nova/tests/test_scheduler.py (+1/-1) nova/tests/test_virt.py (+46/-2) nova/tests/test_volume.py (+1/-1) nova/tests/test_xenapi.py (+60/-51) nova/virt/disk.py (+35/-0) nova/virt/driver.py (+18/-9) nova/virt/fake.py (+15/-10) nova/virt/hyperv.py (+4/-0) nova/virt/libvirt.xml.template (+24/-10) nova/virt/libvirt_conn.py (+292/-70) nova/virt/vmwareapi/vim.py (+29/-25) nova/virt/vmwareapi_conn.py (+2/-1) nova/virt/xenapi/fake.py (+107/-65) nova/virt/xenapi/network_utils.py (+17/-2) nova/virt/xenapi/vm_utils.py (+29/-8) nova/virt/xenapi/vmops.py (+142/-94) nova/virt/xenapi_conn.py (+8/-1) nova/vnc/__init__.py (+34/-0) nova/vnc/auth.py (+138/-0) nova/vnc/proxy.py (+131/-0) nova/volume/driver.py (+75/-2) nova/wsgi.py (+42/-5) plugins/xenserver/xenapi/etc/xapi.d/plugins/agent (+77/-6) plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py (+19/-16) po/ast.po (+2487/-1769) po/cs.po (+2508/-1777) po/da.po (+2488/-1770) po/de.po (+2531/-1779) po/es.po (+3000/-1820) po/it.po (+2541/-1784) po/ja.po (+2989/-1791) po/pt_BR.po (+2677/-1798) po/ru.po (+2608/-1779) po/uk.po (+2522/-1775) po/zh_CN.po (+2585/-1780) setup.py (+16/-0) smoketests/test_admin.py (+1/-1) smoketests/test_sysadmin.py (+3/-2) tools/euca-get-ajax-console (+6/-0) tools/eventlet-patch (+24/-0) tools/install_venv.py (+6/-0) tools/pip-requires (+1/-0) |
To merge this branch: | bzr merge lp:~midokura/nova/network-service |
Related bugs: | |
Related blueprints: |
Refactor Networking
(Essential)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
NTT PF Lab. | Pending | ||
Review via email: mp+57303@code.launchpad.net |
Commit message
Description of the change
Merged trunk
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '.bzrignore' | |||
2 | --- .bzrignore 2011-02-21 20:35:30 +0000 | |||
3 | +++ .bzrignore 2011-04-12 11:01:25 +0000 | |||
4 | @@ -5,12 +5,11 @@ | |||
5 | 5 | keys | 5 | keys |
6 | 6 | networks | 6 | networks |
7 | 7 | nova.sqlite | 7 | nova.sqlite |
15 | 8 | CA/cacert.pem | 8 | CA |
9 | 9 | CA/crl.pem | ||
10 | 10 | CA/index.txt* | ||
11 | 11 | CA/openssl.cnf | ||
12 | 12 | CA/serial* | ||
13 | 13 | CA/newcerts/*.pem | ||
14 | 14 | CA/private/cakey.pem | ||
16 | 15 | nova/vcsversion.py | 9 | nova/vcsversion.py |
17 | 16 | *.DS_Store | 10 | *.DS_Store |
18 | 11 | .project | ||
19 | 12 | .pydevproject | ||
20 | 13 | clean.sqlite | ||
21 | 14 | run_tests.log | ||
22 | 15 | tests.sqlite | ||
23 | 17 | 16 | ||
24 | === modified file 'Authors' | |||
25 | --- Authors 2011-03-25 13:38:57 +0000 | |||
26 | +++ Authors 2011-04-12 11:01:25 +0000 | |||
27 | @@ -12,6 +12,7 @@ | |||
28 | 12 | Chmouel Boudjnah <chmouel@chmouel.com> | 12 | Chmouel Boudjnah <chmouel@chmouel.com> |
29 | 13 | Chris Behrens <cbehrens@codestud.com> | 13 | Chris Behrens <cbehrens@codestud.com> |
30 | 14 | Christian Berendt <berendt@b1-systems.de> | 14 | Christian Berendt <berendt@b1-systems.de> |
31 | 15 | Chuck Short <zulcss@ubuntu.com> | ||
32 | 15 | Cory Wright <corywright@gmail.com> | 16 | Cory Wright <corywright@gmail.com> |
33 | 16 | Dan Prince <dan.prince@rackspace.com> | 17 | Dan Prince <dan.prince@rackspace.com> |
34 | 17 | David Pravec <David.Pravec@danix.org> | 18 | David Pravec <David.Pravec@danix.org> |
35 | @@ -30,7 +31,9 @@ | |||
36 | 30 | Jesse Andrews <anotherjesse@gmail.com> | 31 | Jesse Andrews <anotherjesse@gmail.com> |
37 | 31 | Joe Heck <heckj@mac.com> | 32 | Joe Heck <heckj@mac.com> |
38 | 32 | Joel Moore <joelbm24@gmail.com> | 33 | Joel Moore <joelbm24@gmail.com> |
39 | 34 | Johannes Erdfelt <johannes.erdfelt@rackspace.com> | ||
40 | 33 | John Dewey <john@dewey.ws> | 35 | John Dewey <john@dewey.ws> |
41 | 36 | John Tran <jtran@attinteractive.com> | ||
42 | 34 | Jonathan Bryce <jbryce@jbryce.com> | 37 | Jonathan Bryce <jbryce@jbryce.com> |
43 | 35 | Jordan Rinke <jordan@openstack.org> | 38 | Jordan Rinke <jordan@openstack.org> |
44 | 36 | Josh Durgin <joshd@hq.newdream.net> | 39 | Josh Durgin <joshd@hq.newdream.net> |
45 | 37 | 40 | ||
46 | === modified file 'MANIFEST.in' | |||
47 | --- MANIFEST.in 2011-03-14 20:10:11 +0000 | |||
48 | +++ MANIFEST.in 2011-04-12 11:01:25 +0000 | |||
49 | @@ -1,7 +1,7 @@ | |||
50 | 1 | include HACKING LICENSE run_tests.py run_tests.sh | 1 | include HACKING LICENSE run_tests.py run_tests.sh |
51 | 2 | include README builddeb.sh exercise_rsapi.py | 2 | include README builddeb.sh exercise_rsapi.py |
52 | 3 | include ChangeLog MANIFEST.in pylintrc Authors | 3 | include ChangeLog MANIFEST.in pylintrc Authors |
54 | 4 | graft CA | 4 | graft nova/CA |
55 | 5 | graft doc | 5 | graft doc |
56 | 6 | graft smoketests | 6 | graft smoketests |
57 | 7 | graft tools | 7 | graft tools |
58 | 8 | 8 | ||
59 | === modified file 'bin/nova-ajax-console-proxy' | |||
60 | --- bin/nova-ajax-console-proxy 2011-03-18 13:56:05 +0000 | |||
61 | +++ bin/nova-ajax-console-proxy 2011-04-12 11:01:25 +0000 | |||
62 | @@ -108,17 +108,17 @@ | |||
63 | 108 | return "Server Error" | 108 | return "Server Error" |
64 | 109 | 109 | ||
65 | 110 | def register_listeners(self): | 110 | def register_listeners(self): |
71 | 111 | class Callback: | 111 | class TopicProxy(): |
72 | 112 | def __call__(self, data, message): | 112 | @staticmethod |
73 | 113 | if data['method'] == 'authorize_ajax_console': | 113 | def authorize_ajax_console(context, **kwargs): |
74 | 114 | AjaxConsoleProxy.tokens[data['args']['token']] = \ | 114 | AjaxConsoleProxy.tokens[kwargs['token']] = \ |
75 | 115 | {'args': data['args'], 'last_activity': time.time()} | 115 | {'args': kwargs, 'last_activity': time.time()} |
76 | 116 | 116 | ||
77 | 117 | conn = rpc.Connection.instance(new=True) | 117 | conn = rpc.Connection.instance(new=True) |
82 | 118 | consumer = rpc.TopicConsumer( | 118 | consumer = rpc.TopicAdapterConsumer( |
83 | 119 | connection=conn, | 119 | connection=conn, |
84 | 120 | topic=FLAGS.ajax_console_proxy_topic) | 120 | proxy=TopicProxy, |
85 | 121 | consumer.register_callback(Callback()) | 121 | topic=FLAGS.ajax_console_proxy_topic) |
86 | 122 | 122 | ||
87 | 123 | def delete_expired_tokens(): | 123 | def delete_expired_tokens(): |
88 | 124 | now = time.time() | 124 | now = time.time() |
89 | @@ -130,8 +130,7 @@ | |||
90 | 130 | for k in to_delete: | 130 | for k in to_delete: |
91 | 131 | del AjaxConsoleProxy.tokens[k] | 131 | del AjaxConsoleProxy.tokens[k] |
92 | 132 | 132 | ||
95 | 133 | utils.LoopingCall(consumer.fetch, auto_ack=True, | 133 | utils.LoopingCall(consumer.fetch, enable_callbacks=True).start(0.1) |
94 | 134 | enable_callbacks=True).start(0.1) | ||
96 | 135 | utils.LoopingCall(delete_expired_tokens).start(1) | 134 | utils.LoopingCall(delete_expired_tokens).start(1) |
97 | 136 | 135 | ||
98 | 137 | if __name__ == '__main__': | 136 | if __name__ == '__main__': |
99 | 138 | 137 | ||
100 | === modified file 'bin/nova-dhcpbridge' | |||
101 | --- bin/nova-dhcpbridge 2011-03-14 17:59:41 +0000 | |||
102 | +++ bin/nova-dhcpbridge 2011-04-12 11:01:25 +0000 | |||
103 | @@ -48,6 +48,7 @@ | |||
104 | 48 | flags.DECLARE('network_size', 'nova.network.manager') | 48 | flags.DECLARE('network_size', 'nova.network.manager') |
105 | 49 | flags.DECLARE('num_networks', 'nova.network.manager') | 49 | flags.DECLARE('num_networks', 'nova.network.manager') |
106 | 50 | flags.DECLARE('update_dhcp_on_disassociate', 'nova.network.manager') | 50 | flags.DECLARE('update_dhcp_on_disassociate', 'nova.network.manager') |
107 | 51 | flags.DEFINE_string('dnsmasq_interface', 'br0', 'Default Dnsmasq interface') | ||
108 | 51 | 52 | ||
109 | 52 | LOG = logging.getLogger('nova.dhcpbridge') | 53 | LOG = logging.getLogger('nova.dhcpbridge') |
110 | 53 | 54 | ||
111 | @@ -103,7 +104,8 @@ | |||
112 | 103 | utils.default_flagfile(flagfile) | 104 | utils.default_flagfile(flagfile) |
113 | 104 | argv = FLAGS(sys.argv) | 105 | argv = FLAGS(sys.argv) |
114 | 105 | logging.setup() | 106 | logging.setup() |
116 | 106 | interface = os.environ.get('DNSMASQ_INTERFACE', 'br0') | 107 | # check ENV first so we don't break any older deploys |
117 | 108 | interface = os.environ.get('DNSMASQ_INTERFACE', FLAGS.dnsmasq_interface) | ||
118 | 107 | if int(os.environ.get('TESTING', '0')): | 109 | if int(os.environ.get('TESTING', '0')): |
119 | 108 | from nova.tests import fake_flags | 110 | from nova.tests import fake_flags |
120 | 109 | action = argv[1] | 111 | action = argv[1] |
121 | 110 | 112 | ||
122 | === modified file 'bin/nova-manage' | |||
123 | --- bin/nova-manage 2011-04-07 14:25:01 +0000 | |||
124 | +++ bin/nova-manage 2011-04-12 11:01:25 +0000 | |||
125 | @@ -528,6 +528,49 @@ | |||
126 | 528 | class VmCommands(object): | 528 | class VmCommands(object): |
127 | 529 | """Class for mangaging VM instances.""" | 529 | """Class for mangaging VM instances.""" |
128 | 530 | 530 | ||
129 | 531 | def list(self, host=None): | ||
130 | 532 | """Show a list of all instances | ||
131 | 533 | |||
132 | 534 | :param host: show all instance on specified host. | ||
133 | 535 | :param instance: show specificed instance. | ||
134 | 536 | """ | ||
135 | 537 | print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \ | ||
136 | 538 | " %-10s %-10s %-10s %-5s" % ( | ||
137 | 539 | _('instance'), | ||
138 | 540 | _('node'), | ||
139 | 541 | _('type'), | ||
140 | 542 | _('state'), | ||
141 | 543 | _('launched'), | ||
142 | 544 | _('image'), | ||
143 | 545 | _('kernel'), | ||
144 | 546 | _('ramdisk'), | ||
145 | 547 | _('project'), | ||
146 | 548 | _('user'), | ||
147 | 549 | _('zone'), | ||
148 | 550 | _('index')) | ||
149 | 551 | |||
150 | 552 | if host == None: | ||
151 | 553 | instances = db.instance_get_all(context.get_admin_context()) | ||
152 | 554 | else: | ||
153 | 555 | instances = db.instance_get_all_by_host( | ||
154 | 556 | context.get_admin_context(), host) | ||
155 | 557 | |||
156 | 558 | for instance in instances: | ||
157 | 559 | print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \ | ||
158 | 560 | " %-10s %-10s %-10s %-5d" % ( | ||
159 | 561 | instance['hostname'], | ||
160 | 562 | instance['host'], | ||
161 | 563 | instance['instance_type'], | ||
162 | 564 | instance['state_description'], | ||
163 | 565 | instance['launched_at'], | ||
164 | 566 | instance['image_id'], | ||
165 | 567 | instance['kernel_id'], | ||
166 | 568 | instance['ramdisk_id'], | ||
167 | 569 | instance['project_id'], | ||
168 | 570 | instance['user_id'], | ||
169 | 571 | instance['availability_zone'], | ||
170 | 572 | instance['launch_index']) | ||
171 | 573 | |||
172 | 531 | def live_migration(self, ec2_id, dest): | 574 | def live_migration(self, ec2_id, dest): |
173 | 532 | """Migrates a running instance to a new machine. | 575 | """Migrates a running instance to a new machine. |
174 | 533 | 576 | ||
175 | @@ -659,15 +702,6 @@ | |||
176 | 659 | {"method": "update_available_resource"}) | 702 | {"method": "update_available_resource"}) |
177 | 660 | 703 | ||
178 | 661 | 704 | ||
179 | 662 | class LogCommands(object): | ||
180 | 663 | def request(self, request_id, logfile='/var/log/nova.log'): | ||
181 | 664 | """Show all fields in the log for the given request. Assumes you | ||
182 | 665 | haven't changed the log format too much. | ||
183 | 666 | ARGS: request_id [logfile]""" | ||
184 | 667 | lines = utils.execute("cat %s | grep '\[%s '" % (logfile, request_id)) | ||
185 | 668 | print re.sub('#012', "\n", "\n".join(lines)) | ||
186 | 669 | |||
187 | 670 | |||
188 | 671 | class DbCommands(object): | 705 | class DbCommands(object): |
189 | 672 | """Class for managing the database.""" | 706 | """Class for managing the database.""" |
190 | 673 | 707 | ||
191 | @@ -683,49 +717,6 @@ | |||
192 | 683 | print migration.db_version() | 717 | print migration.db_version() |
193 | 684 | 718 | ||
194 | 685 | 719 | ||
195 | 686 | class InstanceCommands(object): | ||
196 | 687 | """Class for managing instances.""" | ||
197 | 688 | |||
198 | 689 | def list(self, host=None, instance=None): | ||
199 | 690 | """Show a list of all instances""" | ||
200 | 691 | print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \ | ||
201 | 692 | " %-10s %-10s %-10s %-5s" % ( | ||
202 | 693 | _('instance'), | ||
203 | 694 | _('node'), | ||
204 | 695 | _('type'), | ||
205 | 696 | _('state'), | ||
206 | 697 | _('launched'), | ||
207 | 698 | _('image'), | ||
208 | 699 | _('kernel'), | ||
209 | 700 | _('ramdisk'), | ||
210 | 701 | _('project'), | ||
211 | 702 | _('user'), | ||
212 | 703 | _('zone'), | ||
213 | 704 | _('index')) | ||
214 | 705 | |||
215 | 706 | if host == None: | ||
216 | 707 | instances = db.instance_get_all(context.get_admin_context()) | ||
217 | 708 | else: | ||
218 | 709 | instances = db.instance_get_all_by_host( | ||
219 | 710 | context.get_admin_context(), host) | ||
220 | 711 | |||
221 | 712 | for instance in instances: | ||
222 | 713 | print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \ | ||
223 | 714 | " %-10s %-10s %-10s %-5d" % ( | ||
224 | 715 | instance['hostname'], | ||
225 | 716 | instance['host'], | ||
226 | 717 | instance['instance_type'], | ||
227 | 718 | instance['state_description'], | ||
228 | 719 | instance['launched_at'], | ||
229 | 720 | instance['image_id'], | ||
230 | 721 | instance['kernel_id'], | ||
231 | 722 | instance['ramdisk_id'], | ||
232 | 723 | instance['project_id'], | ||
233 | 724 | instance['user_id'], | ||
234 | 725 | instance['availability_zone'], | ||
235 | 726 | instance['launch_index']) | ||
236 | 727 | |||
237 | 728 | |||
238 | 729 | class VolumeCommands(object): | 720 | class VolumeCommands(object): |
239 | 730 | """Methods for dealing with a cloud in an odd state""" | 721 | """Methods for dealing with a cloud in an odd state""" |
240 | 731 | 722 | ||
241 | @@ -836,7 +827,7 @@ | |||
242 | 836 | elif name == "--all": | 827 | elif name == "--all": |
243 | 837 | inst_types = instance_types.get_all_types(True) | 828 | inst_types = instance_types.get_all_types(True) |
244 | 838 | else: | 829 | else: |
246 | 839 | inst_types = instance_types.get_instance_type(name) | 830 | inst_types = instance_types.get_instance_type_by_name(name) |
247 | 840 | except exception.DBError, e: | 831 | except exception.DBError, e: |
248 | 841 | _db_error(e) | 832 | _db_error(e) |
249 | 842 | if isinstance(inst_types.values()[0], dict): | 833 | if isinstance(inst_types.values()[0], dict): |
250 | @@ -852,20 +843,17 @@ | |||
251 | 852 | def __init__(self, *args, **kwargs): | 843 | def __init__(self, *args, **kwargs): |
252 | 853 | self.image_service = utils.import_object(FLAGS.image_service) | 844 | self.image_service = utils.import_object(FLAGS.image_service) |
253 | 854 | 845 | ||
255 | 855 | def _register(self, image_type, disk_format, container_format, | 846 | def _register(self, container_format, disk_format, |
256 | 856 | path, owner, name=None, is_public='T', | 847 | path, owner, name=None, is_public='T', |
257 | 857 | architecture='x86_64', kernel_id=None, ramdisk_id=None): | 848 | architecture='x86_64', kernel_id=None, ramdisk_id=None): |
259 | 858 | meta = {'is_public': True, | 849 | meta = {'is_public': (is_public == 'T'), |
260 | 859 | 'name': name, | 850 | 'name': name, |
261 | 851 | 'container_format': container_format, | ||
262 | 860 | 'disk_format': disk_format, | 852 | 'disk_format': disk_format, |
263 | 861 | 'container_format': container_format, | ||
264 | 862 | 'properties': {'image_state': 'available', | 853 | 'properties': {'image_state': 'available', |
267 | 863 | 'owner': owner, | 854 | 'project_id': owner, |
266 | 864 | 'type': image_type, | ||
268 | 865 | 'architecture': architecture, | 855 | 'architecture': architecture, |
272 | 866 | 'image_location': 'local', | 856 | 'image_location': 'local'}} |
270 | 867 | 'is_public': (is_public == 'T')}} | ||
271 | 868 | print image_type, meta | ||
273 | 869 | if kernel_id: | 857 | if kernel_id: |
274 | 870 | meta['properties']['kernel_id'] = int(kernel_id) | 858 | meta['properties']['kernel_id'] = int(kernel_id) |
275 | 871 | if ramdisk_id: | 859 | if ramdisk_id: |
276 | @@ -890,16 +878,18 @@ | |||
277 | 890 | ramdisk_id = self.ramdisk_register(ramdisk, owner, None, | 878 | ramdisk_id = self.ramdisk_register(ramdisk, owner, None, |
278 | 891 | is_public, architecture) | 879 | is_public, architecture) |
279 | 892 | self.image_register(image, owner, name, is_public, | 880 | self.image_register(image, owner, name, is_public, |
281 | 893 | architecture, kernel_id, ramdisk_id) | 881 | architecture, 'ami', 'ami', |
282 | 882 | kernel_id, ramdisk_id) | ||
283 | 894 | 883 | ||
284 | 895 | def image_register(self, path, owner, name=None, is_public='T', | 884 | def image_register(self, path, owner, name=None, is_public='T', |
287 | 896 | architecture='x86_64', kernel_id=None, ramdisk_id=None, | 885 | architecture='x86_64', container_format='bare', |
288 | 897 | disk_format='ami', container_format='ami'): | 886 | disk_format='raw', kernel_id=None, ramdisk_id=None): |
289 | 898 | """Uploads an image into the image_service | 887 | """Uploads an image into the image_service |
290 | 899 | arguments: path owner [name] [is_public='T'] [architecture='x86_64'] | 888 | arguments: path owner [name] [is_public='T'] [architecture='x86_64'] |
291 | 889 | [container_format='bare'] [disk_format='raw'] | ||
292 | 900 | [kernel_id=None] [ramdisk_id=None] | 890 | [kernel_id=None] [ramdisk_id=None] |
295 | 901 | [disk_format='ami'] [container_format='ami']""" | 891 | """ |
296 | 902 | return self._register('machine', disk_format, container_format, path, | 892 | return self._register(container_format, disk_format, path, |
297 | 903 | owner, name, is_public, architecture, | 893 | owner, name, is_public, architecture, |
298 | 904 | kernel_id, ramdisk_id) | 894 | kernel_id, ramdisk_id) |
299 | 905 | 895 | ||
300 | @@ -908,7 +898,7 @@ | |||
301 | 908 | """Uploads a kernel into the image_service | 898 | """Uploads a kernel into the image_service |
302 | 909 | arguments: path owner [name] [is_public='T'] [architecture='x86_64'] | 899 | arguments: path owner [name] [is_public='T'] [architecture='x86_64'] |
303 | 910 | """ | 900 | """ |
305 | 911 | return self._register('kernel', 'aki', 'aki', path, owner, name, | 901 | return self._register('aki', 'aki', path, owner, name, |
306 | 912 | is_public, architecture) | 902 | is_public, architecture) |
307 | 913 | 903 | ||
308 | 914 | def ramdisk_register(self, path, owner, name=None, is_public='T', | 904 | def ramdisk_register(self, path, owner, name=None, is_public='T', |
309 | @@ -916,7 +906,7 @@ | |||
310 | 916 | """Uploads a ramdisk into the image_service | 906 | """Uploads a ramdisk into the image_service |
311 | 917 | arguments: path owner [name] [is_public='T'] [architecture='x86_64'] | 907 | arguments: path owner [name] [is_public='T'] [architecture='x86_64'] |
312 | 918 | """ | 908 | """ |
314 | 919 | return self._register('ramdisk', 'ari', 'ari', path, owner, name, | 909 | return self._register('ari', 'ari', path, owner, name, |
315 | 920 | is_public, architecture) | 910 | is_public, architecture) |
316 | 921 | 911 | ||
317 | 922 | def _lookup(self, old_image_id): | 912 | def _lookup(self, old_image_id): |
318 | @@ -933,16 +923,17 @@ | |||
319 | 933 | 'ramdisk': 'ari'} | 923 | 'ramdisk': 'ari'} |
320 | 934 | container_format = mapping[old['type']] | 924 | container_format = mapping[old['type']] |
321 | 935 | disk_format = container_format | 925 | disk_format = container_format |
322 | 926 | if container_format == 'ami' and not old.get('kernelId'): | ||
323 | 927 | container_format = 'bare' | ||
324 | 928 | disk_format = 'raw' | ||
325 | 936 | new = {'disk_format': disk_format, | 929 | new = {'disk_format': disk_format, |
326 | 937 | 'container_format': container_format, | 930 | 'container_format': container_format, |
328 | 938 | 'is_public': True, | 931 | 'is_public': old['isPublic'], |
329 | 939 | 'name': old['imageId'], | 932 | 'name': old['imageId'], |
330 | 940 | 'properties': {'image_state': old['imageState'], | 933 | 'properties': {'image_state': old['imageState'], |
332 | 941 | 'owner': old['imageOwnerId'], | 934 | 'project_id': old['imageOwnerId'], |
333 | 942 | 'architecture': old['architecture'], | 935 | 'architecture': old['architecture'], |
337 | 943 | 'type': old['type'], | 936 | 'image_location': old['imageLocation']}} |
335 | 944 | 'image_location': old['imageLocation'], | ||
336 | 945 | 'is_public': old['isPublic']}} | ||
338 | 946 | if old.get('kernelId'): | 937 | if old.get('kernelId'): |
339 | 947 | new['properties']['kernel_id'] = self._lookup(old['kernelId']) | 938 | new['properties']['kernel_id'] = self._lookup(old['kernelId']) |
340 | 948 | if old.get('ramdiskId'): | 939 | if old.get('ramdiskId'): |
341 | @@ -1006,13 +997,11 @@ | |||
342 | 1006 | ('floating', FloatingIpCommands), | 997 | ('floating', FloatingIpCommands), |
343 | 1007 | ('vm', VmCommands), | 998 | ('vm', VmCommands), |
344 | 1008 | ('service', ServiceCommands), | 999 | ('service', ServiceCommands), |
345 | 1009 | ('log', LogCommands), | ||
346 | 1010 | ('db', DbCommands), | 1000 | ('db', DbCommands), |
347 | 1011 | ('volume', VolumeCommands), | 1001 | ('volume', VolumeCommands), |
348 | 1012 | ('instance_type', InstanceTypeCommands), | 1002 | ('instance_type', InstanceTypeCommands), |
349 | 1013 | ('image', ImageCommands), | 1003 | ('image', ImageCommands), |
352 | 1014 | ('flavor', InstanceTypeCommands), | 1004 | ('flavor', InstanceTypeCommands)] |
351 | 1015 | ('instance', InstanceCommands)] | ||
353 | 1016 | 1005 | ||
354 | 1017 | 1006 | ||
355 | 1018 | def lazy_match(name, key_value_tuples): | 1007 | def lazy_match(name, key_value_tuples): |
356 | @@ -1055,8 +1044,8 @@ | |||
357 | 1055 | script_name = argv.pop(0) | 1044 | script_name = argv.pop(0) |
358 | 1056 | if len(argv) < 1: | 1045 | if len(argv) < 1: |
359 | 1057 | print script_name + " category action [<args>]" | 1046 | print script_name + " category action [<args>]" |
362 | 1058 | print "Available categories:" | 1047 | print _("Available categories:") |
363 | 1059 | for k, _ in CATEGORIES: | 1048 | for k, _v in CATEGORIES: |
364 | 1060 | print "\t%s" % k | 1049 | print "\t%s" % k |
365 | 1061 | sys.exit(2) | 1050 | sys.exit(2) |
366 | 1062 | category = argv.pop(0) | 1051 | category = argv.pop(0) |
367 | @@ -1067,7 +1056,7 @@ | |||
368 | 1067 | actions = methods_of(command_object) | 1056 | actions = methods_of(command_object) |
369 | 1068 | if len(argv) < 1: | 1057 | if len(argv) < 1: |
370 | 1069 | print script_name + " category action [<args>]" | 1058 | print script_name + " category action [<args>]" |
372 | 1070 | print "Available actions for %s category:" % category | 1059 | print _("Available actions for %s category:") % category |
373 | 1071 | for k, _v in actions: | 1060 | for k, _v in actions: |
374 | 1072 | print "\t%s" % k | 1061 | print "\t%s" % k |
375 | 1073 | sys.exit(2) | 1062 | sys.exit(2) |
376 | @@ -1079,9 +1068,12 @@ | |||
377 | 1079 | fn(*argv) | 1068 | fn(*argv) |
378 | 1080 | sys.exit(0) | 1069 | sys.exit(0) |
379 | 1081 | except TypeError: | 1070 | except TypeError: |
381 | 1082 | print "Possible wrong number of arguments supplied" | 1071 | print _("Possible wrong number of arguments supplied") |
382 | 1083 | print "%s %s: %s" % (category, action, fn.__doc__) | 1072 | print "%s %s: %s" % (category, action, fn.__doc__) |
383 | 1084 | raise | 1073 | raise |
384 | 1074 | except Exception: | ||
385 | 1075 | print _("Command failed, please check log for more info") | ||
386 | 1076 | raise | ||
387 | 1085 | 1077 | ||
388 | 1086 | if __name__ == '__main__': | 1078 | if __name__ == '__main__': |
389 | 1087 | main() | 1079 | main() |
390 | 1088 | 1080 | ||
391 | === added file 'bin/nova-vncproxy' | |||
392 | --- bin/nova-vncproxy 1970-01-01 00:00:00 +0000 | |||
393 | +++ bin/nova-vncproxy 2011-04-12 11:01:25 +0000 | |||
394 | @@ -0,0 +1,101 @@ | |||
395 | 1 | #!/usr/bin/env python | ||
396 | 2 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||
397 | 3 | |||
398 | 4 | # Copyright (c) 2010 Openstack, LLC. | ||
399 | 5 | # All Rights Reserved. | ||
400 | 6 | # | ||
401 | 7 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
402 | 8 | # you may not use this file except in compliance with the License. | ||
403 | 9 | # You may obtain a copy of the License at | ||
404 | 10 | # | ||
405 | 11 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
406 | 12 | # | ||
407 | 13 | # Unless required by applicable law or agreed to in writing, software | ||
408 | 14 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
409 | 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
410 | 16 | # See the License for the specific language governing permissions and | ||
411 | 17 | # limitations under the License. | ||
412 | 18 | |||
413 | 19 | """VNC Console Proxy Server.""" | ||
414 | 20 | |||
415 | 21 | import eventlet | ||
416 | 22 | import gettext | ||
417 | 23 | import os | ||
418 | 24 | import sys | ||
419 | 25 | |||
420 | 26 | possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), | ||
421 | 27 | os.pardir, | ||
422 | 28 | os.pardir)) | ||
423 | 29 | if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): | ||
424 | 30 | sys.path.insert(0, possible_topdir) | ||
425 | 31 | |||
426 | 32 | gettext.install('nova', unicode=1) | ||
427 | 33 | |||
428 | 34 | from nova import flags | ||
429 | 35 | from nova import log as logging | ||
430 | 36 | from nova import service | ||
431 | 37 | from nova import utils | ||
432 | 38 | from nova import wsgi | ||
433 | 39 | from nova import version | ||
434 | 40 | from nova.vnc import auth | ||
435 | 41 | from nova.vnc import proxy | ||
436 | 42 | |||
437 | 43 | |||
438 | 44 | LOG = logging.getLogger('nova.vnc-proxy') | ||
439 | 45 | |||
440 | 46 | |||
441 | 47 | FLAGS = flags.FLAGS | ||
442 | 48 | flags.DEFINE_string('vncproxy_wwwroot', '/var/lib/nova/noVNC/', | ||
443 | 49 | 'Full path to noVNC directory') | ||
444 | 50 | flags.DEFINE_boolean('vnc_debug', False, | ||
445 | 51 | 'Enable debugging features, like token bypassing') | ||
446 | 52 | flags.DEFINE_integer('vncproxy_port', 6080, | ||
447 | 53 | 'Port that the VNC proxy should bind to') | ||
448 | 54 | flags.DEFINE_string('vncproxy_host', '0.0.0.0', | ||
449 | 55 | 'Address that the VNC proxy should bind to') | ||
450 | 56 | flags.DEFINE_integer('vnc_token_ttl', 300, | ||
451 | 57 | 'How many seconds before deleting tokens') | ||
452 | 58 | flags.DEFINE_string('vncproxy_manager', 'nova.vnc.auth.VNCProxyAuthManager', | ||
453 | 59 | 'Manager for vncproxy auth') | ||
454 | 60 | |||
455 | 61 | flags.DEFINE_flag(flags.HelpFlag()) | ||
456 | 62 | flags.DEFINE_flag(flags.HelpshortFlag()) | ||
457 | 63 | flags.DEFINE_flag(flags.HelpXMLFlag()) | ||
458 | 64 | |||
459 | 65 | |||
460 | 66 | if __name__ == "__main__": | ||
461 | 67 | utils.default_flagfile() | ||
462 | 68 | FLAGS(sys.argv) | ||
463 | 69 | logging.setup() | ||
464 | 70 | |||
465 | 71 | LOG.audit(_("Starting nova-vnc-proxy node (version %s)"), | ||
466 | 72 | version.version_string_with_vcs()) | ||
467 | 73 | |||
468 | 74 | if not (os.path.exists(FLAGS.vncproxy_wwwroot) and | ||
469 | 75 | os.path.exists(FLAGS.vncproxy_wwwroot + '/vnc_auto.html')): | ||
470 | 76 | LOG.info(_("Missing vncproxy_wwwroot (version %s)"), | ||
471 | 77 | FLAGS.vncproxy_wwwroot) | ||
472 | 78 | LOG.info(_("You need a slightly modified version of noVNC " | ||
473 | 79 | "to work with the nova-vnc-proxy")) | ||
474 | 80 | LOG.info(_("Check out the most recent nova noVNC code: %s"), | ||
475 | 81 | "git://github.com/sleepsonthefloor/noVNC.git") | ||
476 | 82 | LOG.info(_("And drop it in %s"), FLAGS.vncproxy_wwwroot) | ||
477 | 83 | exit(1) | ||
478 | 84 | |||
479 | 85 | app = proxy.WebsocketVNCProxy(FLAGS.vncproxy_wwwroot) | ||
480 | 86 | |||
481 | 87 | LOG.audit(_("Allowing access to the following files: %s"), | ||
482 | 88 | app.get_whitelist()) | ||
483 | 89 | |||
484 | 90 | with_logging = auth.LoggingMiddleware(app) | ||
485 | 91 | |||
486 | 92 | if FLAGS.vnc_debug: | ||
487 | 93 | with_auth = proxy.DebugMiddleware(with_logging) | ||
488 | 94 | else: | ||
489 | 95 | with_auth = auth.VNCNovaAuthMiddleware(with_logging) | ||
490 | 96 | |||
491 | 97 | service.serve() | ||
492 | 98 | |||
493 | 99 | server = wsgi.Server() | ||
494 | 100 | server.start(with_auth, FLAGS.vncproxy_port, host=FLAGS.vncproxy_host) | ||
495 | 101 | server.wait() | ||
496 | 0 | 102 | ||
497 | === added file 'doc/source/devref/zone.rst' | |||
498 | --- doc/source/devref/zone.rst 1970-01-01 00:00:00 +0000 | |||
499 | +++ doc/source/devref/zone.rst 2011-04-12 11:01:25 +0000 | |||
500 | @@ -0,0 +1,127 @@ | |||
501 | 1 | .. | ||
502 | 2 | Copyright 2010-2011 OpenStack LLC | ||
503 | 3 | All Rights Reserved. | ||
504 | 4 | |||
505 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
506 | 6 | not use this file except in compliance with the License. You may obtain | ||
507 | 7 | a copy of the License at | ||
508 | 8 | |||
509 | 9 | http://www.apache.org/licenses/LICENSE-2.0 | ||
510 | 10 | |||
511 | 11 | Unless required by applicable law or agreed to in writing, software | ||
512 | 12 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
513 | 13 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
514 | 14 | License for the specific language governing permissions and limitations | ||
515 | 15 | under the License. | ||
516 | 16 | |||
517 | 17 | Zones | ||
518 | 18 | ===== | ||
519 | 19 | |||
520 | 20 | A Nova deployment is called a Zone. At the very least a Zone requires an API node, a Scheduler node, a database and RabbitMQ. Pushed further a Zone may contain many API nodes, many Scheduler, Volume, Network and Compute nodes as well as a cluster of databases and RabbitMQ servers. A Zone allows you to partition your deployments into logical groups for load balancing and instance distribution. | ||
521 | 21 | |||
522 | 22 | The idea behind Zones is, if a particular deployment is not capable of servicing a particular request, the request may be forwarded to (child) Zones for possible processing. Zones may be nested in a tree fashion. | ||
523 | 23 | |||
524 | 24 | Zones only know about their immediate children, they do not know about their parent Zones and may in fact have more than one parent. Likewise, a Zone's children may themselves have child Zones. | ||
525 | 25 | |||
526 | 26 | Zones share nothing. They communicate via the public OpenStack API only. No database, queue, user or project definition is shared between Zones. | ||
527 | 27 | |||
528 | 28 | |||
529 | 29 | Capabilities | ||
530 | 30 | ------------ | ||
531 | 31 | Routing between Zones is based on the Capabilities of that Zone. Capabilities are nothing more than key/value pairs. Values are multi-value, with each value separated with a semicolon (`;`). When expressed as a string they take the form: | ||
532 | 32 | |||
533 | 33 | :: | ||
534 | 34 | |||
535 | 35 | key=value;value;value, key=value;value;value | ||
536 | 36 | |||
537 | 37 | Zones have Capabilities which are general to the Zone and are set via `--zone-capabilities` flag. Zones also have dynamic per-service Capabilities. Services derived from `nova.manager.SchedulerDependentManager` (such as Compute, Volume and Network) can set these capabilities by calling the `update_service_capabilities()` method on their `Manager` base class. These capabilities will be periodically sent to the Scheduler service automatically. The rate at which these updates are sent is controlled by the `--periodic_interval` flag. | ||
538 | 38 | |||
539 | 39 | Flow within a Zone | ||
540 | 40 | ------------------ | ||
541 | 41 | The brunt of the work within a Zone is done in the Scheduler Service. The Scheduler is responsible for: | ||
542 | 42 | - collecting capability messages from the Compute, Volume and Network nodes, | ||
543 | 43 | - polling the child Zones for their status and | ||
544 | 44 | - providing data to the Distributed Scheduler for performing load balancing calculations | ||
545 | 45 | |||
546 | 46 | Inter-service communication within a Zone is done with RabbitMQ. Each class of Service (Compute, Volume and Network) has both a named message exchange (particular to that host) and a general message exchange (particular to that class of service). Messages sent to these exchanges are picked off in round-robin fashion. Zones introduce a new fan-out exchange per service. Messages sent to the fan-out exchange are picked up by all services of a particular class. This fan-out exchange is used by the Scheduler services to receive capability messages from the Compute, Volume and Network nodes. | ||
547 | 47 | |||
548 | 48 | These capability messages are received by the Scheduler services and stored in the `ZoneManager` object. The SchedulerManager object has a reference to the `ZoneManager` it can use for load balancing. | ||
549 | 49 | |||
550 | 50 | The `ZoneManager` also polls the child Zones periodically to gather their capabilities to aid in decision making. This is done via the OpenStack API `/v1.0/zones/info` REST call. This also captures the name of each child Zone. The Zone name is set via the `--zone-name` flag (and defaults to "nova"). | ||
551 | 51 | |||
552 | 52 | Zone administrative functions | ||
553 | 53 | ----------------------------- | ||
554 | 54 | Zone administrative operations are usually done using python-novaclient_ | ||
555 | 55 | |||
556 | 56 | .. _python-novaclient: https://github.com/rackspace/python-novaclient | ||
557 | 57 | |||
558 | 58 | In order to use the Zone operations, be sure to enable administrator operations in OpenStack API by setting the `--allow_admin_api=true` flag. | ||
559 | 59 | |||
560 | 60 | Finally you need to enable Zone Forwarding. This will be used by the Distributed Scheduler initiative currently underway. Set `--enable_zone_routing=true` to enable this feature. | ||
561 | 61 | |||
562 | 62 | Find out about this Zone | ||
563 | 63 | ------------------------ | ||
564 | 64 | In any Zone you can find the Zone's name and capabilities with the ``nova zone-info`` command. | ||
565 | 65 | |||
566 | 66 | :: | ||
567 | 67 | |||
568 | 68 | alice@novadev:~$ nova zone-info | ||
569 | 69 | +-----------------+---------------+ | ||
570 | 70 | | Property | Value | | ||
571 | 71 | +-----------------+---------------+ | ||
572 | 72 | | compute_cpu | 0.7,0.7 | | ||
573 | 73 | | compute_disk | 123000,123000 | | ||
574 | 74 | | compute_network | 800,800 | | ||
575 | 75 | | hypervisor | xenserver | | ||
576 | 76 | | name | nova | | ||
577 | 77 | | network_cpu | 0.7,0.7 | | ||
578 | 78 | | network_disk | 123000,123000 | | ||
579 | 79 | | network_network | 800,800 | | ||
580 | 80 | | os | linux | | ||
581 | 81 | +-----------------+---------------+ | ||
582 | 82 | |||
583 | 83 | This equates to a GET operation on `.../zones/info`. If you have no child Zones defined you'll usually only get back the default `name`, `hypervisor` and `os` capabilities. Otherwise you'll get back a tuple of min, max values for each capabilities of all the hosts of all the services running in the child zone. These take the `<service>_<capability> = <min>,<max>` format. | ||
584 | 84 | |||
585 | 85 | Adding a child Zone | ||
586 | 86 | ------------------- | ||
587 | 87 | Any Zone can be a parent Zone. Children are associated to a Zone. The Zone where this command originates from is known as the Parent Zone. Routing is only ever conducted from a Zone to its children, never the other direction. From a parent zone you can add a child zone with the following command: | ||
588 | 88 | |||
589 | 89 | :: | ||
590 | 90 | |||
591 | 91 | nova zone-add <child zone api url> <username> <nova api key> | ||
592 | 92 | |||
593 | 93 | You can get the `child zone api url`, `nova api key` and `username` from the `novarc` file in the child zone. For example: | ||
594 | 94 | |||
595 | 95 | :: | ||
596 | 96 | |||
597 | 97 | export NOVA_API_KEY="3bd1af06-6435-4e23-a827-413b2eb86934" | ||
598 | 98 | export NOVA_USERNAME="alice" | ||
599 | 99 | export NOVA_URL="http://192.168.2.120:8774/v1.0/" | ||
600 | 100 | |||
601 | 101 | |||
602 | 102 | This equates to a POST operation to `.../zones/` to add a new zone. No connection attempt to the child zone is done when this command. It only puts an entry in the db at this point. After about 30 seconds the `ZoneManager` in the Scheduler services will attempt to talk to the child zone and get its information. | ||
603 | 103 | |||
604 | 104 | Getting a list of child Zones | ||
605 | 105 | ----------------------------- | ||
606 | 106 | |||
607 | 107 | :: | ||
608 | 108 | |||
609 | 109 | nova zone-list | ||
610 | 110 | |||
611 | 111 | alice@novadev:~$ nova zone-list | ||
612 | 112 | +----+-------+-----------+--------------------------------------------+---------------------------------+ | ||
613 | 113 | | ID | Name | Is Active | Capabilities | API URL | | ||
614 | 114 | +----+-------+-----------+--------------------------------------------+---------------------------------+ | ||
615 | 115 | | 2 | zone1 | True | hypervisor=xenserver;kvm, os=linux;windows | http://192.168.2.108:8774/v1.0/ | | ||
616 | 116 | | 3 | zone2 | True | hypervisor=xenserver;kvm, os=linux;windows | http://192.168.2.115:8774/v1.0/ | | ||
617 | 117 | +----+-------+-----------+--------------------------------------------+---------------------------------+ | ||
618 | 118 | |||
619 | 119 | This equates to a GET operation to `.../zones`. | ||
620 | 120 | |||
621 | 121 | Removing a child Zone | ||
622 | 122 | --------------------- | ||
623 | 123 | :: | ||
624 | 124 | |||
625 | 125 | nova zone-delete <N> | ||
626 | 126 | |||
627 | 127 | This equates to a DELETE call to `.../zones/N`. The Zone with ID=N will be removed. This will only remove the zone entry from the current (parent) Zone, no child Zones are affected. Removing a Child Zone doesn't affect any other part of the hierarchy. | ||
628 | 0 | 128 | ||
629 | === modified file 'doc/source/man/novamanage.rst' | |||
630 | --- doc/source/man/novamanage.rst 2011-03-08 20:28:11 +0000 | |||
631 | +++ doc/source/man/novamanage.rst 2011-04-12 11:01:25 +0000 | |||
632 | @@ -240,6 +240,16 @@ | |||
633 | 240 | 240 | ||
634 | 241 | Converts all images in directory from the old (Bexar) format to the new format. | 241 | Converts all images in directory from the old (Bexar) format to the new format. |
635 | 242 | 242 | ||
636 | 243 | Nova VM | ||
637 | 244 | ~~~~~~~~~~~ | ||
638 | 245 | |||
639 | 246 | ``nova-manage vm list [host]`` | ||
640 | 247 | Show a list of all instances. Accepts optional hostname (to show only instances on specific host). | ||
641 | 248 | |||
642 | 249 | ``nova-manage live-migration <ec2_id> <destination host name>`` | ||
643 | 250 | Live migrate instance from current host to destination host. Requires instance id (which comes from euca-describe-instance) and destination host name (which can be found from nova-manage service list). | ||
644 | 251 | |||
645 | 252 | |||
646 | 243 | FILES | 253 | FILES |
647 | 244 | ======== | 254 | ======== |
648 | 245 | 255 | ||
649 | 246 | 256 | ||
650 | === added file 'doc/source/runnova/vncconsole.rst' | |||
651 | --- doc/source/runnova/vncconsole.rst 1970-01-01 00:00:00 +0000 | |||
652 | +++ doc/source/runnova/vncconsole.rst 2011-04-12 11:01:25 +0000 | |||
653 | @@ -0,0 +1,76 @@ | |||
654 | 1 | .. | ||
655 | 2 | Copyright 2010-2011 United States Government as represented by the | ||
656 | 3 | Administrator of the National Aeronautics and Space Administration. | ||
657 | 4 | All Rights Reserved. | ||
658 | 5 | |||
659 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
660 | 7 | not use this file except in compliance with the License. You may obtain | ||
661 | 8 | a copy of the License at | ||
662 | 9 | |||
663 | 10 | http://www.apache.org/licenses/LICENSE-2.0 | ||
664 | 11 | |||
665 | 12 | Unless required by applicable law or agreed to in writing, software | ||
666 | 13 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
667 | 14 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
668 | 15 | License for the specific language governing permissions and limitations | ||
669 | 16 | under the License. | ||
670 | 17 | |||
671 | 18 | Getting Started with the VNC Proxy | ||
672 | 19 | ================================== | ||
673 | 20 | |||
674 | 21 | The VNC Proxy is an OpenStack component that allows users of Nova to access | ||
675 | 22 | their instances through a websocket enabled browser (like Google Chrome). | ||
676 | 23 | |||
677 | 24 | A VNC Connection works like so: | ||
678 | 25 | |||
679 | 26 | * User connects over an api and gets a url like http://ip:port/?token=xyz | ||
680 | 27 | * User pastes url in browser | ||
681 | 28 | * Browser connects to VNC Proxy though a websocket enabled client like noVNC | ||
682 | 29 | * VNC Proxy authorizes users token, maps the token to a host and port of an | ||
683 | 30 | instance's VNC server | ||
684 | 31 | * VNC Proxy initiates connection to VNC server, and continues proxying until | ||
685 | 32 | the session ends | ||
686 | 33 | |||
687 | 34 | |||
688 | 35 | Configuring the VNC Proxy | ||
689 | 36 | ------------------------- | ||
690 | 37 | nova-vncproxy requires a websocket enabled html client to work properly. At | ||
691 | 38 | this time, the only tested client is a slightly modified fork of noVNC, which | ||
692 | 39 | you can at find http://github.com/openstack/noVNC.git | ||
693 | 40 | |||
694 | 41 | .. todo:: add instruction for installing from package | ||
695 | 42 | |||
696 | 43 | noVNC must be in the location specified by --vncproxy_wwwroot, which defaults | ||
697 | 44 | to /var/lib/nova/noVNC. nova-vncproxy will fail to launch until this code | ||
698 | 45 | is properly installed. | ||
699 | 46 | |||
700 | 47 | By default, nova-vncproxy binds 0.0.0.0:6080. This can be configured with: | ||
701 | 48 | |||
702 | 49 | * --vncproxy_port=[port] | ||
703 | 50 | * --vncproxy_host=[host] | ||
704 | 51 | |||
705 | 52 | |||
706 | 53 | Enabling VNC Consoles in Nova | ||
707 | 54 | ----------------------------- | ||
708 | 55 | At the moment, VNC support is supported only when using libvirt. To enable VNC | ||
709 | 56 | Console, configure the following flags: | ||
710 | 57 | |||
711 | 58 | * --vnc_console_proxy_url=http://[proxy_host]:[proxy_port] - proxy_port | ||
712 | 59 | defaults to 6080. This url must point to nova-vncproxy | ||
713 | 60 | * --vnc_enabled=[True|False] - defaults to True. If this flag is not set your | ||
714 | 61 | instances will launch without vnc support. | ||
715 | 62 | |||
716 | 63 | |||
717 | 64 | Getting an instance's VNC Console | ||
718 | 65 | --------------------------------- | ||
719 | 66 | You can access an instance's VNC Console url in the following methods: | ||
720 | 67 | |||
721 | 68 | * Using the direct api: | ||
722 | 69 | eg: 'stack --user=admin --project=admin compute get_vnc_console instance_id=1' | ||
723 | 70 | * Support for Dashboard, and the Openstack API will be forthcoming | ||
724 | 71 | |||
725 | 72 | |||
726 | 73 | Accessing VNC Consoles without a web browser | ||
727 | 74 | -------------------------------------------- | ||
728 | 75 | At the moment, VNC Consoles are only supported through the web browser, but | ||
729 | 76 | more general VNC support is in the works. | ||
730 | 0 | 77 | ||
731 | === added directory 'etc/nova' | |||
732 | === renamed file 'etc/api-paste.ini' => 'etc/nova/api-paste.ini' | |||
733 | === renamed directory 'CA' => 'nova/CA' | |||
734 | === modified file 'nova/CA/geninter.sh' | |||
735 | --- CA/geninter.sh 2010-11-06 00:02:36 +0000 | |||
736 | +++ nova/CA/geninter.sh 2011-04-12 11:01:25 +0000 | |||
737 | @@ -23,7 +23,7 @@ | |||
738 | 23 | cd projects/$NAME | 23 | cd projects/$NAME |
739 | 24 | cp ../../openssl.cnf.tmpl openssl.cnf | 24 | cp ../../openssl.cnf.tmpl openssl.cnf |
740 | 25 | sed -i -e s/%USERNAME%/$NAME/g openssl.cnf | 25 | sed -i -e s/%USERNAME%/$NAME/g openssl.cnf |
742 | 26 | mkdir certs crl newcerts private | 26 | mkdir -p certs crl newcerts private |
743 | 27 | openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes | 27 | openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes |
744 | 28 | echo "10" > serial | 28 | echo "10" > serial |
745 | 29 | touch index.txt | 29 | touch index.txt |
746 | 30 | 30 | ||
747 | === modified file 'nova/CA/genrootca.sh' | |||
748 | --- CA/genrootca.sh 2010-11-06 00:02:36 +0000 | |||
749 | +++ nova/CA/genrootca.sh 2011-04-12 11:01:25 +0000 | |||
750 | @@ -20,8 +20,9 @@ | |||
751 | 20 | then | 20 | then |
752 | 21 | echo "Not installing, it's already done." | 21 | echo "Not installing, it's already done." |
753 | 22 | else | 22 | else |
755 | 23 | cp openssl.cnf.tmpl openssl.cnf | 23 | cp "$(dirname $0)/openssl.cnf.tmpl" openssl.cnf |
756 | 24 | sed -i -e s/%USERNAME%/ROOT/g openssl.cnf | 24 | sed -i -e s/%USERNAME%/ROOT/g openssl.cnf |
757 | 25 | mkdir -p certs crl newcerts private | ||
758 | 25 | openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes | 26 | openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes |
759 | 26 | touch index.txt | 27 | touch index.txt |
760 | 27 | echo "10" > serial | 28 | echo "10" > serial |
761 | 28 | 29 | ||
762 | === modified file 'nova/CA/openssl.cnf.tmpl' | |||
763 | --- CA/openssl.cnf.tmpl 2011-03-16 18:22:29 +0000 | |||
764 | +++ nova/CA/openssl.cnf.tmpl 2011-04-12 11:01:25 +0000 | |||
765 | @@ -41,9 +41,13 @@ | |||
766 | 41 | certopt = default_ca | 41 | certopt = default_ca |
767 | 42 | policy = policy_match | 42 | policy = policy_match |
768 | 43 | 43 | ||
769 | 44 | # NOTE(dprince): stateOrProvinceName must be 'supplied' or 'optional' to | ||
770 | 45 | # work around a stateOrProvince printable string UTF8 mismatch on | ||
771 | 46 | # RHEL 6 and Fedora 14 (using openssl-1.0.0-4.el6.x86_64 or | ||
772 | 47 | # openssl-1.0.0d-1.fc14.x86_64) | ||
773 | 44 | [ policy_match ] | 48 | [ policy_match ] |
774 | 45 | countryName = match | 49 | countryName = match |
776 | 46 | stateOrProvinceName = match | 50 | stateOrProvinceName = supplied |
777 | 47 | organizationName = optional | 51 | organizationName = optional |
778 | 48 | organizationalUnitName = optional | 52 | organizationalUnitName = optional |
779 | 49 | commonName = supplied | 53 | commonName = supplied |
780 | 50 | 54 | ||
781 | === removed file 'nova/adminclient.py' | |||
782 | --- nova/adminclient.py 2011-02-24 19:36:15 +0000 | |||
783 | +++ nova/adminclient.py 1970-01-01 00:00:00 +0000 | |||
784 | @@ -1,476 +0,0 @@ | |||
785 | 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||
786 | 2 | |||
787 | 3 | # Copyright 2010 United States Government as represented by the | ||
788 | 4 | # Administrator of the National Aeronautics and Space Administration. | ||
789 | 5 | # All Rights Reserved. | ||
790 | 6 | # | ||
791 | 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
792 | 8 | # not use this file except in compliance with the License. You may obtain | ||
793 | 9 | # a copy of the License at | ||
794 | 10 | # | ||
795 | 11 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
796 | 12 | # | ||
797 | 13 | # Unless required by applicable law or agreed to in writing, software | ||
798 | 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
799 | 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
800 | 16 | # License for the specific language governing permissions and limitations | ||
801 | 17 | # under the License. | ||
802 | 18 | """ | ||
803 | 19 | Nova User API client library. | ||
804 | 20 | """ | ||
805 | 21 | |||
806 | 22 | import base64 | ||
807 | 23 | import boto | ||
808 | 24 | import boto.exception | ||
809 | 25 | import httplib | ||
810 | 26 | import re | ||
811 | 27 | import string | ||
812 | 28 | |||
813 | 29 | from boto.ec2.regioninfo import RegionInfo | ||
814 | 30 | |||
815 | 31 | |||
816 | 32 | DEFAULT_CLC_URL = 'http://127.0.0.1:8773' | ||
817 | 33 | DEFAULT_REGION = 'nova' | ||
818 | 34 | |||
819 | 35 | |||
820 | 36 | class UserInfo(object): | ||
821 | 37 | """ | ||
822 | 38 | Information about a Nova user, as parsed through SAX. | ||
823 | 39 | |||
824 | 40 | **Fields Include** | ||
825 | 41 | |||
826 | 42 | * username | ||
827 | 43 | * accesskey | ||
828 | 44 | * secretkey | ||
829 | 45 | * file (optional) containing zip of X509 cert & rc file | ||
830 | 46 | |||
831 | 47 | """ | ||
832 | 48 | |||
833 | 49 | def __init__(self, connection=None, username=None, endpoint=None): | ||
834 | 50 | self.connection = connection | ||
835 | 51 | self.username = username | ||
836 | 52 | self.endpoint = endpoint | ||
837 | 53 | |||
838 | 54 | def __repr__(self): | ||
839 | 55 | return 'UserInfo:%s' % self.username | ||
840 | 56 | |||
841 | 57 | def startElement(self, name, attrs, connection): | ||
842 | 58 | return None | ||
843 | 59 | |||
844 | 60 | def endElement(self, name, value, connection): | ||
845 | 61 | if name == 'username': | ||
846 | 62 | self.username = str(value) | ||
847 | 63 | elif name == 'file': | ||
848 | 64 | self.file = base64.b64decode(str(value)) | ||
849 | 65 | elif name == 'accesskey': | ||
850 | 66 | self.accesskey = str(value) | ||
851 | 67 | elif name == 'secretkey': | ||
852 | 68 | self.secretkey = str(value) | ||
853 | 69 | |||
854 | 70 | |||
855 | 71 | class UserRole(object): | ||
856 | 72 | """ | ||
857 | 73 | Information about a Nova user's role, as parsed through SAX. | ||
858 | 74 | |||
859 | 75 | **Fields include** | ||
860 | 76 | |||
861 | 77 | * role | ||
862 | 78 | |||
863 | 79 | """ | ||
864 | 80 | |||
865 | 81 | def __init__(self, connection=None): | ||
866 | 82 | self.connection = connection | ||
867 | 83 | self.role = None | ||
868 | 84 | |||
869 | 85 | def __repr__(self): | ||
870 | 86 | return 'UserRole:%s' % self.role | ||
871 | 87 | |||
872 | 88 | def startElement(self, name, attrs, connection): | ||
873 | 89 | return None | ||
874 | 90 | |||
875 | 91 | def endElement(self, name, value, connection): | ||
876 | 92 | if name == 'role': | ||
877 | 93 | self.role = value | ||
878 | 94 | else: | ||
879 | 95 | setattr(self, name, str(value)) | ||
880 | 96 | |||
881 | 97 | |||
882 | 98 | class ProjectInfo(object): | ||
883 | 99 | """ | ||
884 | 100 | Information about a Nova project, as parsed through SAX. | ||
885 | 101 | |||
886 | 102 | **Fields include** | ||
887 | 103 | |||
888 | 104 | * projectname | ||
889 | 105 | * description | ||
890 | 106 | * projectManagerId | ||
891 | 107 | * memberIds | ||
892 | 108 | |||
893 | 109 | """ | ||
894 | 110 | |||
895 | 111 | def __init__(self, connection=None): | ||
896 | 112 | self.connection = connection | ||
897 | 113 | self.projectname = None | ||
898 | 114 | self.description = None | ||
899 | 115 | self.projectManagerId = None | ||
900 | 116 | self.memberIds = [] | ||
901 | 117 | |||
902 | 118 | def __repr__(self): | ||
903 | 119 | return 'ProjectInfo:%s' % self.projectname | ||
904 | 120 | |||
905 | 121 | def startElement(self, name, attrs, connection): | ||
906 | 122 | return None | ||
907 | 123 | |||
908 | 124 | def endElement(self, name, value, connection): | ||
909 | 125 | if name == 'projectname': | ||
910 | 126 | self.projectname = value | ||
911 | 127 | elif name == 'description': | ||
912 | 128 | self.description = value | ||
913 | 129 | elif name == 'projectManagerId': | ||
914 | 130 | self.projectManagerId = value | ||
915 | 131 | elif name == 'memberId': | ||
916 | 132 | self.memberIds.append(value) | ||
917 | 133 | else: | ||
918 | 134 | setattr(self, name, str(value)) | ||
919 | 135 | |||
920 | 136 | |||
921 | 137 | class ProjectMember(object): | ||
922 | 138 | """ | ||
923 | 139 | Information about a Nova project member, as parsed through SAX. | ||
924 | 140 | |||
925 | 141 | **Fields include** | ||
926 | 142 | |||
927 | 143 | * memberId | ||
928 | 144 | |||
929 | 145 | """ | ||
930 | 146 | |||
931 | 147 | def __init__(self, connection=None): | ||
932 | 148 | self.connection = connection | ||
933 | 149 | self.memberId = None | ||
934 | 150 | |||
935 | 151 | def __repr__(self): | ||
936 | 152 | return 'ProjectMember:%s' % self.memberId | ||
937 | 153 | |||
938 | 154 | def startElement(self, name, attrs, connection): | ||
939 | 155 | return None | ||
940 | 156 | |||
941 | 157 | def endElement(self, name, value, connection): | ||
942 | 158 | if name == 'member': | ||
943 | 159 | self.memberId = value | ||
944 | 160 | else: | ||
945 | 161 | setattr(self, name, str(value)) | ||
946 | 162 | |||
947 | 163 | |||
948 | 164 | class HostInfo(object): | ||
949 | 165 | """ | ||
950 | 166 | Information about a Nova Host, as parsed through SAX. | ||
951 | 167 | |||
952 | 168 | **Fields Include** | ||
953 | 169 | |||
954 | 170 | * Hostname | ||
955 | 171 | * Compute service status | ||
956 | 172 | * Volume service status | ||
957 | 173 | * Instance count | ||
958 | 174 | * Volume count | ||
959 | 175 | """ | ||
960 | 176 | |||
961 | 177 | def __init__(self, connection=None): | ||
962 | 178 | self.connection = connection | ||
963 | 179 | self.hostname = None | ||
964 | 180 | self.compute = None | ||
965 | 181 | self.volume = None | ||
966 | 182 | self.instance_count = 0 | ||
967 | 183 | self.volume_count = 0 | ||
968 | 184 | |||
969 | 185 | def __repr__(self): | ||
970 | 186 | return 'Host:%s' % self.hostname | ||
971 | 187 | |||
972 | 188 | # this is needed by the sax parser, so ignore the ugly name | ||
973 | 189 | def startElement(self, name, attrs, connection): | ||
974 | 190 | return None | ||
975 | 191 | |||
976 | 192 | # this is needed by the sax parser, so ignore the ugly name | ||
977 | 193 | def endElement(self, name, value, connection): | ||
978 | 194 | fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name)) | ||
979 | 195 | setattr(self, fixed_name, value) | ||
980 | 196 | |||
981 | 197 | |||
982 | 198 | class Vpn(object): | ||
983 | 199 | """ | ||
984 | 200 | Information about a Vpn, as parsed through SAX | ||
985 | 201 | |||
986 | 202 | **Fields Include** | ||
987 | 203 | |||
988 | 204 | * instance_id | ||
989 | 205 | * project_id | ||
990 | 206 | * public_ip | ||
991 | 207 | * public_port | ||
992 | 208 | * created_at | ||
993 | 209 | * internal_ip | ||
994 | 210 | * state | ||
995 | 211 | """ | ||
996 | 212 | |||
997 | 213 | def __init__(self, connection=None): | ||
998 | 214 | self.connection = connection | ||
999 | 215 | self.instance_id = None | ||
1000 | 216 | self.project_id = None | ||
1001 | 217 | |||
1002 | 218 | def __repr__(self): | ||
1003 | 219 | return 'Vpn:%s:%s' % (self.project_id, self.instance_id) | ||
1004 | 220 | |||
1005 | 221 | def startElement(self, name, attrs, connection): | ||
1006 | 222 | return None | ||
1007 | 223 | |||
1008 | 224 | def endElement(self, name, value, connection): | ||
1009 | 225 | fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name)) | ||
1010 | 226 | setattr(self, fixed_name, value) | ||
1011 | 227 | |||
1012 | 228 | |||
1013 | 229 | class InstanceType(object): | ||
1014 | 230 | """ | ||
1015 | 231 | Information about a Nova instance type, as parsed through SAX. | ||
1016 | 232 | |||
1017 | 233 | **Fields include** | ||
1018 | 234 | |||
1019 | 235 | * name | ||
1020 | 236 | * vcpus | ||
1021 | 237 | * disk_gb | ||
1022 | 238 | * memory_mb | ||
1023 | 239 | * flavor_id | ||
1024 | 240 | |||
1025 | 241 | """ | ||
1026 | 242 | |||
1027 | 243 | def __init__(self, connection=None): | ||
1028 | 244 | self.connection = connection | ||
1029 | 245 | self.name = None | ||
1030 | 246 | self.vcpus = None | ||
1031 | 247 | self.disk_gb = None | ||
1032 | 248 | self.memory_mb = None | ||
1033 | 249 | self.flavor_id = None | ||
1034 | 250 | |||
1035 | 251 | def __repr__(self): | ||
1036 | 252 | return 'InstanceType:%s' % self.name | ||
1037 | 253 | |||
1038 | 254 | def startElement(self, name, attrs, connection): | ||
1039 | 255 | return None | ||
1040 | 256 | |||
1041 | 257 | def endElement(self, name, value, connection): | ||
1042 | 258 | if name == "memoryMb": | ||
1043 | 259 | self.memory_mb = str(value) | ||
1044 | 260 | elif name == "flavorId": | ||
1045 | 261 | self.flavor_id = str(value) | ||
1046 | 262 | elif name == "diskGb": | ||
1047 | 263 | self.disk_gb = str(value) | ||
1048 | 264 | else: | ||
1049 | 265 | setattr(self, name, str(value)) | ||
1050 | 266 | |||
1051 | 267 | |||
1052 | 268 | class NovaAdminClient(object): | ||
1053 | 269 | |||
1054 | 270 | def __init__( | ||
1055 | 271 | self, | ||
1056 | 272 | clc_url=DEFAULT_CLC_URL, | ||
1057 | 273 | region=DEFAULT_REGION, | ||
1058 | 274 | access_key=None, | ||
1059 | 275 | secret_key=None, | ||
1060 | 276 | **kwargs): | ||
1061 | 277 | parts = self.split_clc_url(clc_url) | ||
1062 | 278 | |||
1063 | 279 | self.clc_url = clc_url | ||
1064 | 280 | self.region = region | ||
1065 | 281 | self.access = access_key | ||
1066 | 282 | self.secret = secret_key | ||
1067 | 283 | self.apiconn = boto.connect_ec2(aws_access_key_id=access_key, | ||
1068 | 284 | aws_secret_access_key=secret_key, | ||
1069 | 285 | is_secure=parts['is_secure'], | ||
1070 | 286 | region=RegionInfo(None, | ||
1071 | 287 | region, | ||
1072 | 288 | parts['ip']), | ||
1073 | 289 | port=parts['port'], | ||
1074 | 290 | path='/services/Admin', | ||
1075 | 291 | **kwargs) | ||
1076 | 292 | self.apiconn.APIVersion = 'nova' | ||
1077 | 293 | |||
1078 | 294 | def connection_for(self, username, project, clc_url=None, region=None, | ||
1079 | 295 | **kwargs): | ||
1080 | 296 | """Returns a boto ec2 connection for the given username.""" | ||
1081 | 297 | if not clc_url: | ||
1082 | 298 | clc_url = self.clc_url | ||
1083 | 299 | if not region: | ||
1084 | 300 | region = self.region | ||
1085 | 301 | parts = self.split_clc_url(clc_url) | ||
1086 | 302 | user = self.get_user(username) | ||
1087 | 303 | access_key = '%s:%s' % (user.accesskey, project) | ||
1088 | 304 | return boto.connect_ec2(aws_access_key_id=access_key, | ||
1089 | 305 | aws_secret_access_key=user.secretkey, | ||
1090 | 306 | is_secure=parts['is_secure'], | ||
1091 | 307 | region=RegionInfo(None, | ||
1092 | 308 | self.region, | ||
1093 | 309 | parts['ip']), | ||
1094 | 310 | port=parts['port'], | ||
1095 | 311 | path='/services/Cloud', | ||
1096 | 312 | **kwargs) | ||
1097 | 313 | |||
1098 | 314 | def split_clc_url(self, clc_url): | ||
1099 | 315 | """Splits a cloud controller endpoint url.""" | ||
1100 | 316 | parts = httplib.urlsplit(clc_url) | ||
1101 | 317 | is_secure = parts.scheme == 'https' | ||
1102 | 318 | ip, port = parts.netloc.split(':') | ||
1103 | 319 | return {'ip': ip, 'port': int(port), 'is_secure': is_secure} | ||
1104 | 320 | |||
1105 | 321 | def get_users(self): | ||
1106 | 322 | """Grabs the list of all users.""" | ||
1107 | 323 | return self.apiconn.get_list('DescribeUsers', {}, [('item', UserInfo)]) | ||
1108 | 324 | |||
1109 | 325 | def get_user(self, name): | ||
1110 | 326 | """Grab a single user by name.""" | ||
1111 | 327 | try: | ||
1112 | 328 | return self.apiconn.get_object('DescribeUser', | ||
1113 | 329 | {'Name': name}, | ||
1114 | 330 | UserInfo) | ||
1115 | 331 | except boto.exception.BotoServerError, e: | ||
1116 | 332 | if e.status == 400 and e.error_code == 'NotFound': | ||
1117 | 333 | return None | ||
1118 | 334 | raise | ||
1119 | 335 | |||
1120 | 336 | def has_user(self, username): | ||
1121 | 337 | """Determine if user exists.""" | ||
1122 | 338 | return self.get_user(username) != None | ||
1123 | 339 | |||
1124 | 340 | def create_user(self, username): | ||
1125 | 341 | """Creates a new user, returning the userinfo object with | ||
1126 | 342 | access/secret.""" | ||
1127 | 343 | return self.apiconn.get_object('RegisterUser', {'Name': username}, | ||
1128 | 344 | UserInfo) | ||
1129 | 345 | |||
1130 | 346 | def delete_user(self, username): | ||
1131 | 347 | """Deletes a user.""" | ||
1132 | 348 | return self.apiconn.get_object('DeregisterUser', {'Name': username}, | ||
1133 | 349 | UserInfo) | ||
1134 | 350 | |||
1135 | 351 | def get_roles(self, project_roles=True): | ||
1136 | 352 | """Returns a list of available roles.""" | ||
1137 | 353 | return self.apiconn.get_list('DescribeRoles', | ||
1138 | 354 | {'ProjectRoles': project_roles}, | ||
1139 | 355 | [('item', UserRole)]) | ||
1140 | 356 | |||
1141 | 357 | def get_user_roles(self, user, project=None): | ||
1142 | 358 | """Returns a list of roles for the given user. | ||
1143 | 359 | |||
1144 | 360 | Omitting project will return any global roles that the user has. | ||
1145 | 361 | Specifying project will return only project specific roles. | ||
1146 | 362 | |||
1147 | 363 | """ | ||
1148 | 364 | params = {'User': user} | ||
1149 | 365 | if project: | ||
1150 | 366 | params['Project'] = project | ||
1151 | 367 | return self.apiconn.get_list('DescribeUserRoles', | ||
1152 | 368 | params, | ||
1153 | 369 | [('item', UserRole)]) | ||
1154 | 370 | |||
1155 | 371 | def add_user_role(self, user, role, project=None): | ||
1156 | 372 | """Add a role to a user either globally or for a specific project.""" | ||
1157 | 373 | return self.modify_user_role(user, role, project=project, | ||
1158 | 374 | operation='add') | ||
1159 | 375 | |||
1160 | 376 | def remove_user_role(self, user, role, project=None): | ||
1161 | 377 | """Remove a role from a user either globally or for a specific | ||
1162 | 378 | project.""" | ||
1163 | 379 | return self.modify_user_role(user, role, project=project, | ||
1164 | 380 | operation='remove') | ||
1165 | 381 | |||
1166 | 382 | def modify_user_role(self, user, role, project=None, operation='add', | ||
1167 | 383 | **kwargs): | ||
1168 | 384 | """Add or remove a role for a user and project.""" | ||
1169 | 385 | params = {'User': user, | ||
1170 | 386 | 'Role': role, | ||
1171 | 387 | 'Project': project, | ||
1172 | 388 | 'Operation': operation} | ||
1173 | 389 | return self.apiconn.get_status('ModifyUserRole', params) | ||
1174 | 390 | |||
1175 | 391 | def get_projects(self, user=None): | ||
1176 | 392 | """Returns a list of all projects.""" | ||
1177 | 393 | if user: | ||
1178 | 394 | params = {'User': user} | ||
1179 | 395 | else: | ||
1180 | 396 | params = {} | ||
1181 | 397 | return self.apiconn.get_list('DescribeProjects', | ||
1182 | 398 | params, | ||
1183 | 399 | [('item', ProjectInfo)]) | ||
1184 | 400 | |||
1185 | 401 | def get_project(self, name): | ||
1186 | 402 | """Returns a single project with the specified name.""" | ||
1187 | 403 | project = self.apiconn.get_object('DescribeProject', | ||
1188 | 404 | {'Name': name}, | ||
1189 | 405 | ProjectInfo) | ||
1190 | 406 | |||
1191 | 407 | if project.projectname != None: | ||
1192 | 408 | return project | ||
1193 | 409 | |||
1194 | 410 | def create_project(self, projectname, manager_user, description=None, | ||
1195 | 411 | member_users=None): | ||
1196 | 412 | """Creates a new project.""" | ||
1197 | 413 | params = {'Name': projectname, | ||
1198 | 414 | 'ManagerUser': manager_user, | ||
1199 | 415 | 'Description': description, | ||
1200 | 416 | 'MemberUsers': member_users} | ||
1201 | 417 | return self.apiconn.get_object('RegisterProject', params, ProjectInfo) | ||
1202 | 418 | |||
1203 | 419 | def modify_project(self, projectname, manager_user=None, description=None): | ||
1204 | 420 | """Modifies an existing project.""" | ||
1205 | 421 | params = {'Name': projectname, | ||
1206 | 422 | 'ManagerUser': manager_user, | ||
1207 | 423 | 'Description': description} | ||
1208 | 424 | return self.apiconn.get_status('ModifyProject', params) | ||
1209 | 425 | |||
1210 | 426 | def delete_project(self, projectname): | ||
1211 | 427 | """Permanently deletes the specified project.""" | ||
1212 | 428 | return self.apiconn.get_object('DeregisterProject', | ||
1213 | 429 | {'Name': projectname}, | ||
1214 | 430 | ProjectInfo) | ||
1215 | 431 | |||
1216 | 432 | def get_project_members(self, name): | ||
1217 | 433 | """Returns a list of members of a project.""" | ||
1218 | 434 | return self.apiconn.get_list('DescribeProjectMembers', | ||
1219 | 435 | {'Name': name}, | ||
1220 | 436 | [('item', ProjectMember)]) | ||
1221 | 437 | |||
1222 | 438 | def add_project_member(self, user, project): | ||
1223 | 439 | """Adds a user to a project.""" | ||
1224 | 440 | return self.modify_project_member(user, project, operation='add') | ||
1225 | 441 | |||
1226 | 442 | def remove_project_member(self, user, project): | ||
1227 | 443 | """Removes a user from a project.""" | ||
1228 | 444 | return self.modify_project_member(user, project, operation='remove') | ||
1229 | 445 | |||
1230 | 446 | def modify_project_member(self, user, project, operation='add'): | ||
1231 | 447 | """Adds or removes a user from a project.""" | ||
1232 | 448 | params = {'User': user, | ||
1233 | 449 | 'Project': project, | ||
1234 | 450 | 'Operation': operation} | ||
1235 | 451 | return self.apiconn.get_status('ModifyProjectMember', params) | ||
1236 | 452 | |||
1237 | 453 | def get_zip(self, user, project): | ||
1238 | 454 | """Returns the content of a zip file containing novarc and access | ||
1239 | 455 | credentials.""" | ||
1240 | 456 | params = {'Name': user, 'Project': project} | ||
1241 | 457 | zip = self.apiconn.get_object('GenerateX509ForUser', params, UserInfo) | ||
1242 | 458 | return zip.file | ||
1243 | 459 | |||
1244 | 460 | def start_vpn(self, project): | ||
1245 | 461 | """ | ||
1246 | 462 | Starts the vpn for a user | ||
1247 | 463 | """ | ||
1248 | 464 | return self.apiconn.get_object('StartVpn', {'Project': project}, Vpn) | ||
1249 | 465 | |||
1250 | 466 | def get_vpns(self): | ||
1251 | 467 | """Return a list of vpn with project name""" | ||
1252 | 468 | return self.apiconn.get_list('DescribeVpns', {}, [('item', Vpn)]) | ||
1253 | 469 | |||
1254 | 470 | def get_hosts(self): | ||
1255 | 471 | return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)]) | ||
1256 | 472 | |||
1257 | 473 | def get_instance_types(self): | ||
1258 | 474 | """Grabs the list of all users.""" | ||
1259 | 475 | return self.apiconn.get_list('DescribeInstanceTypes', {}, | ||
1260 | 476 | [('item', InstanceType)]) | ||
1261 | 477 | 0 | ||
1262 | === modified file 'nova/api/direct.py' | |||
1263 | --- nova/api/direct.py 2011-03-24 20:20:15 +0000 | |||
1264 | +++ nova/api/direct.py 2011-04-12 11:01:25 +0000 | |||
1265 | @@ -206,10 +206,14 @@ | |||
1266 | 206 | # 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. |
1267 | 207 | params = dict([(str(k), v) for (k, v) in params.iteritems()]) | 207 | params = dict([(str(k), v) for (k, v) in params.iteritems()]) |
1268 | 208 | result = method(context, **params) | 208 | result = method(context, **params) |
1269 | 209 | |||
1270 | 209 | if result is None or type(result) is str or type(result) is unicode: | 210 | if result is None or type(result) is str or type(result) is unicode: |
1271 | 210 | return result | 211 | return result |
1272 | 212 | |||
1273 | 211 | try: | 213 | try: |
1275 | 212 | return self._serialize(result, req.best_match_content_type()) | 214 | content_type = req.best_match_content_type() |
1276 | 215 | default_xmlns = self.get_default_xmlns(req) | ||
1277 | 216 | return self._serialize(result, content_type, default_xmlns) | ||
1278 | 213 | except: | 217 | except: |
1279 | 214 | raise exception.Error("returned non-serializable type: %s" | 218 | raise exception.Error("returned non-serializable type: %s" |
1280 | 215 | % result) | 219 | % result) |
1281 | 216 | 220 | ||
1282 | === modified file 'nova/api/ec2/cloud.py' | |||
1283 | --- nova/api/ec2/cloud.py 2011-04-07 08:40:22 +0000 | |||
1284 | +++ nova/api/ec2/cloud.py 2011-04-12 11:01:25 +0000 | |||
1285 | @@ -103,10 +103,18 @@ | |||
1286 | 103 | # Gen root CA, if we don't have one | 103 | # Gen root CA, if we don't have one |
1287 | 104 | root_ca_path = os.path.join(FLAGS.ca_path, FLAGS.ca_file) | 104 | root_ca_path = os.path.join(FLAGS.ca_path, FLAGS.ca_file) |
1288 | 105 | if not os.path.exists(root_ca_path): | 105 | if not os.path.exists(root_ca_path): |
1289 | 106 | genrootca_sh_path = os.path.join(os.path.dirname(__file__), | ||
1290 | 107 | os.path.pardir, | ||
1291 | 108 | os.path.pardir, | ||
1292 | 109 | 'CA', | ||
1293 | 110 | 'genrootca.sh') | ||
1294 | 111 | |||
1295 | 106 | start = os.getcwd() | 112 | start = os.getcwd() |
1296 | 113 | if not os.path.exists(FLAGS.ca_path): | ||
1297 | 114 | os.makedirs(FLAGS.ca_path) | ||
1298 | 107 | os.chdir(FLAGS.ca_path) | 115 | os.chdir(FLAGS.ca_path) |
1299 | 108 | # TODO(vish): Do this with M2Crypto instead | 116 | # TODO(vish): Do this with M2Crypto instead |
1301 | 109 | utils.runthis(_("Generating root CA: %s"), "sh", "genrootca.sh") | 117 | utils.runthis(_("Generating root CA: %s"), "sh", genrootca_sh_path) |
1302 | 110 | os.chdir(start) | 118 | os.chdir(start) |
1303 | 111 | 119 | ||
1304 | 112 | def _get_mpi_data(self, context, project_id): | 120 | def _get_mpi_data(self, context, project_id): |
1305 | @@ -134,6 +142,11 @@ | |||
1306 | 134 | instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address) | 142 | instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address) |
1307 | 135 | if instance_ref is None: | 143 | if instance_ref is None: |
1308 | 136 | return None | 144 | return None |
1309 | 145 | |||
1310 | 146 | # This ensures that all attributes of the instance | ||
1311 | 147 | # are populated. | ||
1312 | 148 | instance_ref = db.instance_get(ctxt, instance_ref['id']) | ||
1313 | 149 | |||
1314 | 137 | mpi = self._get_mpi_data(ctxt, instance_ref['project_id']) | 150 | mpi = self._get_mpi_data(ctxt, instance_ref['project_id']) |
1315 | 138 | if instance_ref['key_name']: | 151 | if instance_ref['key_name']: |
1316 | 139 | keys = {'0': {'_name': instance_ref['key_name'], | 152 | keys = {'0': {'_name': instance_ref['key_name'], |
1317 | @@ -156,7 +169,7 @@ | |||
1318 | 156 | floating_ip = net['IPs'][0]['floating_ips'][0] | 169 | floating_ip = net['IPs'][0]['floating_ips'][0] |
1319 | 157 | 170 | ||
1320 | 158 | ec2_id = ec2utils.id_to_ec2_id(instance_ref['id']) | 171 | ec2_id = ec2utils.id_to_ec2_id(instance_ref['id']) |
1322 | 159 | image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'machine') | 172 | image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'ami') |
1323 | 160 | data = { | 173 | data = { |
1324 | 161 | 'user-data': base64.b64decode(instance_ref['user_data']), | 174 | 'user-data': base64.b64decode(instance_ref['user_data']), |
1325 | 162 | 'meta-data': { | 175 | 'meta-data': { |
1326 | @@ -186,7 +199,7 @@ | |||
1327 | 186 | for image_type in ['kernel', 'ramdisk']: | 199 | for image_type in ['kernel', 'ramdisk']: |
1328 | 187 | if '%s_id' % image_type in instance_ref: | 200 | if '%s_id' % image_type in instance_ref: |
1329 | 188 | ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type], | 201 | ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type], |
1331 | 189 | image_type) | 202 | self._image_type(image_type)) |
1332 | 190 | data['meta-data']['%s-id' % image_type] = ec2_id | 203 | data['meta-data']['%s-id' % image_type] = ec2_id |
1333 | 191 | 204 | ||
1334 | 192 | if False: # TODO(vish): store ancestor ids | 205 | if False: # TODO(vish): store ancestor ids |
1335 | @@ -546,6 +559,13 @@ | |||
1336 | 546 | return self.compute_api.get_ajax_console(context, | 559 | return self.compute_api.get_ajax_console(context, |
1337 | 547 | instance_id=instance_id) | 560 | instance_id=instance_id) |
1338 | 548 | 561 | ||
1339 | 562 | def get_vnc_console(self, context, instance_id, **kwargs): | ||
1340 | 563 | """Returns vnc browser url. Used by OS dashboard.""" | ||
1341 | 564 | ec2_id = instance_id | ||
1342 | 565 | instance_id = ec2utils.ec2_id_to_id(ec2_id) | ||
1343 | 566 | return self.compute_api.get_vnc_console(context, | ||
1344 | 567 | instance_id=instance_id) | ||
1345 | 568 | |||
1346 | 549 | def describe_volumes(self, context, volume_id=None, **kwargs): | 569 | def describe_volumes(self, context, volume_id=None, **kwargs): |
1347 | 550 | if volume_id: | 570 | if volume_id: |
1348 | 551 | volumes = [] | 571 | volumes = [] |
1349 | @@ -734,7 +754,10 @@ | |||
1350 | 734 | instance['project_id'], | 754 | instance['project_id'], |
1351 | 735 | instance['host']) | 755 | instance['host']) |
1352 | 736 | i['productCodesSet'] = self._convert_to_set([], 'product_codes') | 756 | i['productCodesSet'] = self._convert_to_set([], 'product_codes') |
1354 | 737 | i['instanceType'] = instance['instance_type'] | 757 | if instance['instance_type']: |
1355 | 758 | i['instanceType'] = instance['instance_type'].get('name') | ||
1356 | 759 | else: | ||
1357 | 760 | i['instanceType'] = None | ||
1358 | 738 | i['launchTime'] = instance['created_at'] | 761 | i['launchTime'] = instance['created_at'] |
1359 | 739 | i['amiLaunchIndex'] = instance['launch_index'] | 762 | i['amiLaunchIndex'] = instance['launch_index'] |
1360 | 740 | i['displayName'] = instance['display_name'] | 763 | i['displayName'] = instance['display_name'] |
1361 | @@ -763,6 +786,7 @@ | |||
1362 | 763 | 786 | ||
1363 | 764 | def format_addresses(self, context): | 787 | def format_addresses(self, context): |
1364 | 765 | addresses = [] | 788 | addresses = [] |
1365 | 789 | |||
1366 | 766 | net_factory = net_service.get_service_factory(context, | 790 | net_factory = net_service.get_service_factory(context, |
1367 | 767 | context.project_id) | 791 | context.project_id) |
1368 | 768 | net_api = net_factory.get_api_service() | 792 | net_api = net_factory.get_api_service() |
1369 | @@ -770,6 +794,9 @@ | |||
1370 | 770 | ip_info_list = net_api.get_addresses(context, project_id) | 794 | ip_info_list = net_api.get_addresses(context, project_id) |
1371 | 771 | 795 | ||
1372 | 772 | for ip_info in ip_info_list: | 796 | for ip_info in ip_info_list: |
1373 | 797 | if ip_info['project_id'] is None: | ||
1374 | 798 | continue | ||
1375 | 799 | |||
1376 | 773 | ec2_id = None | 800 | ec2_id = None |
1377 | 774 | if ip_info['vnic_id']: | 801 | if ip_info['vnic_id']: |
1378 | 775 | instance = db.instance_get_by_virtual_nic(context, | 802 | instance = db.instance_get_by_virtual_nic(context, |
1379 | @@ -788,6 +815,7 @@ | |||
1380 | 788 | 815 | ||
1381 | 789 | def allocate_address(self, context, **kwargs): | 816 | def allocate_address(self, context, **kwargs): |
1382 | 790 | LOG.audit(_("Allocate address"), context=context) | 817 | LOG.audit(_("Allocate address"), context=context) |
1383 | 818 | |||
1384 | 791 | net_factory = net_service.get_service_factory(context, | 819 | net_factory = net_service.get_service_factory(context, |
1385 | 792 | context.project_id) | 820 | context.project_id) |
1386 | 793 | net_api_service = net_factory.get_api_service() | 821 | net_api_service = net_factory.get_api_service() |
1387 | @@ -802,7 +830,7 @@ | |||
1388 | 802 | context.project_id) | 830 | context.project_id) |
1389 | 803 | raise ex | 831 | raise ex |
1390 | 804 | 832 | ||
1392 | 805 | return {'addressSet': [{'publicIp': public_ip}]} | 833 | return {'publicIp': public_ip} |
1393 | 806 | 834 | ||
1394 | 807 | def release_address(self, context, public_ip, **kwargs): | 835 | def release_address(self, context, public_ip, **kwargs): |
1395 | 808 | LOG.audit(_("Release address %s"), public_ip, context=context) | 836 | LOG.audit(_("Release address %s"), public_ip, context=context) |
1396 | @@ -841,7 +869,7 @@ | |||
1397 | 841 | ramdisk = self._get_image(context, kwargs['ramdisk_id']) | 869 | ramdisk = self._get_image(context, kwargs['ramdisk_id']) |
1398 | 842 | kwargs['ramdisk_id'] = ramdisk['id'] | 870 | kwargs['ramdisk_id'] = ramdisk['id'] |
1399 | 843 | instances = self.compute_api.create(context, | 871 | instances = self.compute_api.create(context, |
1401 | 844 | instance_type=instance_types.get_by_type( | 872 | instance_type=instance_types.get_instance_type_by_name( |
1402 | 845 | kwargs.get('instance_type', None)), | 873 | kwargs.get('instance_type', None)), |
1403 | 846 | image_id=self._get_image(context, kwargs['image_id'])['id'], | 874 | image_id=self._get_image(context, kwargs['image_id'])['id'], |
1404 | 847 | min_count=int(kwargs.get('min_count', max_count)), | 875 | min_count=int(kwargs.get('min_count', max_count)), |
1405 | @@ -898,13 +926,27 @@ | |||
1406 | 898 | self.compute_api.update(context, instance_id=instance_id, **kwargs) | 926 | self.compute_api.update(context, instance_id=instance_id, **kwargs) |
1407 | 899 | return True | 927 | return True |
1408 | 900 | 928 | ||
1416 | 901 | _type_prefix_map = {'machine': 'ami', | 929 | @staticmethod |
1417 | 902 | 'kernel': 'aki', | 930 | def _image_type(image_type): |
1418 | 903 | 'ramdisk': 'ari'} | 931 | """Converts to a three letter image type. |
1419 | 904 | 932 | ||
1420 | 905 | def _image_ec2_id(self, image_id, image_type='machine'): | 933 | aki, kernel => aki |
1421 | 906 | prefix = self._type_prefix_map[image_type] | 934 | ari, ramdisk => ari |
1422 | 907 | template = prefix + '-%08x' | 935 | anything else => ami |
1423 | 936 | |||
1424 | 937 | """ | ||
1425 | 938 | if image_type == 'kernel': | ||
1426 | 939 | return 'aki' | ||
1427 | 940 | if image_type == 'ramdisk': | ||
1428 | 941 | return 'ari' | ||
1429 | 942 | if image_type not in ['aki', 'ari']: | ||
1430 | 943 | return 'ami' | ||
1431 | 944 | return image_type | ||
1432 | 945 | |||
1433 | 946 | @staticmethod | ||
1434 | 947 | def _image_ec2_id(image_id, image_type='ami'): | ||
1435 | 948 | """Returns image ec2_id using id and three letter type.""" | ||
1436 | 949 | template = image_type + '-%08x' | ||
1437 | 908 | return ec2utils.id_to_ec2_id(int(image_id), template=template) | 950 | return ec2utils.id_to_ec2_id(int(image_id), template=template) |
1438 | 909 | 951 | ||
1439 | 910 | def _get_image(self, context, ec2_id): | 952 | def _get_image(self, context, ec2_id): |
1440 | @@ -917,24 +959,34 @@ | |||
1441 | 917 | def _format_image(self, image): | 959 | def _format_image(self, image): |
1442 | 918 | """Convert from format defined by BaseImageService to S3 format.""" | 960 | """Convert from format defined by BaseImageService to S3 format.""" |
1443 | 919 | i = {} | 961 | i = {} |
1445 | 920 | image_type = image['properties'].get('type') | 962 | image_type = self._image_type(image.get('container_format')) |
1446 | 921 | ec2_id = self._image_ec2_id(image.get('id'), image_type) | 963 | ec2_id = self._image_ec2_id(image.get('id'), image_type) |
1447 | 922 | name = image.get('name') | 964 | name = image.get('name') |
1452 | 923 | if name: | 965 | i['imageId'] = ec2_id |
1449 | 924 | i['imageId'] = "%s (%s)" % (ec2_id, name) | ||
1450 | 925 | else: | ||
1451 | 926 | i['imageId'] = ec2_id | ||
1453 | 927 | kernel_id = image['properties'].get('kernel_id') | 966 | kernel_id = image['properties'].get('kernel_id') |
1454 | 928 | if kernel_id: | 967 | if kernel_id: |
1456 | 929 | i['kernelId'] = self._image_ec2_id(kernel_id, 'kernel') | 968 | i['kernelId'] = self._image_ec2_id(kernel_id, 'aki') |
1457 | 930 | ramdisk_id = image['properties'].get('ramdisk_id') | 969 | ramdisk_id = image['properties'].get('ramdisk_id') |
1458 | 931 | if ramdisk_id: | 970 | if ramdisk_id: |
1460 | 932 | i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ramdisk') | 971 | i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ari') |
1461 | 933 | i['imageOwnerId'] = image['properties'].get('owner_id') | 972 | i['imageOwnerId'] = image['properties'].get('owner_id') |
1466 | 934 | i['imageLocation'] = image['properties'].get('image_location') | 973 | if name: |
1467 | 935 | i['imageState'] = image['properties'].get('image_state') | 974 | i['imageLocation'] = "%s (%s)" % (image['properties']. |
1468 | 936 | i['type'] = image_type | 975 | get('image_location'), name) |
1469 | 937 | i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True' | 976 | else: |
1470 | 977 | i['imageLocation'] = image['properties'].get('image_location') | ||
1471 | 978 | # NOTE(vish): fallback status if image_state isn't set | ||
1472 | 979 | state = image.get('status') | ||
1473 | 980 | if state == 'active': | ||
1474 | 981 | state = 'available' | ||
1475 | 982 | i['imageState'] = image['properties'].get('image_state', state) | ||
1476 | 983 | i['displayName'] = name | ||
1477 | 984 | i['description'] = image.get('description') | ||
1478 | 985 | display_mapping = {'aki': 'kernel', | ||
1479 | 986 | 'ari': 'ramdisk', | ||
1480 | 987 | 'ami': 'machine'} | ||
1481 | 988 | i['imageType'] = display_mapping.get(image_type) | ||
1482 | 989 | i['isPublic'] = image.get('is_public') == True | ||
1483 | 938 | i['architecture'] = image['properties'].get('architecture') | 990 | i['architecture'] = image['properties'].get('architecture') |
1484 | 939 | return i | 991 | return i |
1485 | 940 | 992 | ||
1486 | @@ -966,8 +1018,9 @@ | |||
1487 | 966 | image_location = kwargs['name'] | 1018 | image_location = kwargs['name'] |
1488 | 967 | metadata = {'properties': {'image_location': image_location}} | 1019 | metadata = {'properties': {'image_location': image_location}} |
1489 | 968 | image = self.image_service.create(context, metadata) | 1020 | image = self.image_service.create(context, metadata) |
1490 | 1021 | image_type = self._image_type(image.get('container_format')) | ||
1491 | 969 | image_id = self._image_ec2_id(image['id'], | 1022 | image_id = self._image_ec2_id(image['id'], |
1493 | 970 | image['properties']['type']) | 1023 | image_type) |
1494 | 971 | msg = _("Registered image %(image_location)s with" | 1024 | msg = _("Registered image %(image_location)s with" |
1495 | 972 | " id %(image_id)s") % locals() | 1025 | " id %(image_id)s") % locals() |
1496 | 973 | LOG.audit(msg, context=context) | 1026 | LOG.audit(msg, context=context) |
1497 | @@ -982,7 +1035,7 @@ | |||
1498 | 982 | except exception.NotFound: | 1035 | except exception.NotFound: |
1499 | 983 | raise exception.NotFound(_('Image %s not found') % image_id) | 1036 | raise exception.NotFound(_('Image %s not found') % image_id) |
1500 | 984 | result = {'imageId': image_id, 'launchPermission': []} | 1037 | result = {'imageId': image_id, 'launchPermission': []} |
1502 | 985 | if image['properties']['is_public']: | 1038 | if image['is_public']: |
1503 | 986 | result['launchPermission'].append({'group': 'all'}) | 1039 | result['launchPermission'].append({'group': 'all'}) |
1504 | 987 | return result | 1040 | return result |
1505 | 988 | 1041 | ||
1506 | @@ -1007,7 +1060,7 @@ | |||
1507 | 1007 | internal_id = image['id'] | 1060 | internal_id = image['id'] |
1508 | 1008 | del(image['id']) | 1061 | del(image['id']) |
1509 | 1009 | 1062 | ||
1511 | 1010 | image['properties']['is_public'] = (operation_type == 'add') | 1063 | image['is_public'] = (operation_type == 'add') |
1512 | 1011 | return self.image_service.update(context, internal_id, image) | 1064 | return self.image_service.update(context, internal_id, image) |
1513 | 1012 | 1065 | ||
1514 | 1013 | def update_image(self, context, image_id, **kwargs): | 1066 | def update_image(self, context, image_id, **kwargs): |
1515 | 1014 | 1067 | ||
1516 | === modified file 'nova/api/openstack/__init__.py' | |||
1517 | --- nova/api/openstack/__init__.py 2011-04-07 01:08:35 +0000 | |||
1518 | +++ nova/api/openstack/__init__.py 2011-04-12 11:01:25 +0000 | |||
1519 | @@ -36,6 +36,7 @@ | |||
1520 | 36 | from nova.api.openstack import flavors | 36 | from nova.api.openstack import flavors |
1521 | 37 | from nova.api.openstack import images | 37 | from nova.api.openstack import images |
1522 | 38 | from nova.api.openstack import image_metadata | 38 | from nova.api.openstack import image_metadata |
1523 | 39 | from nova.api.openstack import ips | ||
1524 | 39 | from nova.api.openstack import limits | 40 | from nova.api.openstack import limits |
1525 | 40 | from nova.api.openstack import servers | 41 | from nova.api.openstack import servers |
1526 | 41 | from nova.api.openstack import server_metadata | 42 | from nova.api.openstack import server_metadata |
1527 | @@ -108,23 +109,11 @@ | |||
1528 | 108 | controller=accounts.Controller(), | 109 | controller=accounts.Controller(), |
1529 | 109 | collection={'detail': 'GET'}) | 110 | collection={'detail': 'GET'}) |
1530 | 110 | 111 | ||
1531 | 111 | mapper.resource("backup_schedule", "backup_schedule", | ||
1532 | 112 | controller=backup_schedules.Controller(), | ||
1533 | 113 | parent_resource=dict(member_name='server', | ||
1534 | 114 | collection_name='servers')) | ||
1535 | 115 | |||
1536 | 116 | mapper.resource("console", "consoles", | 112 | mapper.resource("console", "consoles", |
1537 | 117 | controller=consoles.Controller(), | 113 | controller=consoles.Controller(), |
1538 | 118 | parent_resource=dict(member_name='server', | 114 | parent_resource=dict(member_name='server', |
1539 | 119 | collection_name='servers')) | 115 | collection_name='servers')) |
1540 | 120 | 116 | ||
1541 | 121 | mapper.resource("image", "images", controller=images.Controller(), | ||
1542 | 122 | collection={'detail': 'GET'}) | ||
1543 | 123 | |||
1544 | 124 | mapper.resource("shared_ip_group", "shared_ip_groups", | ||
1545 | 125 | collection={'detail': 'GET'}, | ||
1546 | 126 | controller=shared_ip_groups.Controller()) | ||
1547 | 127 | |||
1548 | 128 | _limits = limits.LimitsController() | 117 | _limits = limits.LimitsController() |
1549 | 129 | mapper.resource("limit", "limits", controller=_limits) | 118 | mapper.resource("limit", "limits", controller=_limits) |
1550 | 130 | 119 | ||
1551 | @@ -144,10 +133,28 @@ | |||
1552 | 144 | collection={'detail': 'GET'}, | 133 | collection={'detail': 'GET'}, |
1553 | 145 | member=self.server_members) | 134 | member=self.server_members) |
1554 | 146 | 135 | ||
1555 | 136 | mapper.resource("image", "images", | ||
1556 | 137 | controller=images.ControllerV10(), | ||
1557 | 138 | collection={'detail': 'GET'}) | ||
1558 | 139 | |||
1559 | 147 | mapper.resource("flavor", "flavors", | 140 | mapper.resource("flavor", "flavors", |
1560 | 148 | controller=flavors.ControllerV10(), | 141 | controller=flavors.ControllerV10(), |
1561 | 149 | collection={'detail': 'GET'}) | 142 | collection={'detail': 'GET'}) |
1562 | 150 | 143 | ||
1563 | 144 | mapper.resource("shared_ip_group", "shared_ip_groups", | ||
1564 | 145 | collection={'detail': 'GET'}, | ||
1565 | 146 | controller=shared_ip_groups.Controller()) | ||
1566 | 147 | |||
1567 | 148 | mapper.resource("backup_schedule", "backup_schedule", | ||
1568 | 149 | controller=backup_schedules.Controller(), | ||
1569 | 150 | parent_resource=dict(member_name='server', | ||
1570 | 151 | collection_name='servers')) | ||
1571 | 152 | |||
1572 | 153 | mapper.resource("ip", "ips", controller=ips.Controller(), | ||
1573 | 154 | collection=dict(public='GET', private='GET'), | ||
1574 | 155 | parent_resource=dict(member_name='server', | ||
1575 | 156 | collection_name='servers')) | ||
1576 | 157 | |||
1577 | 151 | 158 | ||
1578 | 152 | class APIRouterV11(APIRouter): | 159 | class APIRouterV11(APIRouter): |
1579 | 153 | """Define routes specific to OpenStack API V1.1.""" | 160 | """Define routes specific to OpenStack API V1.1.""" |
1580 | @@ -159,6 +166,10 @@ | |||
1581 | 159 | collection={'detail': 'GET'}, | 166 | collection={'detail': 'GET'}, |
1582 | 160 | member=self.server_members) | 167 | member=self.server_members) |
1583 | 161 | 168 | ||
1584 | 169 | mapper.resource("image", "images", | ||
1585 | 170 | controller=images.ControllerV11(), | ||
1586 | 171 | collection={'detail': 'GET'}) | ||
1587 | 172 | |||
1588 | 162 | mapper.resource("image_meta", "meta", | 173 | mapper.resource("image_meta", "meta", |
1589 | 163 | controller=image_metadata.Controller(), | 174 | controller=image_metadata.Controller(), |
1590 | 164 | parent_resource=dict(member_name='image', | 175 | parent_resource=dict(member_name='image', |
1591 | 165 | 176 | ||
1592 | === modified file 'nova/api/openstack/accounts.py' | |||
1593 | --- nova/api/openstack/accounts.py 2011-03-18 14:34:08 +0000 | |||
1594 | +++ nova/api/openstack/accounts.py 2011-04-12 11:01:25 +0000 | |||
1595 | @@ -13,15 +13,14 @@ | |||
1596 | 13 | # License for the specific language governing permissions and limitations | 13 | # License for the specific language governing permissions and limitations |
1597 | 14 | # under the License. | 14 | # under the License. |
1598 | 15 | 15 | ||
1599 | 16 | import common | ||
1600 | 17 | import webob.exc | 16 | import webob.exc |
1601 | 18 | 17 | ||
1602 | 19 | from nova import exception | 18 | from nova import exception |
1603 | 20 | from nova import flags | 19 | from nova import flags |
1604 | 21 | from nova import log as logging | 20 | from nova import log as logging |
1605 | 22 | from nova import wsgi | ||
1606 | 23 | 21 | ||
1607 | 24 | from nova.auth import manager | 22 | from nova.auth import manager |
1608 | 23 | from nova.api.openstack import common | ||
1609 | 25 | from nova.api.openstack import faults | 24 | from nova.api.openstack import faults |
1610 | 26 | 25 | ||
1611 | 27 | FLAGS = flags.FLAGS | 26 | FLAGS = flags.FLAGS |
1612 | @@ -35,7 +34,7 @@ | |||
1613 | 35 | manager=account.project_manager_id) | 34 | manager=account.project_manager_id) |
1614 | 36 | 35 | ||
1615 | 37 | 36 | ||
1617 | 38 | class Controller(wsgi.Controller): | 37 | class Controller(common.OpenstackController): |
1618 | 39 | 38 | ||
1619 | 40 | _serialization_metadata = { | 39 | _serialization_metadata = { |
1620 | 41 | 'application/xml': { | 40 | 'application/xml': { |
1621 | 42 | 41 | ||
1622 | === modified file 'nova/api/openstack/backup_schedules.py' | |||
1623 | --- nova/api/openstack/backup_schedules.py 2011-03-09 18:10:45 +0000 | |||
1624 | +++ nova/api/openstack/backup_schedules.py 2011-04-12 11:01:25 +0000 | |||
1625 | @@ -19,7 +19,7 @@ | |||
1626 | 19 | 19 | ||
1627 | 20 | from webob import exc | 20 | from webob import exc |
1628 | 21 | 21 | ||
1630 | 22 | from nova import wsgi | 22 | from nova.api.openstack import common |
1631 | 23 | from nova.api.openstack import faults | 23 | from nova.api.openstack import faults |
1632 | 24 | import nova.image.service | 24 | import nova.image.service |
1633 | 25 | 25 | ||
1634 | @@ -29,7 +29,7 @@ | |||
1635 | 29 | return dict(backupSchedule=inst) | 29 | return dict(backupSchedule=inst) |
1636 | 30 | 30 | ||
1637 | 31 | 31 | ||
1639 | 32 | class Controller(wsgi.Controller): | 32 | class Controller(common.OpenstackController): |
1640 | 33 | """ The backup schedule API controller for the Openstack API """ | 33 | """ The backup schedule API controller for the Openstack API """ |
1641 | 34 | 34 | ||
1642 | 35 | _serialization_metadata = { | 35 | _serialization_metadata = { |
1643 | @@ -42,7 +42,11 @@ | |||
1644 | 42 | 42 | ||
1645 | 43 | def index(self, req, server_id): | 43 | def index(self, req, server_id): |
1646 | 44 | """ Returns the list of backup schedules for a given instance """ | 44 | """ Returns the list of backup schedules for a given instance """ |
1648 | 45 | return _translate_keys({}) | 45 | return faults.Fault(exc.HTTPNotImplemented()) |
1649 | 46 | |||
1650 | 47 | def show(self, req, server_id, id): | ||
1651 | 48 | """ Returns a single backup schedule for a given instance """ | ||
1652 | 49 | return faults.Fault(exc.HTTPNotImplemented()) | ||
1653 | 46 | 50 | ||
1654 | 47 | def create(self, req, server_id): | 51 | def create(self, req, server_id): |
1655 | 48 | """ No actual update method required, since the existing API allows | 52 | """ No actual update method required, since the existing API allows |
1656 | 49 | 53 | ||
1657 | === modified file 'nova/api/openstack/common.py' | |||
1658 | --- nova/api/openstack/common.py 2011-03-24 23:34:52 +0000 | |||
1659 | +++ nova/api/openstack/common.py 2011-04-12 11:01:25 +0000 | |||
1660 | @@ -21,10 +21,20 @@ | |||
1661 | 21 | 21 | ||
1662 | 22 | from nova import exception | 22 | from nova import exception |
1663 | 23 | from nova import flags | 23 | from nova import flags |
1664 | 24 | from nova import log as logging | ||
1665 | 25 | from nova import wsgi | ||
1666 | 26 | |||
1667 | 27 | |||
1668 | 28 | LOG = logging.getLogger('common') | ||
1669 | 29 | |||
1670 | 24 | 30 | ||
1671 | 25 | FLAGS = flags.FLAGS | 31 | FLAGS = flags.FLAGS |
1672 | 26 | 32 | ||
1673 | 27 | 33 | ||
1674 | 34 | XML_NS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' | ||
1675 | 35 | XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1' | ||
1676 | 36 | |||
1677 | 37 | |||
1678 | 28 | def limited(items, request, max_limit=FLAGS.osapi_max_limit): | 38 | def limited(items, request, max_limit=FLAGS.osapi_max_limit): |
1679 | 29 | """ | 39 | """ |
1680 | 30 | Return a slice of items according to requested offset and limit. | 40 | Return a slice of items according to requested offset and limit. |
1681 | @@ -121,4 +131,11 @@ | |||
1682 | 121 | try: | 131 | try: |
1683 | 122 | return int(urlparse(href).path.split('/')[-1]) | 132 | return int(urlparse(href).path.split('/')[-1]) |
1684 | 123 | except: | 133 | except: |
1685 | 134 | LOG.debug(_("Error extracting id from href: %s") % href) | ||
1686 | 124 | raise webob.exc.HTTPBadRequest(_('could not parse id from href')) | 135 | raise webob.exc.HTTPBadRequest(_('could not parse id from href')) |
1687 | 136 | |||
1688 | 137 | |||
1689 | 138 | class OpenstackController(wsgi.Controller): | ||
1690 | 139 | def get_default_xmlns(self, req): | ||
1691 | 140 | # Use V10 by default | ||
1692 | 141 | return XML_NS_V10 | ||
1693 | 125 | 142 | ||
1694 | === modified file 'nova/api/openstack/consoles.py' | |||
1695 | --- nova/api/openstack/consoles.py 2011-03-11 19:49:32 +0000 | |||
1696 | +++ nova/api/openstack/consoles.py 2011-04-12 11:01:25 +0000 | |||
1697 | @@ -19,7 +19,7 @@ | |||
1698 | 19 | 19 | ||
1699 | 20 | from nova import console | 20 | from nova import console |
1700 | 21 | from nova import exception | 21 | from nova import exception |
1702 | 22 | from nova import wsgi | 22 | from nova.api.openstack import common |
1703 | 23 | from nova.api.openstack import faults | 23 | from nova.api.openstack import faults |
1704 | 24 | 24 | ||
1705 | 25 | 25 | ||
1706 | @@ -43,7 +43,7 @@ | |||
1707 | 43 | return dict(console=info) | 43 | return dict(console=info) |
1708 | 44 | 44 | ||
1709 | 45 | 45 | ||
1711 | 46 | class Controller(wsgi.Controller): | 46 | class Controller(common.OpenstackController): |
1712 | 47 | """The Consoles Controller for the Openstack API""" | 47 | """The Consoles Controller for the Openstack API""" |
1713 | 48 | 48 | ||
1714 | 49 | _serialization_metadata = { | 49 | _serialization_metadata = { |
1715 | 50 | 50 | ||
1716 | === added directory 'nova/api/openstack/contrib' | |||
1717 | === added file 'nova/api/openstack/contrib/__init__.py' | |||
1718 | --- nova/api/openstack/contrib/__init__.py 1970-01-01 00:00:00 +0000 | |||
1719 | +++ nova/api/openstack/contrib/__init__.py 2011-04-12 11:01:25 +0000 | |||
1720 | @@ -0,0 +1,22 @@ | |||
1721 | 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||
1722 | 2 | |||
1723 | 3 | # Copyright 2011 Justin Santa Barbara | ||
1724 | 4 | # All Rights Reserved. | ||
1725 | 5 | # | ||
1726 | 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
1727 | 7 | # not use this file except in compliance with the License. You may obtain | ||
1728 | 8 | # a copy of the License at | ||
1729 | 9 | # | ||
1730 | 10 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
1731 | 11 | # | ||
1732 | 12 | # Unless required by applicable law or agreed to in writing, software | ||
1733 | 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
1734 | 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
1735 | 15 | # License for the specific language governing permissions and limitations | ||
1736 | 16 | # under the License.import datetime | ||
1737 | 17 | |||
1738 | 18 | """Contrib contains extensions that are shipped with nova. | ||
1739 | 19 | |||
1740 | 20 | It can't be called 'extensions' because that causes namespacing problems. | ||
1741 | 21 | |||
1742 | 22 | """ | ||
1743 | 0 | 23 | ||
1744 | === added file 'nova/api/openstack/contrib/volumes.py' | |||
1745 | --- nova/api/openstack/contrib/volumes.py 1970-01-01 00:00:00 +0000 | |||
1746 | +++ nova/api/openstack/contrib/volumes.py 2011-04-12 11:01:25 +0000 | |||
1747 | @@ -0,0 +1,336 @@ | |||
1748 | 1 | # Copyright 2011 Justin Santa Barbara | ||
1749 | 2 | # All Rights Reserved. | ||
1750 | 3 | # | ||
1751 | 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
1752 | 5 | # not use this file except in compliance with the License. You may obtain | ||
1753 | 6 | # a copy of the License at | ||
1754 | 7 | # | ||
1755 | 8 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
1756 | 9 | # | ||
1757 | 10 | # Unless required by applicable law or agreed to in writing, software | ||
1758 | 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
1759 | 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
1760 | 13 | # License for the specific language governing permissions and limitations | ||
1761 | 14 | # under the License. | ||
1762 | 15 | |||
1763 | 16 | """The volumes extension.""" | ||
1764 | 17 | |||
1765 | 18 | from webob import exc | ||
1766 | 19 | |||
1767 | 20 | from nova import compute | ||
1768 | 21 | from nova import exception | ||
1769 | 22 | from nova import flags | ||
1770 | 23 | from nova import log as logging | ||
1771 | 24 | from nova import volume | ||
1772 | 25 | from nova import wsgi | ||
1773 | 26 | from nova.api.openstack import common | ||
1774 | 27 | from nova.api.openstack import extensions | ||
1775 | 28 | from nova.api.openstack import faults | ||
1776 | 29 | |||
1777 | 30 | |||
1778 | 31 | LOG = logging.getLogger("nova.api.volumes") | ||
1779 | 32 | |||
1780 | 33 | |||
1781 | 34 | FLAGS = flags.FLAGS | ||
1782 | 35 | |||
1783 | 36 | |||
1784 | 37 | def _translate_volume_detail_view(context, vol): | ||
1785 | 38 | """Maps keys for volumes details view.""" | ||
1786 | 39 | |||
1787 | 40 | d = _translate_volume_summary_view(context, vol) | ||
1788 | 41 | |||
1789 | 42 | # No additional data / lookups at the moment | ||
1790 | 43 | |||
1791 | 44 | return d | ||
1792 | 45 | |||
1793 | 46 | |||
1794 | 47 | def _translate_volume_summary_view(context, vol): | ||
1795 | 48 | """Maps keys for volumes summary view.""" | ||
1796 | 49 | d = {} | ||
1797 | 50 | |||
1798 | 51 | d['id'] = vol['id'] | ||
1799 | 52 | d['status'] = vol['status'] | ||
1800 | 53 | d['size'] = vol['size'] | ||
1801 | 54 | d['availabilityZone'] = vol['availability_zone'] | ||
1802 | 55 | d['createdAt'] = vol['created_at'] | ||
1803 | 56 | |||
1804 | 57 | if vol['attach_status'] == 'attached': | ||
1805 | 58 | d['attachments'] = [_translate_attachment_detail_view(context, vol)] | ||
1806 | 59 | else: | ||
1807 | 60 | d['attachments'] = [{}] | ||
1808 | 61 | |||
1809 | 62 | d['displayName'] = vol['display_name'] | ||
1810 | 63 | d['displayDescription'] = vol['display_description'] | ||
1811 | 64 | return d | ||
1812 | 65 | |||
1813 | 66 | |||
1814 | 67 | class VolumeController(wsgi.Controller): | ||
1815 | 68 | """The Volumes API controller for the OpenStack API.""" | ||
1816 | 69 | |||
1817 | 70 | _serialization_metadata = { | ||
1818 | 71 | 'application/xml': { | ||
1819 | 72 | "attributes": { | ||
1820 | 73 | "volume": [ | ||
1821 | 74 | "id", | ||
1822 | 75 | "status", | ||
1823 | 76 | "size", | ||
1824 | 77 | "availabilityZone", | ||
1825 | 78 | "createdAt", | ||
1826 | 79 | "displayName", | ||
1827 | 80 | "displayDescription", | ||
1828 | 81 | ]}}} | ||
1829 | 82 | |||
1830 | 83 | def __init__(self): | ||
1831 | 84 | self.volume_api = volume.API() | ||
1832 | 85 | super(VolumeController, self).__init__() | ||
1833 | 86 | |||
1834 | 87 | def show(self, req, id): | ||
1835 | 88 | """Return data about the given volume.""" | ||
1836 | 89 | context = req.environ['nova.context'] | ||
1837 | 90 | |||
1838 | 91 | try: | ||
1839 | 92 | vol = self.volume_api.get(context, id) | ||
1840 | 93 | except exception.NotFound: | ||
1841 | 94 | return faults.Fault(exc.HTTPNotFound()) | ||
1842 | 95 | |||
1843 | 96 | return {'volume': _translate_volume_detail_view(context, vol)} | ||
1844 | 97 | |||
1845 | 98 | def delete(self, req, id): | ||
1846 | 99 | """Delete a volume.""" | ||
1847 | 100 | context = req.environ['nova.context'] | ||
1848 | 101 | |||
1849 | 102 | LOG.audit(_("Delete volume with id: %s"), id, context=context) | ||
1850 | 103 | |||
1851 | 104 | try: | ||
1852 | 105 | self.volume_api.delete(context, volume_id=id) | ||
1853 | 106 | except exception.NotFound: | ||
1854 | 107 | return faults.Fault(exc.HTTPNotFound()) | ||
1855 | 108 | return exc.HTTPAccepted() | ||
1856 | 109 | |||
1857 | 110 | def index(self, req): | ||
1858 | 111 | """Returns a summary list of volumes.""" | ||
1859 | 112 | return self._items(req, entity_maker=_translate_volume_summary_view) | ||
1860 | 113 | |||
1861 | 114 | def detail(self, req): | ||
1862 | 115 | """Returns a detailed list of volumes.""" | ||
1863 | 116 | return self._items(req, entity_maker=_translate_volume_detail_view) | ||
1864 | 117 | |||
1865 | 118 | def _items(self, req, entity_maker): | ||
1866 | 119 | """Returns a list of volumes, transformed through entity_maker.""" | ||
1867 | 120 | context = req.environ['nova.context'] | ||
1868 | 121 | |||
1869 | 122 | volumes = self.volume_api.get_all(context) | ||
1870 | 123 | limited_list = common.limited(volumes, req) | ||
1871 | 124 | res = [entity_maker(context, vol) for vol in limited_list] | ||
1872 | 125 | return {'volumes': res} | ||
1873 | 126 | |||
1874 | 127 | def create(self, req): | ||
1875 | 128 | """Creates a new volume.""" | ||
1876 | 129 | context = req.environ['nova.context'] | ||
1877 | 130 | |||
1878 | 131 | env = self._deserialize(req.body, req.get_content_type()) | ||
1879 | 132 | if not env: | ||
1880 | 133 | return faults.Fault(exc.HTTPUnprocessableEntity()) | ||
1881 | 134 | |||
1882 | 135 | vol = env['volume'] | ||
1883 | 136 | size = vol['size'] | ||
1884 | 137 | LOG.audit(_("Create volume of %s GB"), size, context=context) | ||
1885 | 138 | new_volume = self.volume_api.create(context, size, | ||
1886 | 139 | vol.get('display_name'), | ||
1887 | 140 | vol.get('display_description')) | ||
1888 | 141 | |||
1889 | 142 | # Work around problem that instance is lazy-loaded... | ||
1890 | 143 | new_volume['instance'] = None | ||
1891 | 144 | |||
1892 | 145 | retval = _translate_volume_detail_view(context, new_volume) | ||
1893 | 146 | |||
1894 | 147 | return {'volume': retval} | ||
1895 | 148 | |||
1896 | 149 | |||
1897 | 150 | def _translate_attachment_detail_view(_context, vol): | ||
1898 | 151 | """Maps keys for attachment details view.""" | ||
1899 | 152 | |||
1900 | 153 | d = _translate_attachment_summary_view(_context, vol) | ||
1901 | 154 | |||
1902 | 155 | # No additional data / lookups at the moment | ||
1903 | 156 | |||
1904 | 157 | return d | ||
1905 | 158 | |||
1906 | 159 | |||
1907 | 160 | def _translate_attachment_summary_view(_context, vol): | ||
1908 | 161 | """Maps keys for attachment summary view.""" | ||
1909 | 162 | d = {} | ||
1910 | 163 | |||
1911 | 164 | volume_id = vol['id'] | ||
1912 | 165 | |||
1913 | 166 | # NOTE(justinsb): We use the volume id as the id of the attachment object | ||
1914 | 167 | d['id'] = volume_id | ||
1915 | 168 | |||
1916 | 169 | d['volumeId'] = volume_id | ||
1917 | 170 | if vol.get('instance_id'): | ||
1918 | 171 | d['serverId'] = vol['instance_id'] | ||
1919 | 172 | if vol.get('mountpoint'): | ||
1920 | 173 | d['device'] = vol['mountpoint'] | ||
1921 | 174 | |||
1922 | 175 | return d | ||
1923 | 176 | |||
1924 | 177 | |||
1925 | 178 | class VolumeAttachmentController(wsgi.Controller): | ||
1926 | 179 | """The volume attachment API controller for the Openstack API. | ||
1927 | 180 | |||
1928 | 181 | A child resource of the server. Note that we use the volume id | ||
1929 | 182 | as the ID of the attachment (though this is not guaranteed externally) | ||
1930 | 183 | |||
1931 | 184 | """ | ||
1932 | 185 | |||
1933 | 186 | _serialization_metadata = { | ||
1934 | 187 | 'application/xml': { | ||
1935 | 188 | 'attributes': { | ||
1936 | 189 | 'volumeAttachment': ['id', | ||
1937 | 190 | 'serverId', | ||
1938 | 191 | 'volumeId', | ||
1939 | 192 | 'device']}}} | ||
1940 | 193 | |||
1941 | 194 | def __init__(self): | ||
1942 | 195 | self.compute_api = compute.API() | ||
1943 | 196 | self.volume_api = volume.API() | ||
1944 | 197 | super(VolumeAttachmentController, self).__init__() | ||
1945 | 198 | |||
1946 | 199 | def index(self, req, server_id): | ||
1947 | 200 | """Returns the list of volume attachments for a given instance.""" | ||
1948 | 201 | return self._items(req, server_id, | ||
1949 | 202 | entity_maker=_translate_attachment_summary_view) | ||
1950 | 203 | |||
1951 | 204 | def show(self, req, server_id, id): | ||
1952 | 205 | """Return data about the given volume attachment.""" | ||
1953 | 206 | context = req.environ['nova.context'] | ||
1954 | 207 | |||
1955 | 208 | volume_id = id | ||
1956 | 209 | try: | ||
1957 | 210 | vol = self.volume_api.get(context, volume_id) | ||
1958 | 211 | except exception.NotFound: | ||
1959 | 212 | LOG.debug("volume_id not found") | ||
1960 | 213 | return faults.Fault(exc.HTTPNotFound()) | ||
1961 | 214 | |||
1962 | 215 | if str(vol['instance_id']) != server_id: | ||
1963 | 216 | LOG.debug("instance_id != server_id") | ||
1964 | 217 | return faults.Fault(exc.HTTPNotFound()) | ||
1965 | 218 | |||
1966 | 219 | return {'volumeAttachment': _translate_attachment_detail_view(context, | ||
1967 | 220 | vol)} | ||
1968 | 221 | |||
1969 | 222 | def create(self, req, server_id): | ||
1970 | 223 | """Attach a volume to an instance.""" | ||
1971 | 224 | context = req.environ['nova.context'] | ||
1972 | 225 | |||
1973 | 226 | env = self._deserialize(req.body, req.get_content_type()) | ||
1974 | 227 | if not env: | ||
1975 | 228 | return faults.Fault(exc.HTTPUnprocessableEntity()) | ||
1976 | 229 | |||
1977 | 230 | instance_id = server_id | ||
1978 | 231 | volume_id = env['volumeAttachment']['volumeId'] | ||
1979 | 232 | device = env['volumeAttachment']['device'] | ||
1980 | 233 | |||
1981 | 234 | msg = _("Attach volume %(volume_id)s to instance %(server_id)s" | ||
1982 | 235 | " at %(device)s") % locals() | ||
1983 | 236 | LOG.audit(msg, context=context) | ||
1984 | 237 | |||
1985 | 238 | try: | ||
1986 | 239 | self.compute_api.attach_volume(context, | ||
1987 | 240 | instance_id=instance_id, | ||
1988 | 241 | volume_id=volume_id, | ||
1989 | 242 | device=device) | ||
1990 | 243 | except exception.NotFound: | ||
1991 | 244 | return faults.Fault(exc.HTTPNotFound()) | ||
1992 | 245 | |||
1993 | 246 | # The attach is async | ||
1994 | 247 | attachment = {} | ||
1995 | 248 | attachment['id'] = volume_id | ||
1996 | 249 | attachment['volumeId'] = volume_id | ||
1997 | 250 | |||
1998 | 251 | # NOTE(justinsb): And now, we have a problem... | ||
1999 | 252 | # The attach is async, so there's a window in which we don't see | ||
2000 | 253 | # the attachment (until the attachment completes). We could also | ||
2001 | 254 | # get problems with concurrent requests. I think we need an | ||
2002 | 255 | # attachment state, and to write to the DB here, but that's a bigger | ||
2003 | 256 | # change. | ||
2004 | 257 | # For now, we'll probably have to rely on libraries being smart | ||
2005 | 258 | |||
2006 | 259 | # TODO(justinsb): How do I return "accepted" here? | ||
2007 | 260 | return {'volumeAttachment': attachment} | ||
2008 | 261 | |||
2009 | 262 | def update(self, _req, _server_id, _id): | ||
2010 | 263 | """Update a volume attachment. We don't currently support this.""" | ||
2011 | 264 | return faults.Fault(exc.HTTPBadRequest()) | ||
2012 | 265 | |||
2013 | 266 | def delete(self, req, server_id, id): | ||
2014 | 267 | """Detach a volume from an instance.""" | ||
2015 | 268 | context = req.environ['nova.context'] | ||
2016 | 269 | |||
2017 | 270 | volume_id = id | ||
2018 | 271 | LOG.audit(_("Detach volume %s"), volume_id, context=context) | ||
2019 | 272 | |||
2020 | 273 | try: | ||
2021 | 274 | vol = self.volume_api.get(context, volume_id) | ||
2022 | 275 | except exception.NotFound: | ||
2023 | 276 | return faults.Fault(exc.HTTPNotFound()) | ||
2024 | 277 | |||
2025 | 278 | if str(vol['instance_id']) != server_id: | ||
2026 | 279 | LOG.debug("instance_id != server_id") | ||
2027 | 280 | return faults.Fault(exc.HTTPNotFound()) | ||
2028 | 281 | |||
2029 | 282 | self.compute_api.detach_volume(context, | ||
2030 | 283 | volume_id=volume_id) | ||
2031 | 284 | |||
2032 | 285 | return exc.HTTPAccepted() | ||
2033 | 286 | |||
2034 | 287 | def _items(self, req, server_id, entity_maker): | ||
2035 | 288 | """Returns a list of attachments, transformed through entity_maker.""" | ||
2036 | 289 | context = req.environ['nova.context'] | ||
2037 | 290 | |||
2038 | 291 | try: | ||
2039 | 292 | instance = self.compute_api.get(context, server_id) | ||
2040 | 293 | except exception.NotFound: | ||
2041 | 294 | return faults.Fault(exc.HTTPNotFound()) | ||
2042 | 295 | |||
2043 | 296 | volumes = instance['volumes'] | ||
2044 | 297 | limited_list = common.limited(volumes, req) | ||
2045 | 298 | res = [entity_maker(context, vol) for vol in limited_list] | ||
2046 | 299 | return {'volumeAttachments': res} | ||
2047 | 300 | |||
2048 | 301 | |||
2049 | 302 | class Volumes(extensions.ExtensionDescriptor): | ||
2050 | 303 | def get_name(self): | ||
2051 | 304 | return "Volumes" | ||
2052 | 305 | |||
2053 | 306 | def get_alias(self): | ||
2054 | 307 | return "VOLUMES" | ||
2055 | 308 | |||
2056 | 309 | def get_description(self): | ||
2057 | 310 | return "Volumes support" | ||
2058 | 311 | |||
2059 | 312 | def get_namespace(self): | ||
2060 | 313 | return "http://docs.openstack.org/ext/volumes/api/v1.1" | ||
2061 | 314 | |||
2062 | 315 | def get_updated(self): | ||
2063 | 316 | return "2011-03-25T00:00:00+00:00" | ||
2064 | 317 | |||
2065 | 318 | def get_resources(self): | ||
2066 | 319 | resources = [] | ||
2067 | 320 | |||
2068 | 321 | # NOTE(justinsb): No way to provide singular name ('volume') | ||
2069 | 322 | # Does this matter? | ||
2070 | 323 | res = extensions.ResourceExtension('volumes', | ||
2071 | 324 | VolumeController(), | ||
2072 | 325 | collection_actions={'detail': 'GET'} | ||
2073 | 326 | ) | ||
2074 | 327 | resources.append(res) | ||
2075 | 328 | |||
2076 | 329 | res = extensions.ResourceExtension('volume_attachments', | ||
2077 | 330 | VolumeAttachmentController(), | ||
2078 | 331 | parent=dict( | ||
2079 | 332 | member_name='server', | ||
2080 | 333 | collection_name='servers')) | ||
2081 | 334 | resources.append(res) | ||
2082 | 335 | |||
2083 | 336 | return resources | ||
2084 | 0 | 337 | ||
2085 | === modified file 'nova/api/openstack/extensions.py' | |||
2086 | --- nova/api/openstack/extensions.py 2011-03-21 18:00:39 +0000 | |||
2087 | +++ nova/api/openstack/extensions.py 2011-04-12 11:01:25 +0000 | |||
2088 | @@ -1,6 +1,7 @@ | |||
2089 | 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 |
2090 | 2 | 2 | ||
2091 | 3 | # Copyright 2011 OpenStack LLC. | 3 | # Copyright 2011 OpenStack LLC. |
2092 | 4 | # Copyright 2011 Justin Santa Barbara | ||
2093 | 4 | # All Rights Reserved. | 5 | # All Rights Reserved. |
2094 | 5 | # | 6 | # |
2095 | 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
2096 | @@ -16,15 +17,18 @@ | |||
2097 | 16 | # under the License. | 17 | # under the License. |
2098 | 17 | 18 | ||
2099 | 18 | import imp | 19 | import imp |
2100 | 20 | import inspect | ||
2101 | 19 | import os | 21 | import os |
2102 | 20 | import sys | 22 | import sys |
2103 | 21 | import routes | 23 | import routes |
2104 | 22 | import webob.dec | 24 | import webob.dec |
2105 | 23 | import webob.exc | 25 | import webob.exc |
2106 | 24 | 26 | ||
2107 | 27 | from nova import exception | ||
2108 | 25 | from nova import flags | 28 | from nova import flags |
2109 | 26 | from nova import log as logging | 29 | from nova import log as logging |
2110 | 27 | from nova import wsgi | 30 | from nova import wsgi |
2111 | 31 | from nova.api.openstack import common | ||
2112 | 28 | from nova.api.openstack import faults | 32 | from nova.api.openstack import faults |
2113 | 29 | 33 | ||
2114 | 30 | 34 | ||
2115 | @@ -34,7 +38,85 @@ | |||
2116 | 34 | FLAGS = flags.FLAGS | 38 | FLAGS = flags.FLAGS |
2117 | 35 | 39 | ||
2118 | 36 | 40 | ||
2120 | 37 | class ActionExtensionController(wsgi.Controller): | 41 | class ExtensionDescriptor(object): |
2121 | 42 | """Base class that defines the contract for extensions. | ||
2122 | 43 | |||
2123 | 44 | Note that you don't have to derive from this class to have a valid | ||
2124 | 45 | extension; it is purely a convenience. | ||
2125 | 46 | |||
2126 | 47 | """ | ||
2127 | 48 | |||
2128 | 49 | def get_name(self): | ||
2129 | 50 | """The name of the extension. | ||
2130 | 51 | |||
2131 | 52 | e.g. 'Fox In Socks' | ||
2132 | 53 | |||
2133 | 54 | """ | ||
2134 | 55 | raise NotImplementedError() | ||
2135 | 56 | |||
2136 | 57 | def get_alias(self): | ||
2137 | 58 | """The alias for the extension. | ||
2138 | 59 | |||
2139 | 60 | e.g. 'FOXNSOX' | ||
2140 | 61 | |||
2141 | 62 | """ | ||
2142 | 63 | raise NotImplementedError() | ||
2143 | 64 | |||
2144 | 65 | def get_description(self): | ||
2145 | 66 | """Friendly description for the extension. | ||
2146 | 67 | |||
2147 | 68 | e.g. 'The Fox In Socks Extension' | ||
2148 | 69 | |||
2149 | 70 | """ | ||
2150 | 71 | raise NotImplementedError() | ||
2151 | 72 | |||
2152 | 73 | def get_namespace(self): | ||
2153 | 74 | """The XML namespace for the extension. | ||
2154 | 75 | |||
2155 | 76 | e.g. 'http://www.fox.in.socks/api/ext/pie/v1.0' | ||
2156 | 77 | |||
2157 | 78 | """ | ||
2158 | 79 | raise NotImplementedError() | ||
2159 | 80 | |||
2160 | 81 | def get_updated(self): | ||
2161 | 82 | """The timestamp when the extension was last updated. | ||
2162 | 83 | |||
2163 | 84 | e.g. '2011-01-22T13:25:27-06:00' | ||
2164 | 85 | |||
2165 | 86 | """ | ||
2166 | 87 | # NOTE(justinsb): Not sure of the purpose of this is, vs the XML NS | ||
2167 | 88 | raise NotImplementedError() | ||
2168 | 89 | |||
2169 | 90 | def get_resources(self): | ||
2170 | 91 | """List of extensions.ResourceExtension extension objects. | ||
2171 | 92 | |||
2172 | 93 | Resources define new nouns, and are accessible through URLs. | ||
2173 | 94 | |||
2174 | 95 | """ | ||
2175 | 96 | resources = [] | ||
2176 | 97 | return resources | ||
2177 | 98 | |||
2178 | 99 | def get_actions(self): | ||
2179 | 100 | """List of extensions.ActionExtension extension objects. | ||
2180 | 101 | |||
2181 | 102 | Actions are verbs callable from the API. | ||
2182 | 103 | |||
2183 | 104 | """ | ||
2184 | 105 | actions = [] | ||
2185 | 106 | return actions | ||
2186 | 107 | |||
2187 | 108 | def get_response_extensions(self): | ||
2188 | 109 | """List of extensions.ResponseExtension extension objects. | ||
2189 | 110 | |||
2190 | 111 | Response extensions are used to insert information into existing | ||
2191 | 112 | response data. | ||
2192 | 113 | |||
2193 | 114 | """ | ||
2194 | 115 | response_exts = [] | ||
2195 | 116 | return response_exts | ||
2196 | 117 | |||
2197 | 118 | |||
2198 | 119 | class ActionExtensionController(common.OpenstackController): | ||
2199 | 38 | 120 | ||
2200 | 39 | def __init__(self, application): | 121 | def __init__(self, application): |
2201 | 40 | 122 | ||
2202 | @@ -55,7 +137,7 @@ | |||
2203 | 55 | return res | 137 | return res |
2204 | 56 | 138 | ||
2205 | 57 | 139 | ||
2207 | 58 | class ResponseExtensionController(wsgi.Controller): | 140 | class ResponseExtensionController(common.OpenstackController): |
2208 | 59 | 141 | ||
2209 | 60 | def __init__(self, application): | 142 | def __init__(self, application): |
2210 | 61 | self.application = application | 143 | self.application = application |
2211 | @@ -74,7 +156,8 @@ | |||
2212 | 74 | body = res.body | 156 | body = res.body |
2213 | 75 | headers = res.headers | 157 | headers = res.headers |
2214 | 76 | except AttributeError: | 158 | except AttributeError: |
2216 | 77 | body = self._serialize(res, content_type) | 159 | default_xmlns = None |
2217 | 160 | body = self._serialize(res, content_type, default_xmlns) | ||
2218 | 78 | headers = {"Content-Type": content_type} | 161 | headers = {"Content-Type": content_type} |
2219 | 79 | res = webob.Response() | 162 | res = webob.Response() |
2220 | 80 | res.body = body | 163 | res.body = body |
2221 | @@ -82,7 +165,7 @@ | |||
2222 | 82 | return res | 165 | return res |
2223 | 83 | 166 | ||
2224 | 84 | 167 | ||
2226 | 85 | class ExtensionController(wsgi.Controller): | 168 | class ExtensionController(common.OpenstackController): |
2227 | 86 | 169 | ||
2228 | 87 | def __init__(self, extension_manager): | 170 | def __init__(self, extension_manager): |
2229 | 88 | self.extension_manager = extension_manager | 171 | self.extension_manager = extension_manager |
2230 | @@ -94,45 +177,38 @@ | |||
2231 | 94 | ext_data['description'] = ext.get_description() | 177 | ext_data['description'] = ext.get_description() |
2232 | 95 | ext_data['namespace'] = ext.get_namespace() | 178 | ext_data['namespace'] = ext.get_namespace() |
2233 | 96 | ext_data['updated'] = ext.get_updated() | 179 | ext_data['updated'] = ext.get_updated() |
2235 | 97 | ext_data['links'] = [] # TODO: implement extension links | 180 | ext_data['links'] = [] # TODO(dprince): implement extension links |
2236 | 98 | return ext_data | 181 | return ext_data |
2237 | 99 | 182 | ||
2238 | 100 | def index(self, req): | 183 | def index(self, req): |
2239 | 101 | extensions = [] | 184 | extensions = [] |
2241 | 102 | for alias, ext in self.extension_manager.extensions.iteritems(): | 185 | for _alias, ext in self.extension_manager.extensions.iteritems(): |
2242 | 103 | extensions.append(self._translate(ext)) | 186 | extensions.append(self._translate(ext)) |
2243 | 104 | return dict(extensions=extensions) | 187 | return dict(extensions=extensions) |
2244 | 105 | 188 | ||
2245 | 106 | def show(self, req, id): | 189 | def show(self, req, id): |
2247 | 107 | # NOTE: the extensions alias is used as the 'id' for show | 190 | # NOTE(dprince): the extensions alias is used as the 'id' for show |
2248 | 108 | ext = self.extension_manager.extensions[id] | 191 | ext = self.extension_manager.extensions[id] |
2249 | 109 | return self._translate(ext) | 192 | return self._translate(ext) |
2250 | 110 | 193 | ||
2251 | 111 | def delete(self, req, id): | 194 | def delete(self, req, id): |
2253 | 112 | raise faults.Fault(exc.HTTPNotFound()) | 195 | raise faults.Fault(webob.exc.HTTPNotFound()) |
2254 | 113 | 196 | ||
2255 | 114 | def create(self, req): | 197 | def create(self, req): |
2260 | 115 | raise faults.Fault(exc.HTTPNotFound()) | 198 | raise faults.Fault(webob.exc.HTTPNotFound()) |
2257 | 116 | |||
2258 | 117 | def delete(self, req, id): | ||
2259 | 118 | raise faults.Fault(exc.HTTPNotFound()) | ||
2261 | 119 | 199 | ||
2262 | 120 | 200 | ||
2263 | 121 | class ExtensionMiddleware(wsgi.Middleware): | 201 | class ExtensionMiddleware(wsgi.Middleware): |
2267 | 122 | """ | 202 | """Extensions middleware for WSGI.""" |
2265 | 123 | Extensions middleware that intercepts configured routes for extensions. | ||
2266 | 124 | """ | ||
2268 | 125 | @classmethod | 203 | @classmethod |
2269 | 126 | def factory(cls, global_config, **local_config): | 204 | def factory(cls, global_config, **local_config): |
2271 | 127 | """ paste factory """ | 205 | """Paste factory.""" |
2272 | 128 | def _factory(app): | 206 | def _factory(app): |
2273 | 129 | return cls(app, **local_config) | 207 | return cls(app, **local_config) |
2274 | 130 | return _factory | 208 | return _factory |
2275 | 131 | 209 | ||
2276 | 132 | def _action_ext_controllers(self, application, ext_mgr, mapper): | 210 | def _action_ext_controllers(self, application, ext_mgr, mapper): |
2280 | 133 | """ | 211 | """Return a dict of ActionExtensionController-s by collection.""" |
2278 | 134 | Return a dict of ActionExtensionController objects by collection | ||
2279 | 135 | """ | ||
2281 | 136 | action_controllers = {} | 212 | action_controllers = {} |
2282 | 137 | for action in ext_mgr.get_actions(): | 213 | for action in ext_mgr.get_actions(): |
2283 | 138 | if not action.collection in action_controllers.keys(): | 214 | if not action.collection in action_controllers.keys(): |
2284 | @@ -151,9 +227,7 @@ | |||
2285 | 151 | return action_controllers | 227 | return action_controllers |
2286 | 152 | 228 | ||
2287 | 153 | def _response_ext_controllers(self, application, ext_mgr, mapper): | 229 | def _response_ext_controllers(self, application, ext_mgr, mapper): |
2291 | 154 | """ | 230 | """Returns a dict of ResponseExtensionController-s by collection.""" |
2289 | 155 | Return a dict of ResponseExtensionController objects by collection | ||
2290 | 156 | """ | ||
2292 | 157 | response_ext_controllers = {} | 231 | response_ext_controllers = {} |
2293 | 158 | for resp_ext in ext_mgr.get_response_extensions(): | 232 | for resp_ext in ext_mgr.get_response_extensions(): |
2294 | 159 | if not resp_ext.key in response_ext_controllers.keys(): | 233 | if not resp_ext.key in response_ext_controllers.keys(): |
2295 | @@ -212,18 +286,18 @@ | |||
2296 | 212 | 286 | ||
2297 | 213 | @webob.dec.wsgify(RequestClass=wsgi.Request) | 287 | @webob.dec.wsgify(RequestClass=wsgi.Request) |
2298 | 214 | def __call__(self, req): | 288 | def __call__(self, req): |
2302 | 215 | """ | 289 | """Route the incoming request with router.""" |
2300 | 216 | Route the incoming request with router. | ||
2301 | 217 | """ | ||
2303 | 218 | req.environ['extended.app'] = self.application | 290 | req.environ['extended.app'] = self.application |
2304 | 219 | return self._router | 291 | return self._router |
2305 | 220 | 292 | ||
2306 | 221 | @staticmethod | 293 | @staticmethod |
2307 | 222 | @webob.dec.wsgify(RequestClass=wsgi.Request) | 294 | @webob.dec.wsgify(RequestClass=wsgi.Request) |
2308 | 223 | def _dispatch(req): | 295 | def _dispatch(req): |
2310 | 224 | """ | 296 | """Dispatch the request. |
2311 | 297 | |||
2312 | 225 | Returns the routed WSGI app's response or defers to the extended | 298 | Returns the routed WSGI app's response or defers to the extended |
2313 | 226 | application. | 299 | application. |
2314 | 300 | |||
2315 | 227 | """ | 301 | """ |
2316 | 228 | match = req.environ['wsgiorg.routing_args'][1] | 302 | match = req.environ['wsgiorg.routing_args'][1] |
2317 | 229 | if not match: | 303 | if not match: |
2318 | @@ -233,10 +307,11 @@ | |||
2319 | 233 | 307 | ||
2320 | 234 | 308 | ||
2321 | 235 | class ExtensionManager(object): | 309 | class ExtensionManager(object): |
2326 | 236 | """ | 310 | """Load extensions from the configured extension path. |
2327 | 237 | Load extensions from the configured extension path. | 311 | |
2328 | 238 | See nova/tests/api/openstack/extensions/foxinsocks.py for an example | 312 | See nova/tests/api/openstack/extensions/foxinsocks/extension.py for an |
2329 | 239 | extension implementation. | 313 | example extension implementation. |
2330 | 314 | |||
2331 | 240 | """ | 315 | """ |
2332 | 241 | 316 | ||
2333 | 242 | def __init__(self, path): | 317 | def __init__(self, path): |
2334 | @@ -244,12 +319,10 @@ | |||
2335 | 244 | 319 | ||
2336 | 245 | self.path = path | 320 | self.path = path |
2337 | 246 | self.extensions = {} | 321 | self.extensions = {} |
2339 | 247 | self._load_extensions() | 322 | self._load_all_extensions() |
2340 | 248 | 323 | ||
2341 | 249 | def get_resources(self): | 324 | def get_resources(self): |
2345 | 250 | """ | 325 | """Returns a list of ResourceExtension objects.""" |
2343 | 251 | returns a list of ResourceExtension objects | ||
2344 | 252 | """ | ||
2346 | 253 | resources = [] | 326 | resources = [] |
2347 | 254 | resources.append(ResourceExtension('extensions', | 327 | resources.append(ResourceExtension('extensions', |
2348 | 255 | ExtensionController(self))) | 328 | ExtensionController(self))) |
2349 | @@ -257,40 +330,37 @@ | |||
2350 | 257 | try: | 330 | try: |
2351 | 258 | resources.extend(ext.get_resources()) | 331 | resources.extend(ext.get_resources()) |
2352 | 259 | except AttributeError: | 332 | except AttributeError: |
2354 | 260 | # NOTE: Extension aren't required to have resource extensions | 333 | # NOTE(dprince): Extension aren't required to have resource |
2355 | 334 | # extensions | ||
2356 | 261 | pass | 335 | pass |
2357 | 262 | return resources | 336 | return resources |
2358 | 263 | 337 | ||
2359 | 264 | def get_actions(self): | 338 | def get_actions(self): |
2363 | 265 | """ | 339 | """Returns a list of ActionExtension objects.""" |
2361 | 266 | returns a list of ActionExtension objects | ||
2362 | 267 | """ | ||
2364 | 268 | actions = [] | 340 | actions = [] |
2365 | 269 | for alias, ext in self.extensions.iteritems(): | 341 | for alias, ext in self.extensions.iteritems(): |
2366 | 270 | try: | 342 | try: |
2367 | 271 | actions.extend(ext.get_actions()) | 343 | actions.extend(ext.get_actions()) |
2368 | 272 | except AttributeError: | 344 | except AttributeError: |
2370 | 273 | # NOTE: Extension aren't required to have action extensions | 345 | # NOTE(dprince): Extension aren't required to have action |
2371 | 346 | # extensions | ||
2372 | 274 | pass | 347 | pass |
2373 | 275 | return actions | 348 | return actions |
2374 | 276 | 349 | ||
2375 | 277 | def get_response_extensions(self): | 350 | def get_response_extensions(self): |
2379 | 278 | """ | 351 | """Returns a list of ResponseExtension objects.""" |
2377 | 279 | returns a list of ResponseExtension objects | ||
2378 | 280 | """ | ||
2380 | 281 | response_exts = [] | 352 | response_exts = [] |
2381 | 282 | for alias, ext in self.extensions.iteritems(): | 353 | for alias, ext in self.extensions.iteritems(): |
2382 | 283 | try: | 354 | try: |
2383 | 284 | response_exts.extend(ext.get_response_extensions()) | 355 | response_exts.extend(ext.get_response_extensions()) |
2384 | 285 | except AttributeError: | 356 | except AttributeError: |
2386 | 286 | # NOTE: Extension aren't required to have response extensions | 357 | # NOTE(dprince): Extension aren't required to have response |
2387 | 358 | # extensions | ||
2388 | 287 | pass | 359 | pass |
2389 | 288 | return response_exts | 360 | return response_exts |
2390 | 289 | 361 | ||
2391 | 290 | def _check_extension(self, extension): | 362 | def _check_extension(self, extension): |
2395 | 291 | """ | 363 | """Checks for required methods in extension objects.""" |
2393 | 292 | Checks for required methods in extension objects. | ||
2394 | 293 | """ | ||
2396 | 294 | try: | 364 | try: |
2397 | 295 | LOG.debug(_('Ext name: %s'), extension.get_name()) | 365 | LOG.debug(_('Ext name: %s'), extension.get_name()) |
2398 | 296 | LOG.debug(_('Ext alias: %s'), extension.get_alias()) | 366 | LOG.debug(_('Ext alias: %s'), extension.get_alias()) |
2399 | @@ -300,40 +370,59 @@ | |||
2400 | 300 | except AttributeError as ex: | 370 | except AttributeError as ex: |
2401 | 301 | LOG.exception(_("Exception loading extension: %s"), unicode(ex)) | 371 | LOG.exception(_("Exception loading extension: %s"), unicode(ex)) |
2402 | 302 | 372 | ||
2405 | 303 | def _load_extensions(self): | 373 | def _load_all_extensions(self): |
2406 | 304 | """ | 374 | """Load extensions from the configured path. |
2407 | 375 | |||
2408 | 305 | Load extensions from the configured path. The extension name is | 376 | Load extensions from the configured path. The extension name is |
2409 | 306 | constructed from the module_name. If your extension module was named | 377 | constructed from the module_name. If your extension module was named |
2410 | 307 | widgets.py the extension class within that module should be | 378 | widgets.py the extension class within that module should be |
2411 | 308 | 'Widgets'. | 379 | 'Widgets'. |
2412 | 309 | 380 | ||
2413 | 381 | In addition, extensions are loaded from the 'contrib' directory. | ||
2414 | 382 | |||
2415 | 310 | See nova/tests/api/openstack/extensions/foxinsocks.py for an example | 383 | See nova/tests/api/openstack/extensions/foxinsocks.py for an example |
2416 | 311 | extension implementation. | 384 | extension implementation. |
2417 | 385 | |||
2418 | 312 | """ | 386 | """ |
2423 | 313 | if not os.path.exists(self.path): | 387 | if os.path.exists(self.path): |
2424 | 314 | return | 388 | self._load_all_extensions_from_path(self.path) |
2425 | 315 | 389 | ||
2426 | 316 | for f in os.listdir(self.path): | 390 | contrib_path = os.path.join(os.path.dirname(__file__), "contrib") |
2427 | 391 | if os.path.exists(contrib_path): | ||
2428 | 392 | self._load_all_extensions_from_path(contrib_path) | ||
2429 | 393 | |||
2430 | 394 | def _load_all_extensions_from_path(self, path): | ||
2431 | 395 | for f in os.listdir(path): | ||
2432 | 317 | LOG.audit(_('Loading extension file: %s'), f) | 396 | LOG.audit(_('Loading extension file: %s'), f) |
2433 | 318 | mod_name, file_ext = os.path.splitext(os.path.split(f)[-1]) | 397 | mod_name, file_ext = os.path.splitext(os.path.split(f)[-1]) |
2436 | 319 | ext_path = os.path.join(self.path, f) | 398 | ext_path = os.path.join(path, f) |
2437 | 320 | if file_ext.lower() == '.py': | 399 | if file_ext.lower() == '.py' and not mod_name.startswith('_'): |
2438 | 321 | mod = imp.load_source(mod_name, ext_path) | 400 | mod = imp.load_source(mod_name, ext_path) |
2439 | 322 | ext_name = mod_name[0].upper() + mod_name[1:] | 401 | ext_name = mod_name[0].upper() + mod_name[1:] |
2447 | 323 | try: | 402 | new_ext_class = getattr(mod, ext_name, None) |
2448 | 324 | new_ext = getattr(mod, ext_name)() | 403 | if not new_ext_class: |
2449 | 325 | self._check_extension(new_ext) | 404 | LOG.warn(_('Did not find expected name ' |
2450 | 326 | self.extensions[new_ext.get_alias()] = new_ext | 405 | '"%(ext_name)s" in %(file)s'), |
2451 | 327 | except AttributeError as ex: | 406 | {'ext_name': ext_name, |
2452 | 328 | LOG.exception(_("Exception loading extension: %s"), | 407 | 'file': ext_path}) |
2453 | 329 | unicode(ex)) | 408 | continue |
2454 | 409 | new_ext = new_ext_class() | ||
2455 | 410 | self._check_extension(new_ext) | ||
2456 | 411 | self._add_extension(new_ext) | ||
2457 | 412 | |||
2458 | 413 | def _add_extension(self, ext): | ||
2459 | 414 | alias = ext.get_alias() | ||
2460 | 415 | LOG.audit(_('Loaded extension: %s'), alias) | ||
2461 | 416 | |||
2462 | 417 | self._check_extension(ext) | ||
2463 | 418 | |||
2464 | 419 | if alias in self.extensions: | ||
2465 | 420 | raise exception.Error("Found duplicate extension: %s" % alias) | ||
2466 | 421 | self.extensions[alias] = ext | ||
2467 | 330 | 422 | ||
2468 | 331 | 423 | ||
2469 | 332 | class ResponseExtension(object): | 424 | class ResponseExtension(object): |
2474 | 333 | """ | 425 | """Add data to responses from core nova OpenStack API controllers.""" |
2471 | 334 | ResponseExtension objects can be used to add data to responses from | ||
2472 | 335 | core nova OpenStack API controllers. | ||
2473 | 336 | """ | ||
2475 | 337 | 426 | ||
2476 | 338 | def __init__(self, method, url_route, handler): | 427 | def __init__(self, method, url_route, handler): |
2477 | 339 | self.url_route = url_route | 428 | self.url_route = url_route |
2478 | @@ -343,10 +432,7 @@ | |||
2479 | 343 | 432 | ||
2480 | 344 | 433 | ||
2481 | 345 | class ActionExtension(object): | 434 | class ActionExtension(object): |
2486 | 346 | """ | 435 | """Add custom actions to core nova OpenStack API controllers.""" |
2483 | 347 | ActionExtension objects can be used to add custom actions to core nova | ||
2484 | 348 | nova OpenStack API controllers. | ||
2485 | 349 | """ | ||
2487 | 350 | 436 | ||
2488 | 351 | def __init__(self, collection, action_name, handler): | 437 | def __init__(self, collection, action_name, handler): |
2489 | 352 | self.collection = collection | 438 | self.collection = collection |
2490 | @@ -355,10 +441,7 @@ | |||
2491 | 355 | 441 | ||
2492 | 356 | 442 | ||
2493 | 357 | class ResourceExtension(object): | 443 | class ResourceExtension(object): |
2498 | 358 | """ | 444 | """Add top level resources to the OpenStack API in nova.""" |
2495 | 359 | ResourceExtension objects can be used to add top level resources | ||
2496 | 360 | to the OpenStack API in nova. | ||
2497 | 361 | """ | ||
2499 | 362 | 445 | ||
2500 | 363 | def __init__(self, collection, controller, parent=None, | 446 | def __init__(self, collection, controller, parent=None, |
2501 | 364 | collection_actions={}, member_actions={}): | 447 | collection_actions={}, member_actions={}): |
2502 | 365 | 448 | ||
2503 | === modified file 'nova/api/openstack/faults.py' | |||
2504 | --- nova/api/openstack/faults.py 2011-03-17 20:26:52 +0000 | |||
2505 | +++ nova/api/openstack/faults.py 2011-04-12 11:01:25 +0000 | |||
2506 | @@ -20,10 +20,10 @@ | |||
2507 | 20 | import webob.exc | 20 | import webob.exc |
2508 | 21 | 21 | ||
2509 | 22 | from nova import wsgi | 22 | from nova import wsgi |
2510 | 23 | from nova.api.openstack import common | ||
2511 | 23 | 24 | ||
2512 | 24 | 25 | ||
2513 | 25 | class Fault(webob.exc.HTTPException): | 26 | class Fault(webob.exc.HTTPException): |
2514 | 26 | |||
2515 | 27 | """An RS API fault response.""" | 27 | """An RS API fault response.""" |
2516 | 28 | 28 | ||
2517 | 29 | _fault_names = { | 29 | _fault_names = { |
2518 | @@ -47,7 +47,7 @@ | |||
2519 | 47 | """Generate a WSGI response based on the exception passed to ctor.""" | 47 | """Generate a WSGI response based on the exception passed to ctor.""" |
2520 | 48 | # Replace the body with fault details. | 48 | # Replace the body with fault details. |
2521 | 49 | code = self.wrapped_exc.status_int | 49 | code = self.wrapped_exc.status_int |
2523 | 50 | fault_name = self._fault_names.get(code, "computeFault") | 50 | fault_name = self._fault_names.get(code, "cloudServersFault") |
2524 | 51 | fault_data = { | 51 | fault_data = { |
2525 | 52 | fault_name: { | 52 | fault_name: { |
2526 | 53 | 'code': code, | 53 | 'code': code, |
2527 | @@ -57,9 +57,11 @@ | |||
2528 | 57 | fault_data[fault_name]['retryAfter'] = retry | 57 | fault_data[fault_name]['retryAfter'] = retry |
2529 | 58 | # 'code' is an attribute on the fault tag itself | 58 | # 'code' is an attribute on the fault tag itself |
2530 | 59 | metadata = {'application/xml': {'attributes': {fault_name: 'code'}}} | 59 | metadata = {'application/xml': {'attributes': {fault_name: 'code'}}} |
2532 | 60 | serializer = wsgi.Serializer(metadata) | 60 | default_xmlns = common.XML_NS_V10 |
2533 | 61 | serializer = wsgi.Serializer(metadata, default_xmlns) | ||
2534 | 61 | content_type = req.best_match_content_type() | 62 | content_type = req.best_match_content_type() |
2535 | 62 | self.wrapped_exc.body = serializer.serialize(fault_data, content_type) | 63 | self.wrapped_exc.body = serializer.serialize(fault_data, content_type) |
2536 | 64 | self.wrapped_exc.content_type = content_type | ||
2537 | 63 | return self.wrapped_exc | 65 | return self.wrapped_exc |
2538 | 64 | 66 | ||
2539 | 65 | 67 | ||
2540 | 66 | 68 | ||
2541 | === modified file 'nova/api/openstack/flavors.py' | |||
2542 | --- nova/api/openstack/flavors.py 2011-03-24 16:46:39 +0000 | |||
2543 | +++ nova/api/openstack/flavors.py 2011-04-12 11:01:25 +0000 | |||
2544 | @@ -19,11 +19,11 @@ | |||
2545 | 19 | 19 | ||
2546 | 20 | from nova import db | 20 | from nova import db |
2547 | 21 | from nova import exception | 21 | from nova import exception |
2549 | 22 | from nova import wsgi | 22 | from nova.api.openstack import common |
2550 | 23 | from nova.api.openstack import views | 23 | from nova.api.openstack import views |
2551 | 24 | 24 | ||
2552 | 25 | 25 | ||
2554 | 26 | class Controller(wsgi.Controller): | 26 | class Controller(common.OpenstackController): |
2555 | 27 | """Flavor controller for the OpenStack API.""" | 27 | """Flavor controller for the OpenStack API.""" |
2556 | 28 | 28 | ||
2557 | 29 | _serialization_metadata = { | 29 | _serialization_metadata = { |
2558 | @@ -76,3 +76,6 @@ | |||
2559 | 76 | def _get_view_builder(self, req): | 76 | def _get_view_builder(self, req): |
2560 | 77 | base_url = req.application_url | 77 | base_url = req.application_url |
2561 | 78 | return views.flavors.ViewBuilderV11(base_url) | 78 | return views.flavors.ViewBuilderV11(base_url) |
2562 | 79 | |||
2563 | 80 | def get_default_xmlns(self, req): | ||
2564 | 81 | return common.XML_NS_V11 | ||
2565 | 79 | 82 | ||
2566 | === modified file 'nova/api/openstack/image_metadata.py' | |||
2567 | --- nova/api/openstack/image_metadata.py 2011-03-25 14:07:42 +0000 | |||
2568 | +++ nova/api/openstack/image_metadata.py 2011-04-12 11:01:25 +0000 | |||
2569 | @@ -18,15 +18,17 @@ | |||
2570 | 18 | from webob import exc | 18 | from webob import exc |
2571 | 19 | 19 | ||
2572 | 20 | from nova import flags | 20 | from nova import flags |
2573 | 21 | from nova import quota | ||
2574 | 21 | from nova import utils | 22 | from nova import utils |
2575 | 22 | from nova import wsgi | 23 | from nova import wsgi |
2576 | 24 | from nova.api.openstack import common | ||
2577 | 23 | from nova.api.openstack import faults | 25 | from nova.api.openstack import faults |
2578 | 24 | 26 | ||
2579 | 25 | 27 | ||
2580 | 26 | FLAGS = flags.FLAGS | 28 | FLAGS = flags.FLAGS |
2581 | 27 | 29 | ||
2582 | 28 | 30 | ||
2584 | 29 | class Controller(wsgi.Controller): | 31 | class Controller(common.OpenstackController): |
2585 | 30 | """The image metadata API controller for the Openstack API""" | 32 | """The image metadata API controller for the Openstack API""" |
2586 | 31 | 33 | ||
2587 | 32 | def __init__(self): | 34 | def __init__(self): |
2588 | @@ -39,6 +41,15 @@ | |||
2589 | 39 | metadata = image.get('properties', {}) | 41 | metadata = image.get('properties', {}) |
2590 | 40 | return metadata | 42 | return metadata |
2591 | 41 | 43 | ||
2592 | 44 | def _check_quota_limit(self, context, metadata): | ||
2593 | 45 | if metadata is None: | ||
2594 | 46 | return | ||
2595 | 47 | num_metadata = len(metadata) | ||
2596 | 48 | quota_metadata = quota.allowed_metadata_items(context, num_metadata) | ||
2597 | 49 | if quota_metadata < num_metadata: | ||
2598 | 50 | expl = _("Image metadata limit exceeded") | ||
2599 | 51 | raise exc.HTTPBadRequest(explanation=expl) | ||
2600 | 52 | |||
2601 | 42 | def index(self, req, image_id): | 53 | def index(self, req, image_id): |
2602 | 43 | """Returns the list of metadata for a given instance""" | 54 | """Returns the list of metadata for a given instance""" |
2603 | 44 | context = req.environ['nova.context'] | 55 | context = req.environ['nova.context'] |
2604 | @@ -61,6 +72,7 @@ | |||
2605 | 61 | if 'metadata' in body: | 72 | if 'metadata' in body: |
2606 | 62 | for key, value in body['metadata'].iteritems(): | 73 | for key, value in body['metadata'].iteritems(): |
2607 | 63 | metadata[key] = value | 74 | metadata[key] = value |
2608 | 75 | self._check_quota_limit(context, metadata) | ||
2609 | 64 | img['properties'] = metadata | 76 | img['properties'] = metadata |
2610 | 65 | self.image_service.update(context, image_id, img, None) | 77 | self.image_service.update(context, image_id, img, None) |
2611 | 66 | return dict(metadata=metadata) | 78 | return dict(metadata=metadata) |
2612 | @@ -77,6 +89,7 @@ | |||
2613 | 77 | img = self.image_service.show(context, image_id) | 89 | img = self.image_service.show(context, image_id) |
2614 | 78 | metadata = self._get_metadata(context, image_id, img) | 90 | metadata = self._get_metadata(context, image_id, img) |
2615 | 79 | metadata[id] = body[id] | 91 | metadata[id] = body[id] |
2616 | 92 | self._check_quota_limit(context, metadata) | ||
2617 | 80 | img['properties'] = metadata | 93 | img['properties'] = metadata |
2618 | 81 | self.image_service.update(context, image_id, img, None) | 94 | self.image_service.update(context, image_id, img, None) |
2619 | 82 | 95 | ||
2620 | 83 | 96 | ||
2621 | === modified file 'nova/api/openstack/images.py' | |||
2622 | --- nova/api/openstack/images.py 2011-03-24 21:13:55 +0000 | |||
2623 | +++ nova/api/openstack/images.py 2011-04-12 11:01:25 +0000 | |||
2624 | @@ -1,6 +1,4 @@ | |||
2628 | 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | 1 | # Copyright 2011 OpenStack LLC. |
2626 | 2 | |||
2627 | 3 | # Copyright 2010 OpenStack LLC. | ||
2629 | 4 | # All Rights Reserved. | 2 | # All Rights Reserved. |
2630 | 5 | # | 3 | # |
2631 | 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may |
2632 | @@ -15,248 +13,143 @@ | |||
2633 | 15 | # License for the specific language governing permissions and limitations | 13 | # License for the specific language governing permissions and limitations |
2634 | 16 | # under the License. | 14 | # under the License. |
2635 | 17 | 15 | ||
2639 | 18 | import datetime | 16 | import webob.exc |
2637 | 19 | |||
2638 | 20 | from webob import exc | ||
2640 | 21 | 17 | ||
2641 | 22 | from nova import compute | 18 | from nova import compute |
2642 | 23 | from nova import exception | 19 | from nova import exception |
2643 | 24 | from nova import flags | 20 | from nova import flags |
2644 | 25 | from nova import log | 21 | from nova import log |
2645 | 26 | from nova import utils | 22 | from nova import utils |
2646 | 27 | from nova import wsgi | ||
2647 | 28 | import nova.api.openstack | ||
2648 | 29 | from nova.api.openstack import common | 23 | from nova.api.openstack import common |
2649 | 30 | from nova.api.openstack import faults | 24 | from nova.api.openstack import faults |
2651 | 31 | import nova.image.service | 25 | from nova.api.openstack.views import images as images_view |
2652 | 32 | 26 | ||
2653 | 33 | 27 | ||
2654 | 34 | LOG = log.getLogger('nova.api.openstack.images') | 28 | LOG = log.getLogger('nova.api.openstack.images') |
2655 | 35 | |||
2656 | 36 | FLAGS = flags.FLAGS | 29 | FLAGS = flags.FLAGS |
2657 | 37 | 30 | ||
2658 | 38 | 31 | ||
2826 | 39 | def _translate_keys(item): | 32 | class Controller(common.OpenstackController): |
2827 | 40 | """ | 33 | """Base `wsgi.Controller` for retrieving/displaying images.""" |
2661 | 41 | Maps key names to Rackspace-like attributes for return | ||
2662 | 42 | also pares down attributes to those we want | ||
2663 | 43 | item is a dict | ||
2664 | 44 | |||
2665 | 45 | Note: should be removed when the set of keys expected by the api | ||
2666 | 46 | and the set of keys returned by the image service are equivalent | ||
2667 | 47 | |||
2668 | 48 | """ | ||
2669 | 49 | # TODO(tr3buchet): this map is specific to s3 object store, | ||
2670 | 50 | # replace with a list of keys for _filter_keys later | ||
2671 | 51 | mapped_keys = {'status': 'imageState', | ||
2672 | 52 | 'id': 'imageId', | ||
2673 | 53 | 'name': 'imageLocation'} | ||
2674 | 54 | |||
2675 | 55 | mapped_item = {} | ||
2676 | 56 | # TODO(tr3buchet): | ||
2677 | 57 | # this chunk of code works with s3 and the local image service/glance | ||
2678 | 58 | # when we switch to glance/local image service it can be replaced with | ||
2679 | 59 | # a call to _filter_keys, and mapped_keys can be changed to a list | ||
2680 | 60 | try: | ||
2681 | 61 | for k, v in mapped_keys.iteritems(): | ||
2682 | 62 | # map s3 fields | ||
2683 | 63 | mapped_item[k] = item[v] | ||
2684 | 64 | except KeyError: | ||
2685 | 65 | # return only the fields api expects | ||
2686 | 66 | mapped_item = _filter_keys(item, mapped_keys.keys()) | ||
2687 | 67 | |||
2688 | 68 | return mapped_item | ||
2689 | 69 | |||
2690 | 70 | |||
2691 | 71 | def _translate_status(item): | ||
2692 | 72 | """ | ||
2693 | 73 | Translates status of image to match current Rackspace api bindings | ||
2694 | 74 | item is a dict | ||
2695 | 75 | |||
2696 | 76 | Note: should be removed when the set of statuses expected by the api | ||
2697 | 77 | and the set of statuses returned by the image service are equivalent | ||
2698 | 78 | |||
2699 | 79 | """ | ||
2700 | 80 | status_mapping = { | ||
2701 | 81 | 'pending': 'queued', | ||
2702 | 82 | 'decrypting': 'preparing', | ||
2703 | 83 | 'untarring': 'saving', | ||
2704 | 84 | 'available': 'active'} | ||
2705 | 85 | try: | ||
2706 | 86 | item['status'] = status_mapping[item['status']] | ||
2707 | 87 | except KeyError: | ||
2708 | 88 | # TODO(sirp): Performing translation of status (if necessary) here for | ||
2709 | 89 | # now. Perhaps this should really be done in EC2 API and | ||
2710 | 90 | # S3ImageService | ||
2711 | 91 | pass | ||
2712 | 92 | |||
2713 | 93 | |||
2714 | 94 | def _filter_keys(item, keys): | ||
2715 | 95 | """ | ||
2716 | 96 | Filters all model attributes except for keys | ||
2717 | 97 | item is a dict | ||
2718 | 98 | |||
2719 | 99 | """ | ||
2720 | 100 | return dict((k, v) for k, v in item.iteritems() if k in keys) | ||
2721 | 101 | |||
2722 | 102 | |||
2723 | 103 | def _convert_image_id_to_hash(image): | ||
2724 | 104 | if 'imageId' in image: | ||
2725 | 105 | # Convert EC2-style ID (i-blah) to Rackspace-style (int) | ||
2726 | 106 | image_id = abs(hash(image['imageId'])) | ||
2727 | 107 | image['imageId'] = image_id | ||
2728 | 108 | image['id'] = image_id | ||
2729 | 109 | |||
2730 | 110 | |||
2731 | 111 | def _translate_s3_like_images(image_metadata): | ||
2732 | 112 | """Work-around for leaky S3ImageService abstraction""" | ||
2733 | 113 | api_metadata = image_metadata.copy() | ||
2734 | 114 | _convert_image_id_to_hash(api_metadata) | ||
2735 | 115 | api_metadata = _translate_keys(api_metadata) | ||
2736 | 116 | _translate_status(api_metadata) | ||
2737 | 117 | return api_metadata | ||
2738 | 118 | |||
2739 | 119 | |||
2740 | 120 | def _translate_from_image_service_to_api(image_metadata): | ||
2741 | 121 | """Translate from ImageService to OpenStack API style attribute names | ||
2742 | 122 | |||
2743 | 123 | This involves 4 steps: | ||
2744 | 124 | |||
2745 | 125 | 1. Filter out attributes that the OpenStack API doesn't need | ||
2746 | 126 | |||
2747 | 127 | 2. Translate from base image attributes from names used by | ||
2748 | 128 | BaseImageService to names used by OpenStack API | ||
2749 | 129 | |||
2750 | 130 | 3. Add in any image properties | ||
2751 | 131 | |||
2752 | 132 | 4. Format values according to API spec (for example dates must | ||
2753 | 133 | look like "2010-08-10T12:00:00Z") | ||
2754 | 134 | """ | ||
2755 | 135 | service_metadata = image_metadata.copy() | ||
2756 | 136 | properties = service_metadata.pop('properties', {}) | ||
2757 | 137 | |||
2758 | 138 | # 1. Filter out unecessary attributes | ||
2759 | 139 | api_keys = ['id', 'name', 'updated_at', 'created_at', 'status'] | ||
2760 | 140 | api_metadata = utils.subset_dict(service_metadata, api_keys) | ||
2761 | 141 | |||
2762 | 142 | # 2. Translate base image attributes | ||
2763 | 143 | api_map = {'updated_at': 'updated', 'created_at': 'created'} | ||
2764 | 144 | api_metadata = utils.map_dict_keys(api_metadata, api_map) | ||
2765 | 145 | |||
2766 | 146 | # 3. Add in any image properties | ||
2767 | 147 | # 3a. serverId is used for backups and snapshots | ||
2768 | 148 | try: | ||
2769 | 149 | api_metadata['serverId'] = int(properties['instance_id']) | ||
2770 | 150 | except KeyError: | ||
2771 | 151 | pass # skip if it's not present | ||
2772 | 152 | except ValueError: | ||
2773 | 153 | pass # skip if it's not an integer | ||
2774 | 154 | |||
2775 | 155 | # 3b. Progress special case | ||
2776 | 156 | # TODO(sirp): ImageService doesn't have a notion of progress yet, so for | ||
2777 | 157 | # now just fake it | ||
2778 | 158 | if service_metadata['status'] == 'saving': | ||
2779 | 159 | api_metadata['progress'] = 0 | ||
2780 | 160 | |||
2781 | 161 | # 4. Format values | ||
2782 | 162 | # 4a. Format Image Status (API requires uppercase) | ||
2783 | 163 | api_metadata['status'] = _format_status_for_api(api_metadata['status']) | ||
2784 | 164 | |||
2785 | 165 | # 4b. Format timestamps | ||
2786 | 166 | for attr in ('created', 'updated'): | ||
2787 | 167 | if attr in api_metadata: | ||
2788 | 168 | api_metadata[attr] = _format_datetime_for_api( | ||
2789 | 169 | api_metadata[attr]) | ||
2790 | 170 | |||
2791 | 171 | return api_metadata | ||
2792 | 172 | |||
2793 | 173 | |||
2794 | 174 | def _format_status_for_api(status): | ||
2795 | 175 | """Return status in a format compliant with OpenStack API""" | ||
2796 | 176 | mapping = {'queued': 'QUEUED', | ||
2797 | 177 | 'preparing': 'PREPARING', | ||
2798 | 178 | 'saving': 'SAVING', | ||
2799 | 179 | 'active': 'ACTIVE', | ||
2800 | 180 | 'killed': 'FAILED'} | ||
2801 | 181 | return mapping[status] | ||
2802 | 182 | |||
2803 | 183 | |||
2804 | 184 | def _format_datetime_for_api(datetime_): | ||
2805 | 185 | """Stringify datetime objects in a format compliant with OpenStack API""" | ||
2806 | 186 | API_DATETIME_FMT = '%Y-%m-%dT%H:%M:%SZ' | ||
2807 | 187 | return datetime_.strftime(API_DATETIME_FMT) | ||
2808 | 188 | |||
2809 | 189 | |||
2810 | 190 | def _safe_translate(image_metadata): | ||
2811 | 191 | """Translate attributes for OpenStack API, temporary workaround for | ||
2812 | 192 | S3ImageService attribute leakage. | ||
2813 | 193 | """ | ||
2814 | 194 | # FIXME(sirp): The S3ImageService appears to be leaking implementation | ||
2815 | 195 | # details, including its internal attribute names, and internal | ||
2816 | 196 | # `status` values. Working around it for now. | ||
2817 | 197 | s3_like_image = ('imageId' in image_metadata) | ||
2818 | 198 | if s3_like_image: | ||
2819 | 199 | translate = _translate_s3_like_images | ||
2820 | 200 | else: | ||
2821 | 201 | translate = _translate_from_image_service_to_api | ||
2822 | 202 | return translate(image_metadata) | ||
2823 | 203 | |||
2824 | 204 | |||
2825 | 205 | class Controller(wsgi.Controller): | ||
2828 | 206 | 34 | ||
2829 | 207 | _serialization_metadata = { | 35 | _serialization_metadata = { |
2830 | 208 | 'application/xml': { | 36 | 'application/xml': { |
2831 | 209 | "attributes": { | 37 | "attributes": { |
2832 | 210 | "image": ["id", "name", "updated", "created", "status", | 38 | "image": ["id", "name", "updated", "created", "status", |
2837 | 211 | "serverId", "progress"]}}} | 39 | "serverId", "progress"], |
2838 | 212 | 40 | "link": ["rel", "type", "href"], | |
2839 | 213 | def __init__(self): | 41 | }, |
2840 | 214 | self._service = utils.import_object(FLAGS.image_service) | 42 | }, |
2841 | 43 | } | ||
2842 | 44 | |||
2843 | 45 | def __init__(self, image_service=None, compute_service=None): | ||
2844 | 46 | """Initialize new `ImageController`. | ||
2845 | 47 | |||
2846 | 48 | :param compute_service: `nova.compute.api:API` | ||
2847 | 49 | :param image_service: `nova.image.service:BaseImageService` | ||
2848 | 50 | """ | ||
2849 | 51 | _default_service = utils.import_object(flags.FLAGS.image_service) | ||
2850 | 52 | |||
2851 | 53 | self._compute_service = compute_service or compute.API() | ||
2852 | 54 | self._image_service = image_service or _default_service | ||
2853 | 215 | 55 | ||
2854 | 216 | def index(self, req): | 56 | def index(self, req): |
2856 | 217 | """Return all public images in brief""" | 57 | """Return an index listing of images available to the request. |
2857 | 58 | |||
2858 | 59 | :param req: `wsgi.Request` object | ||
2859 | 60 | """ | ||
2860 | 218 | context = req.environ['nova.context'] | 61 | context = req.environ['nova.context'] |
2864 | 219 | image_metas = self._service.index(context) | 62 | images = self._image_service.index(context) |
2865 | 220 | image_metas = common.limited(image_metas, req) | 63 | images = common.limited(images, req) |
2866 | 221 | return dict(images=image_metas) | 64 | builder = self.get_builder(req).build |
2867 | 65 | return dict(images=[builder(image, detail=False) for image in images]) | ||
2868 | 222 | 66 | ||
2869 | 223 | def detail(self, req): | 67 | def detail(self, req): |
2871 | 224 | """Return all public images in detail""" | 68 | """Return a detailed index listing of images available to the request. |
2872 | 69 | |||
2873 | 70 | :param req: `wsgi.Request` object. | ||
2874 | 71 | """ | ||
2875 | 225 | context = req.environ['nova.context'] | 72 | context = req.environ['nova.context'] |
2881 | 226 | image_metas = self._service.detail(context) | 73 | images = self._image_service.detail(context) |
2882 | 227 | image_metas = common.limited(image_metas, req) | 74 | images = common.limited(images, req) |
2883 | 228 | api_image_metas = [_safe_translate(image_meta) | 75 | builder = self.get_builder(req).build |
2884 | 229 | for image_meta in image_metas] | 76 | return dict(images=[builder(image, detail=True) for image in images]) |
2880 | 230 | return dict(images=api_image_metas) | ||
2885 | 231 | 77 | ||
2886 | 232 | def show(self, req, id): | 78 | def show(self, req, id): |
2888 | 233 | """Return data about the given image id""" | 79 | """Return detailed information about a specific image. |
2889 | 80 | |||
2890 | 81 | :param req: `wsgi.Request` object | ||
2891 | 82 | :param id: Image identifier (integer) | ||
2892 | 83 | """ | ||
2893 | 234 | context = req.environ['nova.context'] | 84 | context = req.environ['nova.context'] |
2897 | 235 | try: | 85 | |
2898 | 236 | image_id = common.get_image_id_from_image_hash( | 86 | try: |
2899 | 237 | self._service, context, id) | 87 | image_id = int(id) |
2900 | 88 | except ValueError: | ||
2901 | 89 | explanation = _("Image not found.") | ||
2902 | 90 | raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation)) | ||
2903 | 91 | |||
2904 | 92 | try: | ||
2905 | 93 | image = self._image_service.show(context, image_id) | ||
2906 | 238 | except exception.NotFound: | 94 | except exception.NotFound: |
2908 | 239 | raise faults.Fault(exc.HTTPNotFound()) | 95 | explanation = _("Image '%d' not found.") % (image_id) |
2909 | 96 | raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation)) | ||
2910 | 240 | 97 | ||
2914 | 241 | image_meta = self._service.show(context, image_id) | 98 | return dict(image=self.get_builder(req).build(image, detail=True)) |
2912 | 242 | api_image_meta = _safe_translate(image_meta) | ||
2913 | 243 | return dict(image=api_image_meta) | ||
2915 | 244 | 99 | ||
2916 | 245 | def delete(self, req, id): | 100 | def delete(self, req, id): |
2919 | 246 | # Only public images are supported for now. | 101 | """Delete an image, if allowed. |
2920 | 247 | raise faults.Fault(exc.HTTPNotFound()) | 102 | |
2921 | 103 | :param req: `wsgi.Request` object | ||
2922 | 104 | :param id: Image identifier (integer) | ||
2923 | 105 | """ | ||
2924 | 106 | image_id = id | ||
2925 | 107 | context = req.environ['nova.context'] | ||
2926 | 108 | self._image_service.delete(context, image_id) | ||
2927 | 109 | return webob.exc.HTTPNoContent() | ||
2928 | 248 | 110 | ||
2929 | 249 | def create(self, req): | 111 | def create(self, req): |
2930 | 112 | """Snapshot a server instance and save the image. | ||
2931 | 113 | |||
2932 | 114 | :param req: `wsgi.Request` object | ||
2933 | 115 | """ | ||
2934 | 250 | context = req.environ['nova.context'] | 116 | context = req.environ['nova.context'] |
2947 | 251 | env = self._deserialize(req.body, req.get_content_type()) | 117 | content_type = req.get_content_type() |
2948 | 252 | instance_id = env["image"]["serverId"] | 118 | image = self._deserialize(req.body, content_type) |
2949 | 253 | name = env["image"]["name"] | 119 | |
2950 | 254 | image_meta = compute.API().snapshot( | 120 | if not image: |
2951 | 255 | context, instance_id, name) | 121 | raise webob.exc.HTTPBadRequest() |
2952 | 256 | api_image_meta = _safe_translate(image_meta) | 122 | |
2953 | 257 | return dict(image=api_image_meta) | 123 | try: |
2954 | 258 | 124 | server_id = image["image"]["serverId"] | |
2955 | 259 | def update(self, req, id): | 125 | image_name = image["image"]["name"] |
2956 | 260 | # Users may not modify public images, and that's all that | 126 | except KeyError: |
2957 | 261 | # we support for now. | 127 | raise webob.exc.HTTPBadRequest() |
2958 | 262 | raise faults.Fault(exc.HTTPNotFound()) | 128 | |
2959 | 129 | image = self._compute_service.snapshot(context, server_id, image_name) | ||
2960 | 130 | return self.get_builder(req).build(image, detail=True) | ||
2961 | 131 | |||
2962 | 132 | def get_builder(self, request): | ||
2963 | 133 | """Indicates that you must use a Controller subclass.""" | ||
2964 | 134 | raise NotImplementedError | ||
2965 | 135 | |||
2966 | 136 | |||
2967 | 137 | class ControllerV10(Controller): | ||
2968 | 138 | """Version 1.0 specific controller logic.""" | ||
2969 | 139 | |||
2970 | 140 | def get_builder(self, request): | ||
2971 | 141 | """Property to get the ViewBuilder class we need to use.""" | ||
2972 | 142 | base_url = request.application_url | ||
2973 | 143 | return images_view.ViewBuilderV10(base_url) | ||
2974 | 144 | |||
2975 | 145 | |||
2976 | 146 | class ControllerV11(Controller): | ||
2977 | 147 | """Version 1.1 specific controller logic.""" | ||
2978 | 148 | |||
2979 | 149 | def get_builder(self, request): | ||
2980 | 150 | """Property to get the ViewBuilder class we need to use.""" | ||
2981 | 151 | base_url = request.application_url | ||
2982 | 152 | return images_view.ViewBuilderV11(base_url) | ||
2983 | 153 | |||
2984 | 154 | def get_default_xmlns(self, req): | ||
2985 | 155 | return common.XML_NS_V11 | ||
2986 | 263 | 156 | ||
2987 | === added file 'nova/api/openstack/ips.py' | |||
2988 | --- nova/api/openstack/ips.py 1970-01-01 00:00:00 +0000 | |||
2989 | +++ nova/api/openstack/ips.py 2011-04-12 11:01:25 +0000 | |||
2990 | @@ -0,0 +1,72 @@ | |||
2991 | 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||
2992 | 2 | |||
2993 | 3 | # Copyright 2011 OpenStack LLC. | ||
2994 | 4 | # All Rights Reserved. | ||
2995 | 5 | # | ||
2996 | 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
2997 | 7 | # not use this file except in compliance with the License. You may obtain | ||
2998 | 8 | # a copy of the License at | ||
2999 | 9 | # | ||
3000 | 10 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
3001 | 11 | # | ||
3002 | 12 | # Unless required by applicable law or agreed to in writing, software | ||
3003 | 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
3004 | 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
3005 | 15 | # License for the specific language governing permissions and limitations | ||
3006 | 16 | # under the License. | ||
3007 | 17 | |||
3008 | 18 | import time | ||
3009 | 19 | |||
3010 | 20 | from webob import exc | ||
3011 | 21 | |||
3012 | 22 | import nova | ||
3013 | 23 | import nova.api.openstack.views.addresses | ||
3014 | 24 | from nova.api.openstack import common | ||
3015 | 25 | from nova.api.openstack import faults | ||
3016 | 26 | |||
3017 | 27 | |||
3018 | 28 | class Controller(common.OpenstackController): | ||
3019 | 29 | """The servers addresses API controller for the Openstack API.""" | ||
3020 | 30 | |||
3021 | 31 | _serialization_metadata = { | ||
3022 | 32 | 'application/xml': { | ||
3023 | 33 | 'list_collections': { | ||
3024 | 34 | 'public': {'item_name': 'ip', 'item_key': 'addr'}, | ||
3025 | 35 | 'private': {'item_name': 'ip', 'item_key': 'addr'}, | ||
3026 | 36 | }, | ||
3027 | 37 | }, | ||
3028 | 38 | } | ||
3029 | 39 | |||
3030 | 40 | def __init__(self): | ||
3031 | 41 | self.compute_api = nova.compute.API() | ||
3032 | 42 | self.builder = nova.api.openstack.views.addresses.ViewBuilderV10() | ||
3033 | 43 | |||
3034 | 44 | def index(self, req, server_id): | ||
3035 | 45 | try: | ||
3036 | 46 | instance = self.compute_api.get(req.environ['nova.context'], id) | ||
3037 | 47 | except nova.exception.NotFound: | ||
3038 | 48 | return faults.Fault(exc.HTTPNotFound()) | ||
3039 | 49 | return {'addresses': self.builder.build(instance)} | ||
3040 | 50 | |||
3041 | 51 | def public(self, req, server_id): | ||
3042 | 52 | try: | ||
3043 | 53 | instance = self.compute_api.get(req.environ['nova.context'], id) | ||
3044 | 54 | except nova.exception.NotFound: | ||
3045 | 55 | return faults.Fault(exc.HTTPNotFound()) | ||
3046 | 56 | return {'public': self.builder.build_public_parts(instance)} | ||
3047 | 57 | |||
3048 | 58 | def private(self, req, server_id): | ||
3049 | 59 | try: | ||
3050 | 60 | instance = self.compute_api.get(req.environ['nova.context'], id) | ||
3051 | 61 | except nova.exception.NotFound: | ||
3052 | 62 | return faults.Fault(exc.HTTPNotFound()) | ||
3053 | 63 | return {'private': self.builder.build_private_parts(instance)} | ||
3054 | 64 | |||
3055 | 65 | def show(self, req, server_id, id): | ||
3056 | 66 | return faults.Fault(exc.HTTPNotImplemented()) | ||
3057 | 67 | |||
3058 | 68 | def create(self, req, server_id): | ||
3059 | 69 | return faults.Fault(exc.HTTPNotImplemented()) | ||
3060 | 70 | |||
3061 | 71 | def delete(self, req, server_id, id): | ||
3062 | 72 | return faults.Fault(exc.HTTPNotImplemented()) | ||
3063 | 0 | 73 | ||
3064 | === modified file 'nova/api/openstack/limits.py' | |||
3065 | --- nova/api/openstack/limits.py 2011-03-17 20:26:52 +0000 | |||
3066 | +++ nova/api/openstack/limits.py 2011-04-12 11:01:25 +0000 | |||
3067 | @@ -31,8 +31,8 @@ | |||
3068 | 31 | from webob.dec import wsgify | 31 | from webob.dec import wsgify |
3069 | 32 | 32 | ||
3070 | 33 | from nova import wsgi | 33 | from nova import wsgi |
3071 | 34 | from nova.api.openstack import common | ||
3072 | 34 | from nova.api.openstack import faults | 35 | from nova.api.openstack import faults |
3073 | 35 | from nova.wsgi import Controller | ||
3074 | 36 | from nova.wsgi import Middleware | 36 | from nova.wsgi import Middleware |
3075 | 37 | 37 | ||
3076 | 38 | 38 | ||
3077 | @@ -43,7 +43,7 @@ | |||
3078 | 43 | PER_DAY = 60 * 60 * 24 | 43 | PER_DAY = 60 * 60 * 24 |
3079 | 44 | 44 | ||
3080 | 45 | 45 | ||
3082 | 46 | class LimitsController(Controller): | 46 | class LimitsController(common.OpenstackController): |
3083 | 47 | """ | 47 | """ |
3084 | 48 | Controller for accessing limits in the OpenStack API. | 48 | Controller for accessing limits in the OpenStack API. |
3085 | 49 | """ | 49 | """ |
3086 | 50 | 50 | ||
3087 | === modified file 'nova/api/openstack/server_metadata.py' | |||
3088 | --- nova/api/openstack/server_metadata.py 2011-03-22 14:01:18 +0000 | |||
3089 | +++ nova/api/openstack/server_metadata.py 2011-04-12 11:01:25 +0000 | |||
3090 | @@ -19,10 +19,11 @@ | |||
3091 | 19 | 19 | ||
3092 | 20 | from nova import compute | 20 | from nova import compute |
3093 | 21 | from nova import wsgi | 21 | from nova import wsgi |
3094 | 22 | from nova.api.openstack import common | ||
3095 | 22 | from nova.api.openstack import faults | 23 | from nova.api.openstack import faults |
3096 | 23 | 24 | ||
3097 | 24 | 25 | ||
3099 | 25 | class Controller(wsgi.Controller): | 26 | class Controller(common.OpenstackController): |
3100 | 26 | """ The server metadata API controller for the Openstack API """ | 27 | """ The server metadata API controller for the Openstack API """ |
3101 | 27 | 28 | ||
3102 | 28 | def __init__(self): | 29 | def __init__(self): |
3103 | 29 | 30 | ||
3104 | === modified file 'nova/api/openstack/servers.py' | |||
3105 | --- nova/api/openstack/servers.py 2011-03-28 08:27:17 +0000 | |||
3106 | +++ nova/api/openstack/servers.py 2011-04-12 11:01:25 +0000 | |||
3107 | @@ -44,7 +44,7 @@ | |||
3108 | 44 | FLAGS = flags.FLAGS | 44 | FLAGS = flags.FLAGS |
3109 | 45 | 45 | ||
3110 | 46 | 46 | ||
3112 | 47 | class Controller(wsgi.Controller): | 47 | class Controller(common.OpenstackController): |
3113 | 48 | """ The Server API controller for the OpenStack API """ | 48 | """ The Server API controller for the OpenStack API """ |
3114 | 49 | 49 | ||
3115 | 50 | _serialization_metadata = { | 50 | _serialization_metadata = { |
3116 | @@ -55,6 +55,13 @@ | |||
3117 | 55 | "imageRef", "vnics"], | 55 | "imageRef", "vnics"], |
3118 | 56 | "link": ["rel", "type", "href"], | 56 | "link": ["rel", "type", "href"], |
3119 | 57 | }, | 57 | }, |
3120 | 58 | "dict_collections": { | ||
3121 | 59 | "metadata": {"item_name": "meta", "item_key": "key"}, | ||
3122 | 60 | }, | ||
3123 | 61 | "list_collections": { | ||
3124 | 62 | "public": {"item_name": "ip", "item_key": "addr"}, | ||
3125 | 63 | "private": {"item_name": "ip", "item_key": "addr"}, | ||
3126 | 64 | }, | ||
3127 | 58 | }, | 65 | }, |
3128 | 59 | } | 66 | } |
3129 | 60 | 67 | ||
3130 | @@ -63,15 +70,6 @@ | |||
3131 | 63 | self._image_service = utils.import_object(FLAGS.image_service) | 70 | self._image_service = utils.import_object(FLAGS.image_service) |
3132 | 64 | super(Controller, self).__init__() | 71 | super(Controller, self).__init__() |
3133 | 65 | 72 | ||
3134 | 66 | def ips(self, req, id): | ||
3135 | 67 | try: | ||
3136 | 68 | instance = self.compute_api.get(req.environ['nova.context'], id) | ||
3137 | 69 | except exception.NotFound: | ||
3138 | 70 | return faults.Fault(exc.HTTPNotFound()) | ||
3139 | 71 | |||
3140 | 72 | builder = self._get_addresses_view_builder(req) | ||
3141 | 73 | return builder.build(instance) | ||
3142 | 74 | |||
3143 | 75 | def index(self, req): | 73 | def index(self, req): |
3144 | 76 | """ Returns a list of server names and ids for a given user """ | 74 | """ Returns a list of server names and ids for a given user """ |
3145 | 77 | return self._items(req, is_detail=False) | 75 | return self._items(req, is_detail=False) |
3146 | @@ -151,16 +149,27 @@ | |||
3147 | 151 | 149 | ||
3148 | 152 | vnics = [env['server']['vnics']] if 'vnics' in env['server'] else None | 150 | vnics = [env['server']['vnics']] if 'vnics' in env['server'] else None |
3149 | 153 | flavor_id = self._flavor_id_from_req_data(env) | 151 | flavor_id = self._flavor_id_from_req_data(env) |
3150 | 152 | |||
3151 | 153 | if not 'name' in env['server']: | ||
3152 | 154 | msg = _("Server name is not defined") | ||
3153 | 155 | return exc.HTTPBadRequest(msg) | ||
3154 | 156 | |||
3155 | 157 | name = env['server']['name'] | ||
3156 | 158 | self._validate_server_name(name) | ||
3157 | 159 | name = name.strip() | ||
3158 | 160 | |||
3159 | 154 | try: | 161 | try: |
3160 | 162 | inst_type = \ | ||
3161 | 163 | instance_types.get_instance_type_by_flavor_id(flavor_id) | ||
3162 | 155 | (inst,) = self.compute_api.create( | 164 | (inst,) = self.compute_api.create( |
3163 | 156 | context, | 165 | context, |
3165 | 157 | instance_types.get_by_flavor_id(flavor_id), | 166 | inst_type, |
3166 | 158 | image_id, | 167 | image_id, |
3167 | 159 | vnic_list=vnics, | 168 | vnic_list=vnics, |
3168 | 160 | kernel_id=kernel_id, | 169 | kernel_id=kernel_id, |
3169 | 161 | ramdisk_id=ramdisk_id, | 170 | ramdisk_id=ramdisk_id, |
3172 | 162 | display_name=env['server']['name'], | 171 | display_name=name, |
3173 | 163 | display_description=env['server']['name'], | 172 | display_description=name, |
3174 | 164 | key_name=key_name, | 173 | key_name=key_name, |
3175 | 165 | key_data=key_data, | 174 | key_data=key_data, |
3176 | 166 | metadata=metadata, | 175 | metadata=metadata, |
3177 | @@ -168,13 +177,12 @@ | |||
3178 | 168 | except quota.QuotaError as error: | 177 | except quota.QuotaError as error: |
3179 | 169 | self._handle_quota_error(error) | 178 | self._handle_quota_error(error) |
3180 | 170 | 179 | ||
3182 | 171 | inst['instance_type'] = flavor_id | 180 | inst['instance_type'] = inst_type |
3183 | 172 | inst['image_id'] = requested_image_id | 181 | inst['image_id'] = requested_image_id |
3184 | 173 | 182 | ||
3185 | 174 | builder = self._get_view_builder(req) | 183 | builder = self._get_view_builder(req) |
3186 | 175 | server = builder.build(inst, is_detail=True) | 184 | server = builder.build(inst, is_detail=True) |
3189 | 176 | password = "%s%s" % (server['server']['name'][:4], | 185 | password = utils.generate_password(16) |
3188 | 177 | utils.generate_password(12)) | ||
3190 | 178 | server['server']['adminPass'] = password | 186 | server['server']['adminPass'] = password |
3191 | 179 | self.compute_api.set_admin_password(context, server['server']['id'], | 187 | self.compute_api.set_admin_password(context, server['server']['id'], |
3192 | 180 | password) | 188 | password) |
3193 | @@ -248,31 +256,45 @@ | |||
3194 | 248 | 256 | ||
3195 | 249 | ctxt = req.environ['nova.context'] | 257 | ctxt = req.environ['nova.context'] |
3196 | 250 | update_dict = {} | 258 | update_dict = {} |
3203 | 251 | if 'adminPass' in inst_dict['server']: | 259 | |
3198 | 252 | update_dict['admin_pass'] = inst_dict['server']['adminPass'] | ||
3199 | 253 | try: | ||
3200 | 254 | self.compute_api.set_admin_password(ctxt, id) | ||
3201 | 255 | except exception.TimeoutException: | ||
3202 | 256 | return exc.HTTPRequestTimeout() | ||
3204 | 257 | if 'name' in inst_dict['server']: | 260 | if 'name' in inst_dict['server']: |
3206 | 258 | update_dict['display_name'] = inst_dict['server']['name'] | 261 | name = inst_dict['server']['name'] |
3207 | 262 | self._validate_server_name(name) | ||
3208 | 263 | update_dict['display_name'] = name.strip() | ||
3209 | 264 | |||
3210 | 265 | self._parse_update(ctxt, id, inst_dict, update_dict) | ||
3211 | 266 | |||
3212 | 259 | try: | 267 | try: |
3213 | 260 | self.compute_api.update(ctxt, id, **update_dict) | 268 | self.compute_api.update(ctxt, id, **update_dict) |
3214 | 261 | except exception.NotFound: | 269 | except exception.NotFound: |
3215 | 262 | return faults.Fault(exc.HTTPNotFound()) | 270 | return faults.Fault(exc.HTTPNotFound()) |
3216 | 271 | |||
3217 | 263 | return exc.HTTPNoContent() | 272 | return exc.HTTPNoContent() |
3218 | 264 | 273 | ||
3219 | 274 | def _validate_server_name(self, value): | ||
3220 | 275 | if not isinstance(value, basestring): | ||
3221 | 276 | msg = _("Server name is not a string or unicode") | ||
3222 | 277 | raise exc.HTTPBadRequest(msg) | ||
3223 | 278 | |||
3224 | 279 | if value.strip() == '': | ||
3225 | 280 | msg = _("Server name is an empty string") | ||
3226 | 281 | raise exc.HTTPBadRequest(msg) | ||
3227 | 282 | |||
3228 | 283 | def _parse_update(self, context, id, inst_dict, update_dict): | ||
3229 | 284 | pass | ||
3230 | 285 | |||
3231 | 265 | @scheduler_api.redirect_handler | 286 | @scheduler_api.redirect_handler |
3232 | 266 | def action(self, req, id): | 287 | def action(self, req, id): |
3233 | 267 | """Multi-purpose method used to reboot, rebuild, or | 288 | """Multi-purpose method used to reboot, rebuild, or |
3234 | 268 | resize a server""" | 289 | resize a server""" |
3235 | 269 | 290 | ||
3236 | 270 | actions = { | 291 | actions = { |
3239 | 271 | 'reboot': self._action_reboot, | 292 | 'changePassword': self._action_change_password, |
3240 | 272 | 'resize': self._action_resize, | 293 | 'reboot': self._action_reboot, |
3241 | 294 | 'resize': self._action_resize, | ||
3242 | 273 | 'confirmResize': self._action_confirm_resize, | 295 | 'confirmResize': self._action_confirm_resize, |
3245 | 274 | 'revertResize': self._action_revert_resize, | 296 | 'revertResize': self._action_revert_resize, |
3246 | 275 | 'rebuild': self._action_rebuild, | 297 | 'rebuild': self._action_rebuild, |
3247 | 276 | } | 298 | } |
3248 | 277 | 299 | ||
3249 | 278 | input_dict = self._deserialize(req.body, req.get_content_type()) | 300 | input_dict = self._deserialize(req.body, req.get_content_type()) |
3250 | @@ -281,6 +303,9 @@ | |||
3251 | 281 | return actions[key](input_dict, req, id) | 303 | return actions[key](input_dict, req, id) |
3252 | 282 | return faults.Fault(exc.HTTPNotImplemented()) | 304 | return faults.Fault(exc.HTTPNotImplemented()) |
3253 | 283 | 305 | ||
3254 | 306 | def _action_change_password(self, input_dict, req, id): | ||
3255 | 307 | return exc.HTTPNotImplemented() | ||
3256 | 308 | |||
3257 | 284 | def _action_confirm_resize(self, input_dict, req, id): | 309 | def _action_confirm_resize(self, input_dict, req, id): |
3258 | 285 | try: | 310 | try: |
3259 | 286 | self.compute_api.confirm_resize(req.environ['nova.context'], id) | 311 | self.compute_api.confirm_resize(req.environ['nova.context'], id) |
3260 | @@ -479,7 +504,7 @@ | |||
3261 | 479 | 504 | ||
3262 | 480 | @scheduler_api.redirect_handler | 505 | @scheduler_api.redirect_handler |
3263 | 481 | def get_ajax_console(self, req, id): | 506 | def get_ajax_console(self, req, id): |
3265 | 482 | """ Returns a url to an instance's ajaxterm console. """ | 507 | """Returns a url to an instance's ajaxterm console.""" |
3266 | 483 | try: | 508 | try: |
3267 | 484 | self.compute_api.get_ajax_console(req.environ['nova.context'], | 509 | self.compute_api.get_ajax_console(req.environ['nova.context'], |
3268 | 485 | int(id)) | 510 | int(id)) |
3269 | @@ -488,6 +513,16 @@ | |||
3270 | 488 | return exc.HTTPAccepted() | 513 | return exc.HTTPAccepted() |
3271 | 489 | 514 | ||
3272 | 490 | @scheduler_api.redirect_handler | 515 | @scheduler_api.redirect_handler |
3273 | 516 | def get_vnc_console(self, req, id): | ||
3274 | 517 | """Returns a url to an instance's ajaxterm console.""" | ||
3275 | 518 | try: | ||
3276 | 519 | self.compute_api.get_vnc_console(req.environ['nova.context'], | ||
3277 | 520 | int(id)) | ||
3278 | 521 | except exception.NotFound: | ||
3279 | 522 | return faults.Fault(exc.HTTPNotFound()) | ||
3280 | 523 | return exc.HTTPAccepted() | ||
3281 | 524 | |||
3282 | 525 | @scheduler_api.redirect_handler | ||
3283 | 491 | def diagnostics(self, req, id): | 526 | def diagnostics(self, req, id): |
3284 | 492 | """Permit Admins to retrieve server diagnostics.""" | 527 | """Permit Admins to retrieve server diagnostics.""" |
3285 | 493 | ctxt = req.environ["nova.context"] | 528 | ctxt = req.environ["nova.context"] |
3286 | @@ -532,7 +567,7 @@ | |||
3287 | 532 | _("Cannot build from image %(image_id)s, status not active") % | 567 | _("Cannot build from image %(image_id)s, status not active") % |
3288 | 533 | locals()) | 568 | locals()) |
3289 | 534 | 569 | ||
3291 | 535 | if image_meta['properties']['disk_format'] != 'ami': | 570 | if image_meta.get('container_format') != 'ami': |
3292 | 536 | return None, None | 571 | return None, None |
3293 | 537 | 572 | ||
3294 | 538 | try: | 573 | try: |
3295 | @@ -568,6 +603,14 @@ | |||
3296 | 568 | def _limit_items(self, items, req): | 603 | def _limit_items(self, items, req): |
3297 | 569 | return common.limited(items, req) | 604 | return common.limited(items, req) |
3298 | 570 | 605 | ||
3299 | 606 | def _parse_update(self, context, server_id, inst_dict, update_dict): | ||
3300 | 607 | if 'adminPass' in inst_dict['server']: | ||
3301 | 608 | update_dict['admin_pass'] = inst_dict['server']['adminPass'] | ||
3302 | 609 | try: | ||
3303 | 610 | self.compute_api.set_admin_password(context, server_id) | ||
3304 | 611 | except exception.TimeoutException: | ||
3305 | 612 | return exc.HTTPRequestTimeout() | ||
3306 | 613 | |||
3307 | 571 | 614 | ||
3308 | 572 | class ControllerV11(Controller): | 615 | class ControllerV11(Controller): |
3309 | 573 | def _image_id_from_req_data(self, data): | 616 | def _image_id_from_req_data(self, data): |
3310 | @@ -591,9 +634,25 @@ | |||
3311 | 591 | def _get_addresses_view_builder(self, req): | 634 | def _get_addresses_view_builder(self, req): |
3312 | 592 | return nova.api.openstack.views.addresses.ViewBuilderV11(req) | 635 | return nova.api.openstack.views.addresses.ViewBuilderV11(req) |
3313 | 593 | 636 | ||
3314 | 637 | def _action_change_password(self, input_dict, req, id): | ||
3315 | 638 | context = req.environ['nova.context'] | ||
3316 | 639 | if (not 'changePassword' in input_dict | ||
3317 | 640 | or not 'adminPass' in input_dict['changePassword']): | ||
3318 | 641 | msg = _("No adminPass was specified") | ||
3319 | 642 | return exc.HTTPBadRequest(msg) | ||
3320 | 643 | password = input_dict['changePassword']['adminPass'] | ||
3321 | 644 | if not isinstance(password, basestring) or password == '': | ||
3322 | 645 | msg = _("Invalid adminPass") | ||
3323 | 646 | return exc.HTTPBadRequest(msg) | ||
3324 | 647 | self.compute_api.set_admin_password(context, id, password) | ||
3325 | 648 | return exc.HTTPAccepted() | ||
3326 | 649 | |||
3327 | 594 | def _limit_items(self, items, req): | 650 | def _limit_items(self, items, req): |
3328 | 595 | return common.limited_by_marker(items, req) | 651 | return common.limited_by_marker(items, req) |
3329 | 596 | 652 | ||
3330 | 653 | def get_default_xmlns(self, req): | ||
3331 | 654 | return common.XML_NS_V11 | ||
3332 | 655 | |||
3333 | 597 | 656 | ||
3334 | 598 | class ServerCreateRequestXMLDeserializer(object): | 657 | class ServerCreateRequestXMLDeserializer(object): |
3335 | 599 | """ | 658 | """ |
3336 | 600 | 659 | ||
3337 | === modified file 'nova/api/openstack/shared_ip_groups.py' | |||
3338 | --- nova/api/openstack/shared_ip_groups.py 2011-03-09 18:10:45 +0000 | |||
3339 | +++ nova/api/openstack/shared_ip_groups.py 2011-04-12 11:01:25 +0000 | |||
3340 | @@ -17,7 +17,7 @@ | |||
3341 | 17 | 17 | ||
3342 | 18 | from webob import exc | 18 | from webob import exc |
3343 | 19 | 19 | ||
3345 | 20 | from nova import wsgi | 20 | from nova.api.openstack import common |
3346 | 21 | from nova.api.openstack import faults | 21 | from nova.api.openstack import faults |
3347 | 22 | 22 | ||
3348 | 23 | 23 | ||
3349 | @@ -32,7 +32,7 @@ | |||
3350 | 32 | return dict(sharedIpGroups=inst) | 32 | return dict(sharedIpGroups=inst) |
3351 | 33 | 33 | ||
3352 | 34 | 34 | ||
3354 | 35 | class Controller(wsgi.Controller): | 35 | class Controller(common.OpenstackController): |
3355 | 36 | """ The Shared IP Groups Controller for the Openstack API """ | 36 | """ The Shared IP Groups Controller for the Openstack API """ |
3356 | 37 | 37 | ||
3357 | 38 | _serialization_metadata = { | 38 | _serialization_metadata = { |
3358 | @@ -42,11 +42,11 @@ | |||
3359 | 42 | 42 | ||
3360 | 43 | def index(self, req): | 43 | def index(self, req): |
3361 | 44 | """ Returns a list of Shared IP Groups for the user """ | 44 | """ Returns a list of Shared IP Groups for the user """ |
3363 | 45 | return dict(sharedIpGroups=[]) | 45 | raise faults.Fault(exc.HTTPNotImplemented()) |
3364 | 46 | 46 | ||
3365 | 47 | def show(self, req, id): | 47 | def show(self, req, id): |
3366 | 48 | """ Shows in-depth information on a specific Shared IP Group """ | 48 | """ Shows in-depth information on a specific Shared IP Group """ |
3368 | 49 | return _translate_keys({}) | 49 | raise faults.Fault(exc.HTTPNotImplemented()) |
3369 | 50 | 50 | ||
3370 | 51 | def update(self, req, id): | 51 | def update(self, req, id): |
3371 | 52 | """ You can't update a Shared IP Group """ | 52 | """ You can't update a Shared IP Group """ |
3372 | @@ -58,7 +58,7 @@ | |||
3373 | 58 | 58 | ||
3374 | 59 | def detail(self, req): | 59 | def detail(self, req): |
3375 | 60 | """ Returns a complete list of Shared IP Groups """ | 60 | """ Returns a complete list of Shared IP Groups """ |
3377 | 61 | return _translate_detail_keys({}) | 61 | raise faults.Fault(exc.HTTPNotImplemented()) |
3378 | 62 | 62 | ||
3379 | 63 | def create(self, req): | 63 | def create(self, req): |
3380 | 64 | """ Creates a new Shared IP group """ | 64 | """ Creates a new Shared IP group """ |
3381 | 65 | 65 | ||
3382 | === modified file 'nova/api/openstack/users.py' | |||
3383 | --- nova/api/openstack/users.py 2011-03-16 19:15:57 +0000 | |||
3384 | +++ nova/api/openstack/users.py 2011-04-12 11:01:25 +0000 | |||
3385 | @@ -18,7 +18,6 @@ | |||
3386 | 18 | from nova import exception | 18 | from nova import exception |
3387 | 19 | from nova import flags | 19 | from nova import flags |
3388 | 20 | from nova import log as logging | 20 | from nova import log as logging |
3389 | 21 | from nova import wsgi | ||
3390 | 22 | from nova.api.openstack import common | 21 | from nova.api.openstack import common |
3391 | 23 | from nova.api.openstack import faults | 22 | from nova.api.openstack import faults |
3392 | 24 | from nova.auth import manager | 23 | from nova.auth import manager |
3393 | @@ -35,7 +34,7 @@ | |||
3394 | 35 | admin=user.admin) | 34 | admin=user.admin) |
3395 | 36 | 35 | ||
3396 | 37 | 36 | ||
3398 | 38 | class Controller(wsgi.Controller): | 37 | class Controller(common.OpenstackController): |
3399 | 39 | 38 | ||
3400 | 40 | _serialization_metadata = { | 39 | _serialization_metadata = { |
3401 | 41 | 'application/xml': { | 40 | 'application/xml': { |
3402 | 42 | 41 | ||
3403 | === modified file 'nova/api/openstack/versions.py' | |||
3404 | --- nova/api/openstack/versions.py 2011-03-22 18:09:23 +0000 | |||
3405 | +++ nova/api/openstack/versions.py 2011-04-12 11:01:25 +0000 | |||
3406 | @@ -15,8 +15,8 @@ | |||
3407 | 15 | # License for the specific language governing permissions and limitations | 15 | # License for the specific language governing permissions and limitations |
3408 | 16 | # under the License. | 16 | # under the License. |
3409 | 17 | 17 | ||
3410 | 18 | import webob | ||
3411 | 18 | import webob.dec | 19 | import webob.dec |
3412 | 19 | import webob.exc | ||
3413 | 20 | 20 | ||
3414 | 21 | from nova import wsgi | 21 | from nova import wsgi |
3415 | 22 | import nova.api.openstack.views.versions | 22 | import nova.api.openstack.views.versions |
3416 | @@ -51,4 +51,10 @@ | |||
3417 | 51 | } | 51 | } |
3418 | 52 | 52 | ||
3419 | 53 | content_type = req.best_match_content_type() | 53 | content_type = req.best_match_content_type() |
3421 | 54 | return wsgi.Serializer(metadata).serialize(response, content_type) | 54 | body = wsgi.Serializer(metadata).serialize(response, content_type) |
3422 | 55 | |||
3423 | 56 | response = webob.Response() | ||
3424 | 57 | response.content_type = content_type | ||
3425 | 58 | response.body = body | ||
3426 | 59 | |||
3427 | 60 | return response | ||
3428 | 55 | 61 | ||
3429 | === modified file 'nova/api/openstack/views/addresses.py' | |||
3430 | --- nova/api/openstack/views/addresses.py 2011-03-17 07:21:09 +0000 | |||
3431 | +++ nova/api/openstack/views/addresses.py 2011-04-12 11:01:25 +0000 | |||
3432 | @@ -28,10 +28,16 @@ | |||
3433 | 28 | 28 | ||
3434 | 29 | class ViewBuilderV10(ViewBuilder): | 29 | class ViewBuilderV10(ViewBuilder): |
3435 | 30 | def build(self, inst): | 30 | def build(self, inst): |
3438 | 31 | private_ips = utils.get_from_path(inst, 'fixed_ip/address') | 31 | private_ips = self.build_private_parts(inst) |
3439 | 32 | public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') | 32 | public_ips = self.build_public_parts(inst) |
3440 | 33 | return dict(public=public_ips, private=private_ips) | 33 | return dict(public=public_ips, private=private_ips) |
3441 | 34 | 34 | ||
3442 | 35 | def build_public_parts(self, inst): | ||
3443 | 36 | return utils.get_from_path(inst, 'fixed_ip/floating_ips/address') | ||
3444 | 37 | |||
3445 | 38 | def build_private_parts(self, inst): | ||
3446 | 39 | return utils.get_from_path(inst, 'fixed_ip/address') | ||
3447 | 40 | |||
3448 | 35 | 41 | ||
3449 | 36 | class ViewBuilderV11(ViewBuilder): | 42 | class ViewBuilderV11(ViewBuilder): |
3450 | 37 | def build(self, inst): | 43 | def build(self, inst): |
3451 | 38 | 44 | ||
3452 | === modified file 'nova/api/openstack/views/images.py' | |||
3453 | --- nova/api/openstack/views/images.py 2011-03-17 07:41:01 +0000 | |||
3454 | +++ nova/api/openstack/views/images.py 2011-04-12 11:01:25 +0000 | |||
3455 | @@ -15,20 +15,100 @@ | |||
3456 | 15 | # License for the specific language governing permissions and limitations | 15 | # License for the specific language governing permissions and limitations |
3457 | 16 | # under the License. | 16 | # under the License. |
3458 | 17 | 17 | ||
3460 | 18 | from nova.api.openstack import common | 18 | import os.path |
3461 | 19 | 19 | ||
3462 | 20 | 20 | ||
3463 | 21 | class ViewBuilder(object): | 21 | class ViewBuilder(object): |
3472 | 22 | def __init__(self): | 22 | """Base class for generating responses to OpenStack API image requests.""" |
3473 | 23 | pass | 23 | |
3466 | 24 | |||
3467 | 25 | def build(self, image_obj): | ||
3468 | 26 | raise NotImplementedError() | ||
3469 | 27 | |||
3470 | 28 | |||
3471 | 29 | class ViewBuilderV11(ViewBuilder): | ||
3474 | 30 | def __init__(self, base_url): | 24 | def __init__(self, base_url): |
3476 | 31 | self.base_url = base_url | 25 | """Initialize new `ViewBuilder`.""" |
3477 | 26 | self._url = base_url | ||
3478 | 27 | |||
3479 | 28 | def _format_dates(self, image): | ||
3480 | 29 | """Update all date fields to ensure standardized formatting.""" | ||
3481 | 30 | for attr in ['created_at', 'updated_at', 'deleted_at']: | ||
3482 | 31 | if image.get(attr) is not None: | ||
3483 | 32 | image[attr] = image[attr].strftime('%Y-%m-%dT%H:%M:%SZ') | ||
3484 | 33 | |||
3485 | 34 | def _format_status(self, image): | ||
3486 | 35 | """Update the status field to standardize format.""" | ||
3487 | 36 | status_mapping = { | ||
3488 | 37 | 'pending': 'QUEUED', | ||
3489 | 38 | 'decrypting': 'PREPARING', | ||
3490 | 39 | 'untarring': 'SAVING', | ||
3491 | 40 | 'available': 'ACTIVE', | ||
3492 | 41 | 'killed': 'FAILED', | ||
3493 | 42 | } | ||
3494 | 43 | |||
3495 | 44 | try: | ||
3496 | 45 | image['status'] = status_mapping[image['status']].upper() | ||
3497 | 46 | except KeyError: | ||
3498 | 47 | image['status'] = image['status'].upper() | ||
3499 | 32 | 48 | ||
3500 | 33 | def generate_href(self, image_id): | 49 | def generate_href(self, image_id): |
3502 | 34 | return "%s/images/%s" % (self.base_url, image_id) | 50 | """Return an href string pointing to this object.""" |
3503 | 51 | return os.path.join(self._url, "images", str(image_id)) | ||
3504 | 52 | |||
3505 | 53 | def build(self, image_obj, detail=False): | ||
3506 | 54 | """Return a standardized image structure for display by the API.""" | ||
3507 | 55 | properties = image_obj.get("properties", {}) | ||
3508 | 56 | |||
3509 | 57 | self._format_dates(image_obj) | ||
3510 | 58 | |||
3511 | 59 | if "status" in image_obj: | ||
3512 | 60 | self._format_status(image_obj) | ||
3513 | 61 | |||
3514 | 62 | image = { | ||
3515 | 63 | "id": image_obj.get("id"), | ||
3516 | 64 | "name": image_obj.get("name"), | ||
3517 | 65 | } | ||
3518 | 66 | |||
3519 | 67 | if "instance_id" in properties: | ||
3520 | 68 | try: | ||
3521 | 69 | image["serverId"] = int(properties["instance_id"]) | ||
3522 | 70 | except ValueError: | ||
3523 | 71 | pass | ||
3524 | 72 | |||
3525 | 73 | if detail: | ||
3526 | 74 | image.update({ | ||
3527 | 75 | "created": image_obj.get("created_at"), | ||
3528 | 76 | "updated": image_obj.get("updated_at"), | ||
3529 | 77 | "status": image_obj.get("status"), | ||
3530 | 78 | }) | ||
3531 | 79 | |||
3532 | 80 | if image["status"] == "SAVING": | ||
3533 | 81 | image["progress"] = 0 | ||
3534 | 82 | |||
3535 | 83 | return image | ||
3536 | 84 | |||
3537 | 85 | |||
3538 | 86 | class ViewBuilderV10(ViewBuilder): | ||
3539 | 87 | """OpenStack API v1.0 Image Builder""" | ||
3540 | 88 | pass | ||
3541 | 89 | |||
3542 | 90 | |||
3543 | 91 | class ViewBuilderV11(ViewBuilder): | ||
3544 | 92 | """OpenStack API v1.1 Image Builder""" | ||
3545 | 93 | |||
3546 | 94 | def build(self, image_obj, detail=False): | ||
3547 | 95 | """Return a standardized image structure for display by the API.""" | ||
3548 | 96 | image = ViewBuilder.build(self, image_obj, detail) | ||
3549 | 97 | href = self.generate_href(image_obj["id"]) | ||
3550 | 98 | |||
3551 | 99 | image["links"] = [{ | ||
3552 | 100 | "rel": "self", | ||
3553 | 101 | "href": href, | ||
3554 | 102 | }, | ||
3555 | 103 | { | ||
3556 | 104 | "rel": "bookmark", | ||
3557 | 105 | "type": "application/json", | ||
3558 | 106 | "href": href, | ||
3559 | 107 | }, | ||
3560 | 108 | { | ||
3561 | 109 | "rel": "bookmark", | ||
3562 | 110 | "type": "application/xml", | ||
3563 | 111 | "href": href, | ||
3564 | 112 | }] | ||
3565 | 113 | |||
3566 | 114 | return image | ||
3567 | 35 | 115 | ||
3568 | === modified file 'nova/api/openstack/views/servers.py' | |||
3569 | --- nova/api/openstack/views/servers.py 2011-03-24 14:15:50 +0000 | |||
3570 | +++ nova/api/openstack/views/servers.py 2011-04-12 11:01:25 +0000 | |||
3571 | @@ -57,16 +57,16 @@ | |||
3572 | 57 | def _build_detail(self, inst): | 57 | def _build_detail(self, inst): |
3573 | 58 | """Returns a detailed model of a server.""" | 58 | """Returns a detailed model of a server.""" |
3574 | 59 | power_mapping = { | 59 | power_mapping = { |
3585 | 60 | None: 'build', | 60 | None: 'BUILD', |
3586 | 61 | power_state.NOSTATE: 'build', | 61 | power_state.NOSTATE: 'BUILD', |
3587 | 62 | power_state.RUNNING: 'active', | 62 | power_state.RUNNING: 'ACTIVE', |
3588 | 63 | power_state.BLOCKED: 'active', | 63 | power_state.BLOCKED: 'ACTIVE', |
3589 | 64 | power_state.SUSPENDED: 'suspended', | 64 | power_state.SUSPENDED: 'SUSPENDED', |
3590 | 65 | power_state.PAUSED: 'paused', | 65 | power_state.PAUSED: 'PAUSED', |
3591 | 66 | power_state.SHUTDOWN: 'active', | 66 | power_state.SHUTDOWN: 'ACTIVE', |
3592 | 67 | power_state.SHUTOFF: 'active', | 67 | power_state.SHUTOFF: 'ACTIVE', |
3593 | 68 | power_state.CRASHED: 'error', | 68 | power_state.CRASHED: 'ERROR', |
3594 | 69 | power_state.FAILED: 'error'} | 69 | power_state.FAILED: 'ERROR'} |
3595 | 70 | 70 | ||
3596 | 71 | inst_dict = { | 71 | inst_dict = { |
3597 | 72 | 'id': int(inst['id']), | 72 | 'id': int(inst['id']), |
3598 | @@ -77,12 +77,12 @@ | |||
3599 | 77 | ctxt = nova.context.get_admin_context() | 77 | ctxt = nova.context.get_admin_context() |
3600 | 78 | compute_api = nova.compute.API() | 78 | compute_api = nova.compute.API() |
3601 | 79 | if compute_api.has_finished_migration(ctxt, inst['id']): | 79 | if compute_api.has_finished_migration(ctxt, inst['id']): |
3603 | 80 | inst_dict['status'] = 'resize-confirm' | 80 | inst_dict['status'] = 'RESIZE-CONFIRM' |
3604 | 81 | 81 | ||
3605 | 82 | # Return the metadata as a dictionary | 82 | # Return the metadata as a dictionary |
3606 | 83 | metadata = {} | 83 | metadata = {} |
3607 | 84 | for item in inst.get('metadata', []): | 84 | for item in inst.get('metadata', []): |
3609 | 85 | metadata[item['key']] = item['value'] | 85 | metadata[item['key']] = str(item['value']) |
3610 | 86 | inst_dict['metadata'] = metadata | 86 | inst_dict['metadata'] = metadata |
3611 | 87 | 87 | ||
3612 | 88 | inst_dict['hostId'] = '' | 88 | inst_dict['hostId'] = '' |
3613 | @@ -115,7 +115,7 @@ | |||
3614 | 115 | 115 | ||
3615 | 116 | def _build_flavor(self, response, inst): | 116 | def _build_flavor(self, response, inst): |
3616 | 117 | if 'instance_type' in dict(inst): | 117 | if 'instance_type' in dict(inst): |
3618 | 118 | response['flavorId'] = inst['instance_type'] | 118 | response['flavorId'] = inst['instance_type']['flavorid'] |
3619 | 119 | 119 | ||
3620 | 120 | 120 | ||
3621 | 121 | class ViewBuilderV11(ViewBuilder): | 121 | class ViewBuilderV11(ViewBuilder): |
3622 | @@ -134,7 +134,7 @@ | |||
3623 | 134 | 134 | ||
3624 | 135 | def _build_flavor(self, response, inst): | 135 | def _build_flavor(self, response, inst): |
3625 | 136 | if "instance_type" in dict(inst): | 136 | if "instance_type" in dict(inst): |
3627 | 137 | flavor_id = inst["instance_type"] | 137 | flavor_id = inst["instance_type"]['flavorid'] |
3628 | 138 | flavor_ref = self.flavor_builder.generate_href(flavor_id) | 138 | flavor_ref = self.flavor_builder.generate_href(flavor_id) |
3629 | 139 | response["flavorRef"] = flavor_ref | 139 | response["flavorRef"] = flavor_ref |
3630 | 140 | 140 | ||
3631 | 141 | 141 | ||
3632 | === modified file 'nova/api/openstack/zones.py' | |||
3633 | --- nova/api/openstack/zones.py 2011-03-24 19:04:24 +0000 | |||
3634 | +++ nova/api/openstack/zones.py 2011-04-12 11:01:25 +0000 | |||
3635 | @@ -13,12 +13,10 @@ | |||
3636 | 13 | # License for the specific language governing permissions and limitations | 13 | # License for the specific language governing permissions and limitations |
3637 | 14 | # under the License. | 14 | # under the License. |
3638 | 15 | 15 | ||
3639 | 16 | import common | ||
3640 | 17 | |||
3641 | 18 | from nova import db | 16 | from nova import db |
3642 | 19 | from nova import flags | 17 | from nova import flags |
3643 | 20 | from nova import log as logging | 18 | from nova import log as logging |
3645 | 21 | from nova import wsgi | 19 | from nova.api.openstack import common |
3646 | 22 | from nova.scheduler import api | 20 | from nova.scheduler import api |
3647 | 23 | 21 | ||
3648 | 24 | 22 | ||
3649 | @@ -43,7 +41,7 @@ | |||
3650 | 43 | 'deleted', 'deleted_at', 'updated_at')) | 41 | 'deleted', 'deleted_at', 'updated_at')) |
3651 | 44 | 42 | ||
3652 | 45 | 43 | ||
3654 | 46 | class Controller(wsgi.Controller): | 44 | class Controller(common.OpenstackController): |
3655 | 47 | 45 | ||
3656 | 48 | _serialization_metadata = { | 46 | _serialization_metadata = { |
3657 | 49 | 'application/xml': { | 47 | 'application/xml': { |
3658 | 50 | 48 | ||
3659 | === modified file 'nova/compute/api.py' | |||
3660 | --- nova/compute/api.py 2011-04-07 11:20:06 +0000 | |||
3661 | +++ nova/compute/api.py 2011-04-12 11:01:25 +0000 | |||
3662 | @@ -38,8 +38,12 @@ | |||
3663 | 38 | from nova.db import base | 38 | from nova.db import base |
3664 | 39 | from nova.network import service as net_service | 39 | from nova.network import service as net_service |
3665 | 40 | 40 | ||
3666 | 41 | |||
3667 | 42 | LOG = logging.getLogger('nova.compute.api') | ||
3668 | 43 | |||
3669 | 44 | |||
3670 | 41 | FLAGS = flags.FLAGS | 45 | FLAGS = flags.FLAGS |
3672 | 42 | LOG = logging.getLogger('nova.compute.api') | 46 | flags.DECLARE('vncproxy_topic', 'nova.vnc') |
3673 | 43 | 47 | ||
3674 | 44 | def generate_default_hostname(instance_id): | 48 | def generate_default_hostname(instance_id): |
3675 | 45 | """Default function to generate a hostname given an instance reference.""" | 49 | """Default function to generate a hostname given an instance reference.""" |
3676 | @@ -107,8 +111,11 @@ | |||
3677 | 107 | """Create the number of instances requested if quota and | 111 | """Create the number of instances requested if quota and |
3678 | 108 | other arguments check out ok.""" | 112 | other arguments check out ok.""" |
3679 | 109 | 113 | ||
3682 | 110 | type_data = instance_types.get_instance_type(instance_type) | 114 | if not instance_type: |
3683 | 111 | num_instances = quota.allowed_instances(context, max_count, type_data) | 115 | instance_type = instance_types.get_default_instance_type() |
3684 | 116 | |||
3685 | 117 | num_instances = quota.allowed_instances(context, max_count, | ||
3686 | 118 | instance_type) | ||
3687 | 112 | if num_instances < min_count: | 119 | if num_instances < min_count: |
3688 | 113 | pid = context.project_id | 120 | pid = context.project_id |
3689 | 114 | LOG.warn(_("Quota exceeeded for %(pid)s," | 121 | LOG.warn(_("Quota exceeeded for %(pid)s," |
3690 | @@ -194,10 +201,10 @@ | |||
3691 | 194 | 'user_id': context.user_id, | 201 | 'user_id': context.user_id, |
3692 | 195 | 'project_id': context.project_id, | 202 | 'project_id': context.project_id, |
3693 | 196 | 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), | 203 | 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), |
3698 | 197 | 'instance_type': instance_type, | 204 | 'instance_type_id': instance_type['id'], |
3699 | 198 | 'memory_mb': type_data['memory_mb'], | 205 | 'memory_mb': instance_type['memory_mb'], |
3700 | 199 | 'vcpus': type_data['vcpus'], | 206 | 'vcpus': instance_type['vcpus'], |
3701 | 200 | 'local_gb': type_data['local_gb'], | 207 | 'local_gb': instance_type['local_gb'], |
3702 | 201 | 'display_name': display_name, | 208 | 'display_name': display_name, |
3703 | 202 | 'display_description': display_description, | 209 | 'display_description': display_description, |
3704 | 203 | 'user_data': user_data or '', | 210 | 'user_data': user_data or '', |
3705 | @@ -373,11 +380,15 @@ | |||
3706 | 373 | instance_id) | 380 | instance_id) |
3707 | 374 | raise | 381 | raise |
3708 | 375 | 382 | ||
3710 | 376 | if (instance['state_description'] == 'terminating'): | 383 | if instance['state_description'] == 'terminating': |
3711 | 377 | LOG.warning(_("Instance %s is already being terminated"), | 384 | LOG.warning(_("Instance %s is already being terminated"), |
3712 | 378 | instance_id) | 385 | instance_id) |
3713 | 379 | return | 386 | return |
3714 | 380 | 387 | ||
3715 | 388 | if instance['state_description'] == 'migrating': | ||
3716 | 389 | LOG.warning(_("Instance %s is being migrated"), instance_id) | ||
3717 | 390 | return | ||
3718 | 391 | |||
3719 | 381 | self.update(context, | 392 | self.update(context, |
3720 | 382 | instance['id'], | 393 | instance['id'], |
3721 | 383 | state_description='terminating', | 394 | state_description='terminating', |
3722 | @@ -531,8 +542,7 @@ | |||
3723 | 531 | def resize(self, context, instance_id, flavor_id): | 542 | def resize(self, context, instance_id, flavor_id): |
3724 | 532 | """Resize a running instance.""" | 543 | """Resize a running instance.""" |
3725 | 533 | instance = self.db.instance_get(context, instance_id) | 544 | instance = self.db.instance_get(context, instance_id) |
3728 | 534 | current_instance_type = self.db.instance_type_get_by_name( | 545 | current_instance_type = instance['instance_type'] |
3727 | 535 | context, instance['instance_type']) | ||
3729 | 536 | 546 | ||
3730 | 537 | new_instance_type = self.db.instance_type_get_by_flavor_id( | 547 | new_instance_type = self.db.instance_type_get_by_flavor_id( |
3731 | 538 | context, flavor_id) | 548 | context, flavor_id) |
3732 | @@ -622,6 +632,25 @@ | |||
3733 | 622 | return {'url': '%s/?token=%s' % (FLAGS.ajax_console_proxy_url, | 632 | return {'url': '%s/?token=%s' % (FLAGS.ajax_console_proxy_url, |
3734 | 623 | output['token'])} | 633 | output['token'])} |
3735 | 624 | 634 | ||
3736 | 635 | def get_vnc_console(self, context, instance_id): | ||
3737 | 636 | """Get a url to a VNC Console.""" | ||
3738 | 637 | instance = self.get(context, instance_id) | ||
3739 | 638 | output = self._call_compute_message('get_vnc_console', | ||
3740 | 639 | context, | ||
3741 | 640 | instance_id) | ||
3742 | 641 | rpc.call(context, '%s' % FLAGS.vncproxy_topic, | ||
3743 | 642 | {'method': 'authorize_vnc_console', | ||
3744 | 643 | 'args': {'token': output['token'], | ||
3745 | 644 | 'host': output['host'], | ||
3746 | 645 | 'port': output['port']}}) | ||
3747 | 646 | |||
3748 | 647 | # hostignore and portignore are compatability params for noVNC | ||
3749 | 648 | return {'url': '%s/vnc_auto.html?token=%s&host=%s&port=%s' % ( | ||
3750 | 649 | FLAGS.vncproxy_url, | ||
3751 | 650 | output['token'], | ||
3752 | 651 | 'hostignore', | ||
3753 | 652 | 'portignore')} | ||
3754 | 653 | |||
3755 | 625 | def get_console_output(self, context, instance_id): | 654 | def get_console_output(self, context, instance_id): |
3756 | 626 | """Get console output for an an instance""" | 655 | """Get console output for an an instance""" |
3757 | 627 | return self._call_compute_message('get_console_output', | 656 | return self._call_compute_message('get_console_output', |
3758 | 628 | 657 | ||
3759 | === modified file 'nova/compute/instance_types.py' | |||
3760 | --- nova/compute/instance_types.py 2011-03-03 01:38:42 +0000 | |||
3761 | +++ nova/compute/instance_types.py 2011-04-12 11:01:25 +0000 | |||
3762 | @@ -59,8 +59,8 @@ | |||
3763 | 59 | rxtx_quota=rxtx_quota, | 59 | rxtx_quota=rxtx_quota, |
3764 | 60 | rxtx_cap=rxtx_cap)) | 60 | rxtx_cap=rxtx_cap)) |
3765 | 61 | except exception.DBError, e: | 61 | except exception.DBError, e: |
3768 | 62 | LOG.exception(_('DB error: %s' % e)) | 62 | LOG.exception(_('DB error: %s') % e) |
3769 | 63 | raise exception.ApiError(_("Cannot create instance type: %s" % name)) | 63 | raise exception.ApiError(_("Cannot create instance type: %s") % name) |
3770 | 64 | 64 | ||
3771 | 65 | 65 | ||
3772 | 66 | def destroy(name): | 66 | def destroy(name): |
3773 | @@ -72,8 +72,8 @@ | |||
3774 | 72 | try: | 72 | try: |
3775 | 73 | db.instance_type_destroy(context.get_admin_context(), name) | 73 | db.instance_type_destroy(context.get_admin_context(), name) |
3776 | 74 | except exception.NotFound: | 74 | except exception.NotFound: |
3779 | 75 | LOG.exception(_('Instance type %s not found for deletion' % name)) | 75 | LOG.exception(_('Instance type %s not found for deletion') % name) |
3780 | 76 | raise exception.ApiError(_("Unknown instance type: %s" % name)) | 76 | raise exception.ApiError(_("Unknown instance type: %s") % name) |
3781 | 77 | 77 | ||
3782 | 78 | 78 | ||
3783 | 79 | def purge(name): | 79 | def purge(name): |
3784 | @@ -85,8 +85,8 @@ | |||
3785 | 85 | try: | 85 | try: |
3786 | 86 | db.instance_type_purge(context.get_admin_context(), name) | 86 | db.instance_type_purge(context.get_admin_context(), name) |
3787 | 87 | except exception.NotFound: | 87 | except exception.NotFound: |
3790 | 88 | LOG.exception(_('Instance type %s not found for purge' % name)) | 88 | LOG.exception(_('Instance type %s not found for purge') % name) |
3791 | 89 | raise exception.ApiError(_("Unknown instance type: %s" % name)) | 89 | raise exception.ApiError(_("Unknown instance type: %s") % name) |
3792 | 90 | 90 | ||
3793 | 91 | 91 | ||
3794 | 92 | def get_all_types(inactive=0): | 92 | def get_all_types(inactive=0): |
3795 | @@ -101,41 +101,43 @@ | |||
3796 | 101 | return get_all_types(context.get_admin_context()) | 101 | return get_all_types(context.get_admin_context()) |
3797 | 102 | 102 | ||
3798 | 103 | 103 | ||
3800 | 104 | def get_instance_type(name): | 104 | def get_default_instance_type(): |
3801 | 105 | name = FLAGS.default_instance_type | ||
3802 | 106 | try: | ||
3803 | 107 | return get_instance_type_by_name(name) | ||
3804 | 108 | except exception.DBError: | ||
3805 | 109 | raise exception.ApiError(_("Unknown instance type: %s") % name) | ||
3806 | 110 | |||
3807 | 111 | |||
3808 | 112 | def get_instance_type(id): | ||
3809 | 113 | """Retrieves single instance type by id""" | ||
3810 | 114 | if id is None: | ||
3811 | 115 | return get_default_instance_type() | ||
3812 | 116 | try: | ||
3813 | 117 | ctxt = context.get_admin_context() | ||
3814 | 118 | return db.instance_type_get_by_id(ctxt, id) | ||
3815 | 119 | except exception.DBError: | ||
3816 | 120 | raise exception.ApiError(_("Unknown instance type: %s") % name) | ||
3817 | 121 | |||
3818 | 122 | |||
3819 | 123 | def get_instance_type_by_name(name): | ||
3820 | 105 | """Retrieves single instance type by name""" | 124 | """Retrieves single instance type by name""" |
3821 | 106 | if name is None: | 125 | if name is None: |
3823 | 107 | return FLAGS.default_instance_type | 126 | return get_default_instance_type() |
3824 | 108 | try: | 127 | try: |
3825 | 109 | ctxt = context.get_admin_context() | 128 | ctxt = context.get_admin_context() |
3828 | 110 | inst_type = db.instance_type_get_by_name(ctxt, name) | 129 | return db.instance_type_get_by_name(ctxt, name) |
3827 | 111 | return inst_type | ||
3829 | 112 | except exception.DBError: | 130 | except exception.DBError: |
3850 | 113 | raise exception.ApiError(_("Unknown instance type: %s" % name)) | 131 | raise exception.ApiError(_("Unknown instance type: %s") % name) |
3851 | 114 | 132 | ||
3852 | 115 | 133 | ||
3853 | 116 | def get_by_type(instance_type): | 134 | def get_instance_type_by_flavor_id(flavor_id): |
3854 | 117 | """retrieve instance type name""" | 135 | """retrieve instance type by flavor_id""" |
3835 | 118 | if instance_type is None: | ||
3836 | 119 | return FLAGS.default_instance_type | ||
3837 | 120 | |||
3838 | 121 | try: | ||
3839 | 122 | ctxt = context.get_admin_context() | ||
3840 | 123 | inst_type = db.instance_type_get_by_name(ctxt, instance_type) | ||
3841 | 124 | return inst_type['name'] | ||
3842 | 125 | except exception.DBError, e: | ||
3843 | 126 | LOG.exception(_('DB error: %s' % e)) | ||
3844 | 127 | raise exception.ApiError(_("Unknown instance type: %s" %\ | ||
3845 | 128 | instance_type)) | ||
3846 | 129 | |||
3847 | 130 | |||
3848 | 131 | def get_by_flavor_id(flavor_id): | ||
3849 | 132 | """retrieve instance type's name by flavor_id""" | ||
3855 | 133 | if flavor_id is None: | 136 | if flavor_id is None: |
3857 | 134 | return FLAGS.default_instance_type | 137 | return get_default_instance_type() |
3858 | 135 | try: | 138 | try: |
3859 | 136 | ctxt = context.get_admin_context() | 139 | ctxt = context.get_admin_context() |
3862 | 137 | flavor = db.instance_type_get_by_flavor_id(ctxt, flavor_id) | 140 | return db.instance_type_get_by_flavor_id(ctxt, flavor_id) |
3861 | 138 | return flavor['name'] | ||
3863 | 139 | except exception.DBError, e: | 141 | except exception.DBError, e: |
3866 | 140 | LOG.exception(_('DB error: %s' % e)) | 142 | LOG.exception(_('DB error: %s') % e) |
3867 | 141 | raise exception.ApiError(_("Unknown flavor: %s" % flavor_id)) | 143 | raise exception.ApiError(_("Unknown flavor: %s") % flavor_id) |
3868 | 142 | 144 | ||
3869 | === modified file 'nova/compute/manager.py' | |||
3870 | --- nova/compute/manager.py 2011-04-07 01:08:35 +0000 | |||
3871 | +++ nova/compute/manager.py 2011-04-12 11:01:25 +0000 | |||
3872 | @@ -140,12 +140,6 @@ | |||
3873 | 140 | """ | 140 | """ |
3874 | 141 | self.driver.init_host(host=self.host) | 141 | self.driver.init_host(host=self.host) |
3875 | 142 | 142 | ||
3876 | 143 | def periodic_tasks(self, context=None): | ||
3877 | 144 | """Tasks to be run at a periodic interval.""" | ||
3878 | 145 | super(ComputeManager, self).periodic_tasks(context) | ||
3879 | 146 | if FLAGS.rescue_timeout > 0: | ||
3880 | 147 | self.driver.poll_rescued_instances(FLAGS.rescue_timeout) | ||
3881 | 148 | |||
3882 | 149 | def _update_state(self, context, instance_id): | 143 | def _update_state(self, context, instance_id): |
3883 | 150 | """Update the state of an instance from the driver info.""" | 144 | """Update the state of an instance from the driver info.""" |
3884 | 151 | # FIXME(ja): include other fields from state? | 145 | # FIXME(ja): include other fields from state? |
3885 | @@ -708,6 +702,15 @@ | |||
3886 | 708 | 702 | ||
3887 | 709 | return self.driver.get_ajax_console(instance_ref) | 703 | return self.driver.get_ajax_console(instance_ref) |
3888 | 710 | 704 | ||
3889 | 705 | @exception.wrap_exception | ||
3890 | 706 | def get_vnc_console(self, context, instance_id): | ||
3891 | 707 | """Return connection information for an vnc console.""" | ||
3892 | 708 | context = context.elevated() | ||
3893 | 709 | LOG.debug(_("instance %s: getting vnc console"), instance_id) | ||
3894 | 710 | instance_ref = self.db.instance_get(context, instance_id) | ||
3895 | 711 | |||
3896 | 712 | return self.driver.get_vnc_console(instance_ref) | ||
3897 | 713 | |||
3898 | 711 | @checks_instance_lock | 714 | @checks_instance_lock |
3899 | 712 | def attach_volume(self, context, instance_id, volume_id, mountpoint): | 715 | def attach_volume(self, context, instance_id, volume_id, mountpoint): |
3900 | 713 | """Attach a volume to an instance.""" | 716 | """Attach a volume to an instance.""" |
3901 | @@ -1014,11 +1017,20 @@ | |||
3902 | 1014 | error_list = [] | 1017 | error_list = [] |
3903 | 1015 | 1018 | ||
3904 | 1016 | try: | 1019 | try: |
3905 | 1020 | if FLAGS.rescue_timeout > 0: | ||
3906 | 1021 | self.driver.poll_rescued_instances(FLAGS.rescue_timeout) | ||
3907 | 1022 | except Exception as ex: | ||
3908 | 1023 | LOG.warning(_("Error during poll_rescued_instances: %s"), | ||
3909 | 1024 | unicode(ex)) | ||
3910 | 1025 | error_list.append(ex) | ||
3911 | 1026 | |||
3912 | 1027 | try: | ||
3913 | 1017 | self._poll_instance_states(context) | 1028 | self._poll_instance_states(context) |
3914 | 1018 | except Exception as ex: | 1029 | except Exception as ex: |
3915 | 1019 | LOG.warning(_("Error during instance poll: %s"), | 1030 | LOG.warning(_("Error during instance poll: %s"), |
3916 | 1020 | unicode(ex)) | 1031 | unicode(ex)) |
3917 | 1021 | error_list.append(ex) | 1032 | error_list.append(ex) |
3918 | 1033 | |||
3919 | 1022 | return error_list | 1034 | return error_list |
3920 | 1023 | 1035 | ||
3921 | 1024 | def _poll_instance_states(self, context): | 1036 | def _poll_instance_states(self, context): |
3922 | @@ -1032,16 +1044,41 @@ | |||
3923 | 1032 | 1044 | ||
3924 | 1033 | for db_instance in db_instances: | 1045 | for db_instance in db_instances: |
3925 | 1034 | name = db_instance['name'] | 1046 | name = db_instance['name'] |
3926 | 1047 | db_state = db_instance['state'] | ||
3927 | 1035 | vm_instance = vm_instances.get(name) | 1048 | vm_instance = vm_instances.get(name) |
3928 | 1049 | |||
3929 | 1036 | if vm_instance is None: | 1050 | if vm_instance is None: |
3933 | 1037 | LOG.info(_("Found instance '%(name)s' in DB but no VM. " | 1051 | # NOTE(justinsb): We have to be very careful here, because a |
3934 | 1038 | "Setting state to shutoff.") % locals()) | 1052 | # concurrent operation could be in progress (e.g. a spawn) |
3935 | 1039 | vm_state = power_state.SHUTOFF | 1053 | if db_state == power_state.NOSTATE: |
3936 | 1054 | # Assume that NOSTATE => spawning | ||
3937 | 1055 | # TODO(justinsb): This does mean that if we crash during a | ||
3938 | 1056 | # spawn, the machine will never leave the spawning state, | ||
3939 | 1057 | # but this is just the way nova is; this function isn't | ||
3940 | 1058 | # trying to correct that problem. | ||
3941 | 1059 | # We could have a separate task to correct this error. | ||
3942 | 1060 | # TODO(justinsb): What happens during a live migration? | ||
3943 | 1061 | LOG.info(_("Found instance '%(name)s' in DB but no VM. " | ||
3944 | 1062 | "State=%(db_state)s, so assuming spawn is in " | ||
3945 | 1063 | "progress.") % locals()) | ||
3946 | 1064 | vm_state = db_state | ||
3947 | 1065 | else: | ||
3948 | 1066 | LOG.info(_("Found instance '%(name)s' in DB but no VM. " | ||
3949 | 1067 | "State=%(db_state)s, so setting state to " | ||
3950 | 1068 | "shutoff.") % locals()) | ||
3951 | 1069 | vm_state = power_state.SHUTOFF | ||
3952 | 1040 | else: | 1070 | else: |
3953 | 1041 | vm_state = vm_instance.state | 1071 | vm_state = vm_instance.state |
3954 | 1042 | vms_not_found_in_db.remove(name) | 1072 | vms_not_found_in_db.remove(name) |
3955 | 1043 | 1073 | ||
3957 | 1044 | db_state = db_instance['state'] | 1074 | if db_instance['state_description'] == 'migrating': |
3958 | 1075 | # A situation which db record exists, but no instance" | ||
3959 | 1076 | # sometimes occurs while live-migration at src compute, | ||
3960 | 1077 | # this case should be ignored. | ||
3961 | 1078 | LOG.debug(_("Ignoring %(name)s, as it's currently being " | ||
3962 | 1079 | "migrated.") % locals()) | ||
3963 | 1080 | continue | ||
3964 | 1081 | |||
3965 | 1045 | if vm_state != db_state: | 1082 | if vm_state != db_state: |
3966 | 1046 | LOG.info(_("DB/VM state mismatch. Changing state from " | 1083 | LOG.info(_("DB/VM state mismatch. Changing state from " |
3967 | 1047 | "'%(db_state)s' to '%(vm_state)s'") % locals()) | 1084 | "'%(db_state)s' to '%(vm_state)s'") % locals()) |
3968 | @@ -1049,12 +1086,8 @@ | |||
3969 | 1049 | db_instance['id'], | 1086 | db_instance['id'], |
3970 | 1050 | vm_state) | 1087 | vm_state) |
3971 | 1051 | 1088 | ||
3978 | 1052 | if vm_state == power_state.SHUTOFF: | 1089 | # NOTE(justinsb): We no longer auto-remove SHUTOFF instances |
3979 | 1053 | # TODO(soren): This is what the compute manager does when you | 1090 | # It's quite hard to get them back when we do. |
3974 | 1054 | # terminate an instance. At some point I figure we'll have a | ||
3975 | 1055 | # "terminated" state and some sort of cleanup job that runs | ||
3976 | 1056 | # occasionally, cleaning them out. | ||
3977 | 1057 | self.db.instance_destroy(context, db_instance['id']) | ||
3980 | 1058 | 1091 | ||
3981 | 1059 | # Are there VMs not in the DB? | 1092 | # Are there VMs not in the DB? |
3982 | 1060 | for vm_not_found_in_db in vms_not_found_in_db: | 1093 | for vm_not_found_in_db in vms_not_found_in_db: |
3983 | 1061 | 1094 | ||
3984 | === modified file 'nova/crypto.py' | |||
3985 | --- nova/crypto.py 2011-03-23 04:31:50 +0000 | |||
3986 | +++ nova/crypto.py 2011-04-12 11:01:25 +0000 | |||
3987 | @@ -215,9 +215,12 @@ | |||
3988 | 215 | 215 | ||
3989 | 216 | def _ensure_project_folder(project_id): | 216 | def _ensure_project_folder(project_id): |
3990 | 217 | if not os.path.exists(ca_path(project_id)): | 217 | if not os.path.exists(ca_path(project_id)): |
3991 | 218 | geninter_sh_path = os.path.join(os.path.dirname(__file__), | ||
3992 | 219 | 'CA', | ||
3993 | 220 | 'geninter.sh') | ||
3994 | 218 | start = os.getcwd() | 221 | start = os.getcwd() |
3995 | 219 | os.chdir(ca_folder()) | 222 | os.chdir(ca_folder()) |
3997 | 220 | utils.execute('sh', 'geninter.sh', project_id, | 223 | utils.execute('sh', geninter_sh_path, project_id, |
3998 | 221 | _project_cert_subject(project_id)) | 224 | _project_cert_subject(project_id)) |
3999 | 222 | os.chdir(start) | 225 | os.chdir(start) |
4000 | 223 | 226 | ||
4001 | @@ -227,13 +230,16 @@ | |||
4002 | 227 | csr_fn = os.path.join(project_folder, "server.csr") | 230 | csr_fn = os.path.join(project_folder, "server.csr") |
4003 | 228 | crt_fn = os.path.join(project_folder, "server.crt") | 231 | crt_fn = os.path.join(project_folder, "server.crt") |
4004 | 229 | 232 | ||
4005 | 233 | genvpn_sh_path = os.path.join(os.path.dirname(__file__), | ||
4006 | 234 | 'CA', | ||
4007 | 235 | 'genvpn.sh') | ||
4008 | 230 | if os.path.exists(crt_fn): | 236 | if os.path.exists(crt_fn): |
4009 | 231 | return | 237 | return |
4010 | 232 | _ensure_project_folder(project_id) | 238 | _ensure_project_folder(project_id) |
4011 | 233 | start = os.getcwd() | 239 | start = os.getcwd() |
4012 | 234 | os.chdir(ca_folder()) | 240 | os.chdir(ca_folder()) |
4013 | 235 | # TODO(vish): the shell scripts could all be done in python | 241 | # TODO(vish): the shell scripts could all be done in python |
4015 | 236 | utils.execute('sh', 'genvpn.sh', | 242 | utils.execute('sh', genvpn_sh_path, |
4016 | 237 | project_id, _vpn_cert_subject(project_id)) | 243 | project_id, _vpn_cert_subject(project_id)) |
4017 | 238 | with open(csr_fn, "r") as csrfile: | 244 | with open(csr_fn, "r") as csrfile: |
4018 | 239 | csr_text = csrfile.read() | 245 | csr_text = csrfile.read() |
4019 | @@ -263,6 +269,8 @@ | |||
4020 | 263 | LOG.debug(_("Flags path: %s"), ca_folder) | 269 | LOG.debug(_("Flags path: %s"), ca_folder) |
4021 | 264 | start = os.getcwd() | 270 | start = os.getcwd() |
4022 | 265 | # Change working dir to CA | 271 | # Change working dir to CA |
4023 | 272 | if not os.path.exists(ca_folder): | ||
4024 | 273 | os.makedirs(ca_folder) | ||
4025 | 266 | os.chdir(ca_folder) | 274 | os.chdir(ca_folder) |
4026 | 267 | utils.execute('openssl', 'ca', '-batch', '-out', outbound, '-config', | 275 | utils.execute('openssl', 'ca', '-batch', '-out', outbound, '-config', |
4027 | 268 | './openssl.cnf', '-infiles', inbound) | 276 | './openssl.cnf', '-infiles', inbound) |
4028 | 269 | 277 | ||
4029 | === modified file 'nova/db/api.py' | |||
4030 | --- nova/db/api.py 2011-03-28 08:27:17 +0000 | |||
4031 | +++ nova/db/api.py 2011-04-12 11:01:25 +0000 | |||
4032 | @@ -1153,6 +1153,11 @@ | |||
4033 | 1153 | return IMPL.instance_type_get_all(context, inactive) | 1153 | return IMPL.instance_type_get_all(context, inactive) |
4034 | 1154 | 1154 | ||
4035 | 1155 | 1155 | ||
4036 | 1156 | def instance_type_get_by_id(context, id): | ||
4037 | 1157 | """Get instance type by id""" | ||
4038 | 1158 | return IMPL.instance_type_get_by_id(context, id) | ||
4039 | 1159 | |||
4040 | 1160 | |||
4041 | 1156 | def instance_type_get_by_name(context, name): | 1161 | def instance_type_get_by_name(context, name): |
4042 | 1157 | """Get instance type by name""" | 1162 | """Get instance type by name""" |
4043 | 1158 | return IMPL.instance_type_get_by_name(context, name) | 1163 | return IMPL.instance_type_get_by_name(context, name) |
4044 | 1159 | 1164 | ||
4045 | === modified file 'nova/db/sqlalchemy/api.py' | |||
4046 | --- nova/db/sqlalchemy/api.py 2011-03-28 08:27:17 +0000 | |||
4047 | +++ nova/db/sqlalchemy/api.py 2011-04-12 11:01:25 +0000 | |||
4048 | @@ -660,7 +660,9 @@ | |||
4049 | 660 | filter(models.FixedIp.instance_id != None).\ | 660 | filter(models.FixedIp.instance_id != None).\ |
4050 | 661 | filter_by(allocated=0).\ | 661 | filter_by(allocated=0).\ |
4051 | 662 | update({'instance_id': None, | 662 | update({'instance_id': None, |
4053 | 663 | 'leased': 0}) | 663 | 'leased': 0, |
4054 | 664 | 'updated_at': datetime.datetime.utcnow()}, | ||
4055 | 665 | synchronize_session='fetch') | ||
4056 | 664 | return result | 666 | return result |
4057 | 665 | 667 | ||
4058 | 666 | 668 | ||
4059 | @@ -829,6 +831,7 @@ | |||
4060 | 829 | options(joinedload('volumes')).\ | 831 | options(joinedload('volumes')).\ |
4061 | 830 | options(joinedload_all('fixed_ip.network')).\ | 832 | options(joinedload_all('fixed_ip.network')).\ |
4062 | 831 | options(joinedload('metadata')).\ | 833 | options(joinedload('metadata')).\ |
4063 | 834 | options(joinedload('instance_type')).\ | ||
4064 | 832 | filter_by(id=instance_id).\ | 835 | filter_by(id=instance_id).\ |
4065 | 833 | filter_by(deleted=can_read_deleted(context)).\ | 836 | filter_by(deleted=can_read_deleted(context)).\ |
4066 | 834 | first() | 837 | first() |
4067 | @@ -838,6 +841,7 @@ | |||
4068 | 838 | options(joinedload_all('security_groups.rules')).\ | 841 | options(joinedload_all('security_groups.rules')).\ |
4069 | 839 | options(joinedload('volumes')).\ | 842 | options(joinedload('volumes')).\ |
4070 | 840 | options(joinedload('metadata')).\ | 843 | options(joinedload('metadata')).\ |
4071 | 844 | options(joinedload('instance_type')).\ | ||
4072 | 841 | filter_by(project_id=context.project_id).\ | 845 | filter_by(project_id=context.project_id).\ |
4073 | 842 | filter_by(id=instance_id).\ | 846 | filter_by(id=instance_id).\ |
4074 | 843 | filter_by(deleted=False).\ | 847 | filter_by(deleted=False).\ |
4075 | @@ -857,6 +861,7 @@ | |||
4076 | 857 | options(joinedload_all('fixed_ip.floating_ips')).\ | 861 | options(joinedload_all('fixed_ip.floating_ips')).\ |
4077 | 858 | options(joinedload('security_groups')).\ | 862 | options(joinedload('security_groups')).\ |
4078 | 859 | options(joinedload_all('fixed_ip.network')).\ | 863 | options(joinedload_all('fixed_ip.network')).\ |
4079 | 864 | options(joinedload('instance_type')).\ | ||
4080 | 860 | filter_by(deleted=can_read_deleted(context)).\ | 865 | filter_by(deleted=can_read_deleted(context)).\ |
4081 | 861 | all() | 866 | all() |
4082 | 862 | 867 | ||
4083 | @@ -868,6 +873,7 @@ | |||
4084 | 868 | options(joinedload_all('fixed_ip.floating_ips')).\ | 873 | options(joinedload_all('fixed_ip.floating_ips')).\ |
4085 | 869 | options(joinedload('security_groups')).\ | 874 | options(joinedload('security_groups')).\ |
4086 | 870 | options(joinedload_all('fixed_ip.network')).\ | 875 | options(joinedload_all('fixed_ip.network')).\ |
4087 | 876 | options(joinedload('instance_type')).\ | ||
4088 | 871 | filter_by(deleted=can_read_deleted(context)).\ | 877 | filter_by(deleted=can_read_deleted(context)).\ |
4089 | 872 | filter_by(user_id=user_id).\ | 878 | filter_by(user_id=user_id).\ |
4090 | 873 | all() | 879 | all() |
4091 | @@ -880,6 +886,7 @@ | |||
4092 | 880 | options(joinedload_all('fixed_ip.floating_ips')).\ | 886 | options(joinedload_all('fixed_ip.floating_ips')).\ |
4093 | 881 | options(joinedload('security_groups')).\ | 887 | options(joinedload('security_groups')).\ |
4094 | 882 | options(joinedload_all('fixed_ip.network')).\ | 888 | options(joinedload_all('fixed_ip.network')).\ |
4095 | 889 | options(joinedload('instance_type')).\ | ||
4096 | 883 | filter_by(host=host).\ | 890 | filter_by(host=host).\ |
4097 | 884 | filter_by(deleted=can_read_deleted(context)).\ | 891 | filter_by(deleted=can_read_deleted(context)).\ |
4098 | 885 | all() | 892 | all() |
4099 | @@ -894,6 +901,7 @@ | |||
4100 | 894 | options(joinedload_all('fixed_ip.floating_ips')).\ | 901 | options(joinedload_all('fixed_ip.floating_ips')).\ |
4101 | 895 | options(joinedload('security_groups')).\ | 902 | options(joinedload('security_groups')).\ |
4102 | 896 | options(joinedload_all('fixed_ip.network')).\ | 903 | options(joinedload_all('fixed_ip.network')).\ |
4103 | 904 | options(joinedload('instance_type')).\ | ||
4104 | 897 | filter_by(project_id=project_id).\ | 905 | filter_by(project_id=project_id).\ |
4105 | 898 | filter_by(deleted=can_read_deleted(context)).\ | 906 | filter_by(deleted=can_read_deleted(context)).\ |
4106 | 899 | all() | 907 | all() |
4107 | @@ -908,6 +916,7 @@ | |||
4108 | 908 | options(joinedload_all('fixed_ip.floating_ips')).\ | 916 | options(joinedload_all('fixed_ip.floating_ips')).\ |
4109 | 909 | options(joinedload('security_groups')).\ | 917 | options(joinedload('security_groups')).\ |
4110 | 910 | options(joinedload_all('fixed_ip.network')).\ | 918 | options(joinedload_all('fixed_ip.network')).\ |
4111 | 919 | options(joinedload('instance_type')).\ | ||
4112 | 911 | filter_by(reservation_id=reservation_id).\ | 920 | filter_by(reservation_id=reservation_id).\ |
4113 | 912 | filter_by(deleted=can_read_deleted(context)).\ | 921 | filter_by(deleted=can_read_deleted(context)).\ |
4114 | 913 | all() | 922 | all() |
4115 | @@ -916,6 +925,7 @@ | |||
4116 | 916 | options(joinedload_all('fixed_ip.floating_ips')).\ | 925 | options(joinedload_all('fixed_ip.floating_ips')).\ |
4117 | 917 | options(joinedload('security_groups')).\ | 926 | options(joinedload('security_groups')).\ |
4118 | 918 | options(joinedload_all('fixed_ip.network')).\ | 927 | options(joinedload_all('fixed_ip.network')).\ |
4119 | 928 | options(joinedload('instance_type')).\ | ||
4120 | 919 | filter_by(project_id=context.project_id).\ | 929 | filter_by(project_id=context.project_id).\ |
4121 | 920 | filter_by(reservation_id=reservation_id).\ | 930 | filter_by(reservation_id=reservation_id).\ |
4122 | 921 | filter_by(deleted=False).\ | 931 | filter_by(deleted=False).\ |
4123 | @@ -928,6 +938,7 @@ | |||
4124 | 928 | return session.query(models.Instance).\ | 938 | return session.query(models.Instance).\ |
4125 | 929 | options(joinedload_all('fixed_ip.floating_ips')).\ | 939 | options(joinedload_all('fixed_ip.floating_ips')).\ |
4126 | 930 | options(joinedload('security_groups')).\ | 940 | options(joinedload('security_groups')).\ |
4127 | 941 | options(joinedload('instance_type')).\ | ||
4128 | 931 | filter_by(project_id=project_id).\ | 942 | filter_by(project_id=project_id).\ |
4129 | 932 | filter_by(image_id=FLAGS.vpn_image_id).\ | 943 | filter_by(image_id=FLAGS.vpn_image_id).\ |
4130 | 933 | filter_by(deleted=can_read_deleted(context)).\ | 944 | filter_by(deleted=can_read_deleted(context)).\ |
4131 | @@ -2428,6 +2439,19 @@ | |||
4132 | 2428 | 2439 | ||
4133 | 2429 | 2440 | ||
4134 | 2430 | @require_context | 2441 | @require_context |
4135 | 2442 | def instance_type_get_by_id(context, id): | ||
4136 | 2443 | """Returns a dict describing specific instance_type""" | ||
4137 | 2444 | session = get_session() | ||
4138 | 2445 | inst_type = session.query(models.InstanceTypes).\ | ||
4139 | 2446 | filter_by(id=id).\ | ||
4140 | 2447 | first() | ||
4141 | 2448 | if not inst_type: | ||
4142 | 2449 | raise exception.NotFound(_("No instance type with id %s") % id) | ||
4143 | 2450 | else: | ||
4144 | 2451 | return dict(inst_type) | ||
4145 | 2452 | |||
4146 | 2453 | |||
4147 | 2454 | @require_context | ||
4148 | 2431 | def instance_type_get_by_name(context, name): | 2455 | def instance_type_get_by_name(context, name): |
4149 | 2432 | """Returns a dict describing specific instance_type""" | 2456 | """Returns a dict describing specific instance_type""" |
4150 | 2433 | session = get_session() | 2457 | session = get_session() |
4151 | 2434 | 2458 | ||
4152 | === added file 'nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py' | |||
4153 | --- nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py 1970-01-01 00:00:00 +0000 | |||
4154 | +++ nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py 2011-04-12 11:01:25 +0000 | |||
4155 | @@ -0,0 +1,84 @@ | |||
4156 | 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||
4157 | 2 | |||
4158 | 3 | # Copyright 2010 OpenStack LLC. | ||
4159 | 4 | # | ||
4160 | 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
4161 | 6 | # not use this file except in compliance with the License. You may obtain | ||
4162 | 7 | # a copy of the License at | ||
4163 | 8 | # | ||
4164 | 9 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
4165 | 10 | # | ||
4166 | 11 | # Unless required by applicable law or agreed to in writing, software | ||
4167 | 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
4168 | 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
4169 | 14 | # License for the specific language governing permissions and limitations | ||
4170 | 15 | # under the License. | ||
4171 | 16 | |||
4172 | 17 | from sqlalchemy import * | ||
4173 | 18 | from sqlalchemy.sql import text | ||
4174 | 19 | from migrate import * | ||
4175 | 20 | |||
4176 | 21 | #from nova import log as logging | ||
4177 | 22 | |||
4178 | 23 | |||
4179 | 24 | meta = MetaData() | ||
4180 | 25 | |||
4181 | 26 | |||
4182 | 27 | c_instance_type = Column('instance_type', | ||
4183 | 28 | String(length=255, convert_unicode=False, | ||
4184 | 29 | assert_unicode=None, unicode_error=None, | ||
4185 | 30 | _warn_on_bytestring=False), | ||
4186 | 31 | nullable=True) | ||
4187 | 32 | |||
4188 | 33 | c_instance_type_id = Column('instance_type_id', | ||
4189 | 34 | String(length=255, convert_unicode=False, | ||
4190 | 35 | assert_unicode=None, unicode_error=None, | ||
4191 | 36 | _warn_on_bytestring=False), | ||
4192 | 37 | nullable=True) | ||
4193 | 38 | |||
4194 | 39 | instance_types = Table('instance_types', meta, | ||
4195 | 40 | Column('id', Integer(), primary_key=True, nullable=False), | ||
4196 | 41 | Column('name', | ||
4197 | 42 | String(length=255, convert_unicode=False, assert_unicode=None, | ||
4198 | 43 | unicode_error=None, _warn_on_bytestring=False), | ||
4199 | 44 | unique=True)) | ||
4200 | 45 | |||
4201 | 46 | |||
4202 | 47 | def upgrade(migrate_engine): | ||
4203 | 48 | # Upgrade operations go here. Don't create your own engine; | ||
4204 | 49 | # bind migrate_engine to your metadata | ||
4205 | 50 | meta.bind = migrate_engine | ||
4206 | 51 | |||
4207 | 52 | instances = Table('instances', meta, autoload=True, | ||
4208 | 53 | autoload_with=migrate_engine) | ||
4209 | 54 | |||
4210 | 55 | instances.create_column(c_instance_type_id) | ||
4211 | 56 | |||
4212 | 57 | recs = migrate_engine.execute(instance_types.select()) | ||
4213 | 58 | for row in recs: | ||
4214 | 59 | type_id = row[0] | ||
4215 | 60 | type_name = row[1] | ||
4216 | 61 | migrate_engine.execute(instances.update()\ | ||
4217 | 62 | .where(instances.c.instance_type == type_name)\ | ||
4218 | 63 | .values(instance_type_id=type_id)) | ||
4219 | 64 | |||
4220 | 65 | instances.c.instance_type.drop() | ||
4221 | 66 | |||
4222 | 67 | |||
4223 | 68 | def downgrade(migrate_engine): | ||
4224 | 69 | meta.bind = migrate_engine | ||
4225 | 70 | |||
4226 | 71 | instances = Table('instances', meta, autoload=True, | ||
4227 | 72 | autoload_with=migrate_engine) | ||
4228 | 73 | |||
4229 | 74 | instances.create_column(c_instance_type) | ||
4230 | 75 | |||
4231 | 76 | recs = migrate_engine.execute(instance_types.select()) | ||
4232 | 77 | for row in recs: | ||
4233 | 78 | type_id = row[0] | ||
4234 | 79 | type_name = row[1] | ||
4235 | 80 | migrate_engine.execute(instances.update()\ | ||
4236 | 81 | .where(instances.c.instance_type_id == type_id)\ | ||
4237 | 82 | .values(instance_type=type_name)) | ||
4238 | 83 | |||
4239 | 84 | instances.c.instance_type_id.drop() | ||
4240 | 0 | 85 | ||
4241 | === removed file 'nova/db/sqlalchemy/migrate_repo/versions/014_diablo.py' | |||
4242 | --- nova/db/sqlalchemy/migrate_repo/versions/014_diablo.py 2011-03-28 08:27:17 +0000 | |||
4243 | +++ nova/db/sqlalchemy/migrate_repo/versions/014_diablo.py 1970-01-01 00:00:00 +0000 | |||
4244 | @@ -1,92 +0,0 @@ | |||
4245 | 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||
4246 | 2 | |||
4247 | 3 | # Copyright (c) 2011 NTT. | ||
4248 | 4 | # All Rights Reserved. | ||
4249 | 5 | # | ||
4250 | 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
4251 | 7 | # not use this file except in compliance with the License. You may obtain | ||
4252 | 8 | # a copy of the License at | ||
4253 | 9 | # | ||
4254 | 10 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
4255 | 11 | # | ||
4256 | 12 | # Unless required by applicable law or agreed to in writing, software | ||
4257 | 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
4258 | 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
4259 | 15 | # License for the specific language governing permissions and limitations | ||
4260 | 16 | # under the License. | ||
4261 | 17 | |||
4262 | 18 | from sqlalchemy import * | ||
4263 | 19 | from migrate import * | ||
4264 | 20 | |||
4265 | 21 | from nova import log as logging | ||
4266 | 22 | |||
4267 | 23 | meta = MetaData() | ||
4268 | 24 | |||
4269 | 25 | # Just for the ForeignKey and column creation to succeed, these are not the | ||
4270 | 26 | # actual definitions of instances or services. | ||
4271 | 27 | instances = Table('instances', meta, | ||
4272 | 28 | Column('id', Integer(), primary_key=True, nullable=False), | ||
4273 | 29 | ) | ||
4274 | 30 | |||
4275 | 31 | # | ||
4276 | 32 | # New Tables | ||
4277 | 33 | # | ||
4278 | 34 | instance_virtual_nic_association = Table( | ||
4279 | 35 | 'instance_virtual_nic_association', meta, | ||
4280 | 36 | Column('created_at', DateTime(timezone=False)), | ||
4281 | 37 | Column('updated_at', DateTime(timezone=False)), | ||
4282 | 38 | Column('deleted_at', DateTime(timezone=False)), | ||
4283 | 39 | Column('deleted', Boolean(create_constraint=True, name=None)), | ||
4284 | 40 | Column('id', Integer(), primary_key=True, nullable=False), | ||
4285 | 41 | Column('virtual_nic_id', | ||
4286 | 42 | String(length=255, convert_unicode=False, assert_unicode=None, | ||
4287 | 43 | unicode_error=None, _warn_on_bytestring=False), | ||
4288 | 44 | nullable=False), | ||
4289 | 45 | Column('instance_id', Integer(), ForeignKey('instances.id'), | ||
4290 | 46 | nullable=False), | ||
4291 | 47 | ) | ||
4292 | 48 | |||
4293 | 49 | project_network_service_association = Table( | ||
4294 | 50 | 'project_network_service_association', meta, | ||
4295 | 51 | Column('created_at', DateTime(timezone=False)), | ||
4296 | 52 | Column('updated_at', DateTime(timezone=False)), | ||
4297 | 53 | Column('deleted_at', DateTime(timezone=False)), | ||
4298 | 54 | Column('deleted', Boolean(create_constraint=True, name=None)), | ||
4299 | 55 | Column('id', Integer(), primary_key=True, nullable=False), | ||
4300 | 56 | Column('project_id', | ||
4301 | 57 | String(length=255, convert_unicode=False, assert_unicode=None, | ||
4302 | 58 | unicode_error=None, _warn_on_bytestring=False), | ||
4303 | 59 | nullable=False), | ||
4304 | 60 | Column('network_service', | ||
4305 | 61 | String(length=255, convert_unicode=False, assert_unicode=None, | ||
4306 | 62 | unicode_error=None, _warn_on_bytestring=False), | ||
4307 | 63 | nullable=False), | ||
4308 | 64 | UniqueConstraint('project_id') | ||
4309 | 65 | ) | ||
4310 | 66 | |||
4311 | 67 | def upgrade(migrate_engine): | ||
4312 | 68 | # Upgrade operations go here. Don't create your own engine; | ||
4313 | 69 | # bind migrate_engine to your metadata | ||
4314 | 70 | meta.bind = migrate_engine | ||
4315 | 71 | |||
4316 | 72 | for table in (instance_virtual_nic_association, | ||
4317 | 73 | project_network_service_association): | ||
4318 | 74 | try: | ||
4319 | 75 | table.create() | ||
4320 | 76 | except Exception: | ||
4321 | 77 | logging.info(repr(table)) | ||
4322 | 78 | logging.exception('Exception while creating table') | ||
4323 | 79 | raise | ||
4324 | 80 | |||
4325 | 81 | def downgrade(migrate_engine): | ||
4326 | 82 | # Operations to reverse the above upgrade go here. | ||
4327 | 83 | meta.bind = migrate_engine | ||
4328 | 84 | |||
4329 | 85 | for table in (project_network_service_association, | ||
4330 | 86 | instance_virtual_nic_association): | ||
4331 | 87 | try: | ||
4332 | 88 | table.drop() | ||
4333 | 89 | except Exception: | ||
4334 | 90 | logging.info(repr(table)) | ||
4335 | 91 | logging.exception('Exception while dropping table') | ||
4336 | 92 | raise | ||
4337 | 93 | 0 | ||
4338 | === added file 'nova/db/sqlalchemy/migrate_repo/versions/015_diablo.py' | |||
4339 | --- nova/db/sqlalchemy/migrate_repo/versions/015_diablo.py 1970-01-01 00:00:00 +0000 | |||
4340 | +++ nova/db/sqlalchemy/migrate_repo/versions/015_diablo.py 2011-04-12 11:01:25 +0000 | |||
4341 | @@ -0,0 +1,93 @@ | |||
4342 | 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||
4343 | 2 | |||
4344 | 3 | # Copyright (c) 2011 NTT. | ||
4345 | 4 | # All Rights Reserved. | ||
4346 | 5 | # | ||
4347 | 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
4348 | 7 | # not use this file except in compliance with the License. You may obtain | ||
4349 | 8 | # a copy of the License at | ||
4350 | 9 | # | ||
4351 | 10 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
4352 | 11 | # | ||
4353 | 12 | # Unless required by applicable law or agreed to in writing, software | ||
4354 | 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
4355 | 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
4356 | 15 | # License for the specific language governing permissions and limitations | ||
4357 | 16 | # under the License. | ||
4358 | 17 | |||
4359 | 18 | from sqlalchemy import * | ||
4360 | 19 | from migrate import * | ||
4361 | 20 | |||
4362 | 21 | from nova import log as logging | ||
4363 | 22 | |||
4364 | 23 | meta = MetaData() | ||
4365 | 24 | |||
4366 | 25 | # Just for the ForeignKey and column creation to succeed, these are not the | ||
4367 | 26 | # actual definitions of instances or services. | ||
4368 | 27 | instances = Table('instances', meta, | ||
4369 | 28 | Column('id', Integer(), primary_key=True, nullable=False), | ||
4370 | 29 | ) | ||
4371 | 30 | |||
4372 | 31 | # | ||
4373 | 32 | # New Tables | ||
4374 | 33 | # | ||
4375 | 34 | instance_virtual_nic_association = Table( | ||
4376 | 35 | 'instance_virtual_nic_association', meta, | ||
4377 | 36 | Column('created_at', DateTime(timezone=False)), | ||
4378 | 37 | Column('updated_at', DateTime(timezone=False)), | ||
4379 | 38 | Column('deleted_at', DateTime(timezone=False)), | ||
4380 | 39 | Column('deleted', Boolean(create_constraint=True, name=None)), | ||
4381 | 40 | Column('id', Integer(), primary_key=True, nullable=False), | ||
4382 | 41 | Column('virtual_nic_id', | ||
4383 | 42 | String(length=255, convert_unicode=False, assert_unicode=None, | ||
4384 | 43 | unicode_error=None, _warn_on_bytestring=False), | ||
4385 | 44 | nullable=False), | ||
4386 | 45 | Column('instance_id', Integer(), ForeignKey('instances.id'), | ||
4387 | 46 | nullable=False), | ||
4388 | 47 | ) | ||
4389 | 48 | |||
4390 | 49 | project_network_service_association = Table( | ||
4391 | 50 | 'project_network_service_association', meta, | ||
4392 | 51 | Column('created_at', DateTime(timezone=False)), | ||
4393 | 52 | Column('updated_at', DateTime(timezone=False)), | ||
4394 | 53 | Column('deleted_at', DateTime(timezone=False)), | ||
4395 | 54 | Column('deleted', Boolean(create_constraint=True, name=None)), | ||
4396 | 55 | Column('id', Integer(), primary_key=True, nullable=False), | ||
4397 | 56 | Column('project_id', | ||
4398 | 57 | String(length=255, convert_unicode=False, assert_unicode=None, | ||
4399 | 58 | unicode_error=None, _warn_on_bytestring=False), | ||
4400 | 59 | nullable=False), | ||
4401 | 60 | Column('network_service', | ||
4402 | 61 | String(length=255, convert_unicode=False, assert_unicode=None, | ||
4403 | 62 | unicode_error=None, _warn_on_bytestring=False), | ||
4404 | 63 | nullable=False), | ||
4405 | 64 | UniqueConstraint('project_id') | ||
4406 | 65 | ) | ||
4407 | 66 | |||
4408 | 67 | def upgrade(migrate_engine): | ||
4409 | 68 | # Upgrade operations go here. Don't create your own engine; | ||
4410 | 69 | # bind migrate_engine to your metadata | ||
4411 | 70 | meta.bind = migrate_engine | ||
4412 | 71 | return | ||
4413 | 72 | |||
4414 | 73 | for table in (instance_virtual_nic_association, | ||
4415 | 74 | project_network_service_association): | ||
4416 | 75 | try: | ||
4417 | 76 | table.create() | ||
4418 | 77 | except Exception: | ||
4419 | 78 | logging.info(repr(table)) | ||
4420 | 79 | logging.exception('Exception while creating table') | ||
4421 | 80 | raise | ||
4422 | 81 | |||
4423 | 82 | def downgrade(migrate_engine): | ||
4424 | 83 | # Operations to reverse the above upgrade go here. | ||
4425 | 84 | meta.bind = migrate_engine | ||
4426 | 85 | |||
4427 | 86 | for table in (project_network_service_association, | ||
4428 | 87 | instance_virtual_nic_association): | ||
4429 | 88 | try: | ||
4430 | 89 | table.drop() | ||
4431 | 90 | except Exception: | ||
4432 | 91 | logging.info(repr(table)) | ||
4433 | 92 | logging.exception('Exception while dropping table') | ||
4434 | 93 | raise | ||
4435 | 0 | 94 | ||
4436 | === modified file 'nova/db/sqlalchemy/models.py' | |||
4437 | --- nova/db/sqlalchemy/models.py 2011-03-28 08:27:17 +0000 | |||
4438 | +++ nova/db/sqlalchemy/models.py 2011-04-12 11:01:25 +0000 | |||
4439 | @@ -209,7 +209,7 @@ | |||
4440 | 209 | hostname = Column(String(255)) | 209 | hostname = Column(String(255)) |
4441 | 210 | host = Column(String(255)) # , ForeignKey('hosts.id')) | 210 | host = Column(String(255)) # , ForeignKey('hosts.id')) |
4442 | 211 | 211 | ||
4444 | 212 | instance_type = Column(String(255)) | 212 | instance_type_id = Column(String(255)) |
4445 | 213 | 213 | ||
4446 | 214 | user_data = Column(Text) | 214 | user_data = Column(Text) |
4447 | 215 | 215 | ||
4448 | @@ -268,6 +268,12 @@ | |||
4449 | 268 | rxtx_quota = Column(Integer, nullable=False, default=0) | 268 | rxtx_quota = Column(Integer, nullable=False, default=0) |
4450 | 269 | rxtx_cap = Column(Integer, nullable=False, default=0) | 269 | rxtx_cap = Column(Integer, nullable=False, default=0) |
4451 | 270 | 270 | ||
4452 | 271 | instances = relationship(Instance, | ||
4453 | 272 | backref=backref('instance_type', uselist=False), | ||
4454 | 273 | foreign_keys=id, | ||
4455 | 274 | primaryjoin='and_(Instance.instance_type_id == ' | ||
4456 | 275 | 'InstanceTypes.id)') | ||
4457 | 276 | |||
4458 | 271 | 277 | ||
4459 | 272 | class Volume(BASE, NovaBase): | 278 | class Volume(BASE, NovaBase): |
4460 | 273 | """Represents a block storage device that can be attached to a vm.""" | 279 | """Represents a block storage device that can be attached to a vm.""" |
4461 | 274 | 280 | ||
4462 | === added file 'nova/image/fake.py' | |||
4463 | --- nova/image/fake.py 1970-01-01 00:00:00 +0000 | |||
4464 | +++ nova/image/fake.py 2011-04-12 11:01:25 +0000 | |||
4465 | @@ -0,0 +1,113 @@ | |||
4466 | 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||
4467 | 2 | |||
4468 | 3 | # Copyright 2011 Justin Santa Barbara | ||
4469 | 4 | # All Rights Reserved. | ||
4470 | 5 | # | ||
4471 | 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
4472 | 7 | # not use this file except in compliance with the License. You may obtain | ||
4473 | 8 | # a copy of the License at | ||
4474 | 9 | # | ||
4475 | 10 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
4476 | 11 | # | ||
4477 | 12 | # Unless required by applicable law or agreed to in writing, software | ||
4478 | 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
4479 | 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
4480 | 15 | # License for the specific language governing permissions and limitations | ||
4481 | 16 | # under the License. | ||
4482 | 17 | """Implementation of an fake image service""" | ||
4483 | 18 | |||
4484 | 19 | import copy | ||
4485 | 20 | import datetime | ||
4486 | 21 | |||
4487 | 22 | from nova import exception | ||
4488 | 23 | from nova import flags | ||
4489 | 24 | from nova import log as logging | ||
4490 | 25 | from nova.image import service | ||
4491 | 26 | |||
4492 | 27 | |||
4493 | 28 | LOG = logging.getLogger('nova.image.fake') | ||
4494 | 29 | |||
4495 | 30 | |||
4496 | 31 | FLAGS = flags.FLAGS | ||
4497 | 32 | |||
4498 | 33 | |||
4499 | 34 | class FakeImageService(service.BaseImageService): | ||
4500 | 35 | """Mock (fake) image service for unit testing.""" | ||
4501 | 36 | |||
4502 | 37 | def __init__(self): | ||
4503 | 38 | self.images = {} | ||
4504 | 39 | # NOTE(justinsb): The OpenStack API can't upload an image? | ||
4505 | 40 | # So, make sure we've got one.. | ||
4506 | 41 | timestamp = datetime.datetime(2011, 01, 01, 01, 02, 03) | ||
4507 | 42 | image = {'id': '123456', | ||
4508 | 43 | 'name': 'fakeimage123456', | ||
4509 | 44 | 'created_at': timestamp, | ||
4510 | 45 | 'updated_at': timestamp, | ||
4511 | 46 | 'status': 'active', | ||
4512 | 47 | 'container_format': 'ami', | ||
4513 | 48 | 'disk_format': 'raw', | ||
4514 | 49 | 'properties': {'kernel_id': FLAGS.null_kernel, | ||
4515 | 50 | 'ramdisk_id': FLAGS.null_kernel} | ||
4516 | 51 | } | ||
4517 | 52 | self.create(None, image) | ||
4518 | 53 | super(FakeImageService, self).__init__() | ||
4519 | 54 | |||
4520 | 55 | def index(self, context): | ||
4521 | 56 | """Returns list of images.""" | ||
4522 | 57 | return copy.deepcopy(self.images.values()) | ||
4523 | 58 | |||
4524 | 59 | def detail(self, context): | ||
4525 | 60 | """Return list of detailed image information.""" | ||
4526 | 61 | return copy.deepcopy(self.images.values()) | ||
4527 | 62 | |||
4528 | 63 | def show(self, context, image_id): | ||
4529 | 64 | """Get data about specified image. | ||
4530 | 65 | |||
4531 | 66 | Returns a dict containing image data for the given opaque image id. | ||
4532 | 67 | |||
4533 | 68 | """ | ||
4534 | 69 | image_id = int(image_id) | ||
4535 | 70 | image = self.images.get(image_id) | ||
4536 | 71 | if image: | ||
4537 | 72 | return copy.deepcopy(image) | ||
4538 | 73 | LOG.warn("Unable to find image id %s. Have images: %s", | ||
4539 | 74 | image_id, self.images) | ||
4540 | 75 | raise exception.NotFound | ||
4541 | 76 | |||
4542 | 77 | def create(self, context, data): | ||
4543 | 78 | """Store the image data and return the new image id. | ||
4544 | 79 | |||
4545 | 80 | :raises Duplicate if the image already exist. | ||
4546 | 81 | |||
4547 | 82 | """ | ||
4548 | 83 | image_id = int(data['id']) | ||
4549 | 84 | if self.images.get(image_id): | ||
4550 | 85 | raise exception.Duplicate() | ||
4551 | 86 | |||
4552 | 87 | self.images[image_id] = copy.deepcopy(data) | ||
4553 | 88 | |||
4554 | 89 | def update(self, context, image_id, data): | ||
4555 | 90 | """Replace the contents of the given image with the new data. | ||
4556 | 91 | |||
4557 | 92 | :raises NotFound if the image does not exist. | ||
4558 | 93 | |||
4559 | 94 | """ | ||
4560 | 95 | image_id = int(image_id) | ||
4561 | 96 | if not self.images.get(image_id): | ||
4562 | 97 | raise exception.NotFound | ||
4563 | 98 | self.images[image_id] = copy.deepcopy(data) | ||
4564 | 99 | |||
4565 | 100 | def delete(self, context, image_id): | ||
4566 | 101 | """Delete the given image. | ||
4567 | 102 | |||
4568 | 103 | :raises NotFound if the image does not exist. | ||
4569 | 104 | |||
4570 | 105 | """ | ||
4571 | 106 | image_id = int(image_id) | ||
4572 | 107 | removed = self.images.pop(image_id, None) | ||
4573 | 108 | if not removed: | ||
4574 | 109 | raise exception.NotFound | ||
4575 | 110 | |||
4576 | 111 | def delete_all(self): | ||
4577 | 112 | """Clears out all images.""" | ||
4578 | 113 | self.images.clear() | ||
4579 | 0 | 114 | ||
4580 | === modified file 'nova/image/glance.py' | |||
4581 | --- nova/image/glance.py 2011-03-24 21:50:27 +0000 | |||
4582 | +++ nova/image/glance.py 2011-04-12 11:01:25 +0000 | |||
4583 | @@ -151,6 +151,8 @@ | |||
4584 | 151 | 151 | ||
4585 | 152 | :raises NotFound if the image does not exist. | 152 | :raises NotFound if the image does not exist. |
4586 | 153 | """ | 153 | """ |
4587 | 154 | # NOTE(vish): show is to check if image is available | ||
4588 | 155 | self.show(context, image_id) | ||
4589 | 154 | try: | 156 | try: |
4590 | 155 | image_meta = self.client.update_image(image_id, image_meta, data) | 157 | image_meta = self.client.update_image(image_id, image_meta, data) |
4591 | 156 | except glance_exception.NotFound: | 158 | except glance_exception.NotFound: |
4592 | @@ -165,6 +167,8 @@ | |||
4593 | 165 | 167 | ||
4594 | 166 | :raises NotFound if the image does not exist. | 168 | :raises NotFound if the image does not exist. |
4595 | 167 | """ | 169 | """ |
4596 | 170 | # NOTE(vish): show is to check if image is available | ||
4597 | 171 | self.show(context, image_id) | ||
4598 | 168 | try: | 172 | try: |
4599 | 169 | result = self.client.delete_image(image_id) | 173 | result = self.client.delete_image(image_id) |
4600 | 170 | except glance_exception.NotFound: | 174 | except glance_exception.NotFound: |
4601 | @@ -186,33 +190,6 @@ | |||
4602 | 186 | image_meta = _convert_timestamps_to_datetimes(image_meta) | 190 | image_meta = _convert_timestamps_to_datetimes(image_meta) |
4603 | 187 | return image_meta | 191 | return image_meta |
4604 | 188 | 192 | ||
4605 | 189 | @staticmethod | ||
4606 | 190 | def _is_image_available(context, image_meta): | ||
4607 | 191 | """ | ||
4608 | 192 | Images are always available if they are public or if the user is an | ||
4609 | 193 | admin. | ||
4610 | 194 | |||
4611 | 195 | Otherwise, we filter by project_id (if present) and then fall-back to | ||
4612 | 196 | images owned by user. | ||
4613 | 197 | """ | ||
4614 | 198 | # FIXME(sirp): We should be filtering by user_id on the Glance side | ||
4615 | 199 | # for security; however, we can't do that until we get authn/authz | ||
4616 | 200 | # sorted out. Until then, filtering in Nova. | ||
4617 | 201 | if image_meta['is_public'] or context.is_admin: | ||
4618 | 202 | return True | ||
4619 | 203 | |||
4620 | 204 | properties = image_meta['properties'] | ||
4621 | 205 | |||
4622 | 206 | if context.project_id and ('project_id' in properties): | ||
4623 | 207 | return str(properties['project_id']) == str(project_id) | ||
4624 | 208 | |||
4625 | 209 | try: | ||
4626 | 210 | user_id = properties['user_id'] | ||
4627 | 211 | except KeyError: | ||
4628 | 212 | return False | ||
4629 | 213 | |||
4630 | 214 | return str(user_id) == str(context.user_id) | ||
4631 | 215 | |||
4632 | 216 | 193 | ||
4633 | 217 | # utility functions | 194 | # utility functions |
4634 | 218 | def _convert_timestamps_to_datetimes(image_meta): | 195 | def _convert_timestamps_to_datetimes(image_meta): |
4635 | @@ -220,7 +197,7 @@ | |||
4636 | 220 | Returns image with known timestamp fields converted to datetime objects | 197 | Returns image with known timestamp fields converted to datetime objects |
4637 | 221 | """ | 198 | """ |
4638 | 222 | for attr in ['created_at', 'updated_at', 'deleted_at']: | 199 | for attr in ['created_at', 'updated_at', 'deleted_at']: |
4640 | 223 | if image_meta.get(attr) is not None: | 200 | if image_meta.get(attr): |
4641 | 224 | image_meta[attr] = _parse_glance_iso8601_timestamp( | 201 | image_meta[attr] = _parse_glance_iso8601_timestamp( |
4642 | 225 | image_meta[attr]) | 202 | image_meta[attr]) |
4643 | 226 | return image_meta | 203 | return image_meta |
4644 | @@ -230,8 +207,13 @@ | |||
4645 | 230 | """ | 207 | """ |
4646 | 231 | Parse a subset of iso8601 timestamps into datetime objects | 208 | Parse a subset of iso8601 timestamps into datetime objects |
4647 | 232 | """ | 209 | """ |
4653 | 233 | GLANCE_FMT = "%Y-%m-%dT%H:%M:%S" | 210 | iso_formats = ["%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S"] |
4654 | 234 | ISO_FMT = "%Y-%m-%dT%H:%M:%S.%f" | 211 | |
4655 | 235 | # FIXME(sirp): Glance is not returning in ISO format, we should fix Glance | 212 | for iso_format in iso_formats: |
4656 | 236 | # to do so, and then switch to parsing it here | 213 | try: |
4657 | 237 | return datetime.datetime.strptime(timestamp, GLANCE_FMT) | 214 | return datetime.datetime.strptime(timestamp, iso_format) |
4658 | 215 | except ValueError: | ||
4659 | 216 | pass | ||
4660 | 217 | |||
4661 | 218 | raise ValueError(_("%(timestamp)s does not follow any of the " | ||
4662 | 219 | "signatures: %(ISO_FORMATS)s") % locals()) | ||
4663 | 238 | 220 | ||
4664 | === modified file 'nova/image/local.py' | |||
4665 | --- nova/image/local.py 2011-03-23 05:50:53 +0000 | |||
4666 | +++ nova/image/local.py 2011-04-12 11:01:25 +0000 | |||
4667 | @@ -84,7 +84,10 @@ | |||
4668 | 84 | def show(self, context, image_id): | 84 | def show(self, context, image_id): |
4669 | 85 | try: | 85 | try: |
4670 | 86 | with open(self._path_to(image_id)) as metadata_file: | 86 | with open(self._path_to(image_id)) as metadata_file: |
4672 | 87 | return json.load(metadata_file) | 87 | image_meta = json.load(metadata_file) |
4673 | 88 | if not self._is_image_available(context, image_meta): | ||
4674 | 89 | raise exception.NotFound | ||
4675 | 90 | return image_meta | ||
4676 | 88 | except (IOError, ValueError): | 91 | except (IOError, ValueError): |
4677 | 89 | raise exception.NotFound | 92 | raise exception.NotFound |
4678 | 90 | 93 | ||
4679 | @@ -119,10 +122,15 @@ | |||
4680 | 119 | image_path = self._path_to(image_id, None) | 122 | image_path = self._path_to(image_id, None) |
4681 | 120 | if not os.path.exists(image_path): | 123 | if not os.path.exists(image_path): |
4682 | 121 | os.mkdir(image_path) | 124 | os.mkdir(image_path) |
4684 | 122 | return self.update(context, image_id, metadata, data) | 125 | return self._store(context, image_id, metadata, data) |
4685 | 123 | 126 | ||
4686 | 124 | def update(self, context, image_id, metadata, data=None): | 127 | def update(self, context, image_id, metadata, data=None): |
4687 | 125 | """Replace the contents of the given image with the new data.""" | 128 | """Replace the contents of the given image with the new data.""" |
4688 | 129 | # NOTE(vish): show is to check if image is available | ||
4689 | 130 | self.show(context, image_id) | ||
4690 | 131 | return self._store(context, image_id, metadata, data) | ||
4691 | 132 | |||
4692 | 133 | def _store(self, context, image_id, metadata, data=None): | ||
4693 | 126 | metadata['id'] = image_id | 134 | metadata['id'] = image_id |
4694 | 127 | try: | 135 | try: |
4695 | 128 | if data: | 136 | if data: |
4696 | @@ -140,9 +148,11 @@ | |||
4697 | 140 | 148 | ||
4698 | 141 | def delete(self, context, image_id): | 149 | def delete(self, context, image_id): |
4699 | 142 | """Delete the given image. | 150 | """Delete the given image. |
4701 | 143 | Raises OSError if the image does not exist. | 151 | Raises NotFound if the image does not exist. |
4702 | 144 | 152 | ||
4703 | 145 | """ | 153 | """ |
4704 | 154 | # NOTE(vish): show is to check if image is available | ||
4705 | 155 | self.show(context, image_id) | ||
4706 | 146 | try: | 156 | try: |
4707 | 147 | shutil.rmtree(self._path_to(image_id, None)) | 157 | shutil.rmtree(self._path_to(image_id, None)) |
4708 | 148 | except (IOError, ValueError): | 158 | except (IOError, ValueError): |
4709 | 149 | 159 | ||
4710 | === modified file 'nova/image/s3.py' | |||
4711 | --- nova/image/s3.py 2011-03-14 17:59:41 +0000 | |||
4712 | +++ nova/image/s3.py 2011-04-12 11:01:25 +0000 | |||
4713 | @@ -31,6 +31,7 @@ | |||
4714 | 31 | 31 | ||
4715 | 32 | import boto.s3.connection | 32 | import boto.s3.connection |
4716 | 33 | 33 | ||
4717 | 34 | from nova import crypto | ||
4718 | 34 | from nova import exception | 35 | from nova import exception |
4719 | 35 | from nova import flags | 36 | from nova import flags |
4720 | 36 | from nova import utils | 37 | from nova import utils |
4721 | @@ -45,6 +46,7 @@ | |||
4722 | 45 | 46 | ||
4723 | 46 | 47 | ||
4724 | 47 | class S3ImageService(service.BaseImageService): | 48 | class S3ImageService(service.BaseImageService): |
4725 | 49 | """Wraps an existing image service to support s3 based register""" | ||
4726 | 48 | def __init__(self, service=None, *args, **kwargs): | 50 | def __init__(self, service=None, *args, **kwargs): |
4727 | 49 | if service == None: | 51 | if service == None: |
4728 | 50 | service = utils.import_object(FLAGS.image_service) | 52 | service = utils.import_object(FLAGS.image_service) |
4729 | @@ -57,52 +59,23 @@ | |||
4730 | 57 | return image | 59 | return image |
4731 | 58 | 60 | ||
4732 | 59 | def delete(self, context, image_id): | 61 | def delete(self, context, image_id): |
4733 | 60 | # FIXME(vish): call to show is to check filter | ||
4734 | 61 | self.show(context, image_id) | ||
4735 | 62 | self.service.delete(context, image_id) | 62 | self.service.delete(context, image_id) |
4736 | 63 | 63 | ||
4737 | 64 | def update(self, context, image_id, metadata, data=None): | 64 | def update(self, context, image_id, metadata, data=None): |
4738 | 65 | # FIXME(vish): call to show is to check filter | ||
4739 | 66 | self.show(context, image_id) | ||
4740 | 67 | image = self.service.update(context, image_id, metadata, data) | 65 | image = self.service.update(context, image_id, metadata, data) |
4741 | 68 | return image | 66 | return image |
4742 | 69 | 67 | ||
4743 | 70 | def index(self, context): | 68 | def index(self, context): |
4747 | 71 | images = self.service.index(context) | 69 | return self.service.index(context) |
4745 | 72 | # FIXME(vish): index doesn't filter so we do it manually | ||
4746 | 73 | return self._filter(context, images) | ||
4748 | 74 | 70 | ||
4749 | 75 | def detail(self, context): | 71 | def detail(self, context): |
4768 | 76 | images = self.service.detail(context) | 72 | return self.service.detail(context) |
4751 | 77 | # FIXME(vish): detail doesn't filter so we do it manually | ||
4752 | 78 | return self._filter(context, images) | ||
4753 | 79 | |||
4754 | 80 | @classmethod | ||
4755 | 81 | def _is_visible(cls, context, image): | ||
4756 | 82 | return (context.is_admin | ||
4757 | 83 | or context.project_id == image['properties']['owner_id'] | ||
4758 | 84 | or image['properties']['is_public'] == 'True') | ||
4759 | 85 | |||
4760 | 86 | @classmethod | ||
4761 | 87 | def _filter(cls, context, images): | ||
4762 | 88 | filtered = [] | ||
4763 | 89 | for image in images: | ||
4764 | 90 | if not cls._is_visible(context, image): | ||
4765 | 91 | continue | ||
4766 | 92 | filtered.append(image) | ||
4767 | 93 | return filtered | ||
4769 | 94 | 73 | ||
4770 | 95 | def show(self, context, image_id): | 74 | def show(self, context, image_id): |
4775 | 96 | image = self.service.show(context, image_id) | 75 | return self.service.show(context, image_id) |
4772 | 97 | if not self._is_visible(context, image): | ||
4773 | 98 | raise exception.NotFound | ||
4774 | 99 | return image | ||
4776 | 100 | 76 | ||
4777 | 101 | def show_by_name(self, context, name): | 77 | def show_by_name(self, context, name): |
4782 | 102 | image = self.service.show_by_name(context, name) | 78 | return self.service.show(context, name) |
4779 | 103 | if not self._is_visible(context, image): | ||
4780 | 104 | raise exception.NotFound | ||
4781 | 105 | return image | ||
4783 | 106 | 79 | ||
4784 | 107 | @staticmethod | 80 | @staticmethod |
4785 | 108 | def _conn(context): | 81 | def _conn(context): |
4786 | @@ -166,7 +139,7 @@ | |||
4787 | 166 | arch = 'x86_64' | 139 | arch = 'x86_64' |
4788 | 167 | 140 | ||
4789 | 168 | properties = metadata['properties'] | 141 | properties = metadata['properties'] |
4791 | 169 | properties['owner_id'] = context.project_id | 142 | properties['project_id'] = context.project_id |
4792 | 170 | properties['architecture'] = arch | 143 | properties['architecture'] = arch |
4793 | 171 | 144 | ||
4794 | 172 | if kernel_id: | 145 | if kernel_id: |
4795 | @@ -175,8 +148,6 @@ | |||
4796 | 175 | if ramdisk_id: | 148 | if ramdisk_id: |
4797 | 176 | properties['ramdisk_id'] = ec2utils.ec2_id_to_id(ramdisk_id) | 149 | properties['ramdisk_id'] = ec2utils.ec2_id_to_id(ramdisk_id) |
4798 | 177 | 150 | ||
4799 | 178 | properties['is_public'] = False | ||
4800 | 179 | properties['type'] = image_type | ||
4801 | 180 | metadata.update({'disk_format': image_format, | 151 | metadata.update({'disk_format': image_format, |
4802 | 181 | 'container_format': image_format, | 152 | 'container_format': image_format, |
4803 | 182 | 'status': 'queued', | 153 | 'status': 'queued', |
4804 | @@ -210,7 +181,7 @@ | |||
4805 | 210 | 181 | ||
4806 | 211 | # FIXME(vish): grab key from common service so this can run on | 182 | # FIXME(vish): grab key from common service so this can run on |
4807 | 212 | # any host. | 183 | # any host. |
4809 | 213 | cloud_pk = os.path.join(FLAGS.ca_path, "private/cakey.pem") | 184 | cloud_pk = crypto.key_path(context.project_id) |
4810 | 214 | 185 | ||
4811 | 215 | decrypted_filename = os.path.join(image_path, 'image.tar.gz') | 186 | decrypted_filename = os.path.join(image_path, 'image.tar.gz') |
4812 | 216 | self._decrypt_image(encrypted_filename, encrypted_key, | 187 | self._decrypt_image(encrypted_filename, encrypted_key, |
4813 | 217 | 188 | ||
4814 | === modified file 'nova/image/service.py' | |||
4815 | --- nova/image/service.py 2011-03-24 21:13:55 +0000 | |||
4816 | +++ nova/image/service.py 2011-04-12 11:01:25 +0000 | |||
4817 | @@ -136,6 +136,33 @@ | |||
4818 | 136 | """ | 136 | """ |
4819 | 137 | raise NotImplementedError | 137 | raise NotImplementedError |
4820 | 138 | 138 | ||
4821 | 139 | @staticmethod | ||
4822 | 140 | def _is_image_available(context, image_meta): | ||
4823 | 141 | """ | ||
4824 | 142 | Images are always available if they are public or if the user is an | ||
4825 | 143 | admin. | ||
4826 | 144 | |||
4827 | 145 | Otherwise, we filter by project_id (if present) and then fall-back to | ||
4828 | 146 | images owned by user. | ||
4829 | 147 | """ | ||
4830 | 148 | # FIXME(sirp): We should be filtering by user_id on the Glance side | ||
4831 | 149 | # for security; however, we can't do that until we get authn/authz | ||
4832 | 150 | # sorted out. Until then, filtering in Nova. | ||
4833 | 151 | if image_meta['is_public'] or context.is_admin: | ||
4834 | 152 | return True | ||
4835 | 153 | |||
4836 | 154 | properties = image_meta['properties'] | ||
4837 | 155 | |||
4838 | 156 | if context.project_id and ('project_id' in properties): | ||
4839 | 157 | return str(properties['project_id']) == str(context.project_id) | ||
4840 | 158 | |||
4841 | 159 | try: | ||
4842 | 160 | user_id = properties['user_id'] | ||
4843 | 161 | except KeyError: | ||
4844 | 162 | return False | ||
4845 | 163 | |||
4846 | 164 | return str(user_id) == str(context.user_id) | ||
4847 | 165 | |||
4848 | 139 | @classmethod | 166 | @classmethod |
4849 | 140 | def _translate_to_base(cls, metadata): | 167 | def _translate_to_base(cls, metadata): |
4850 | 141 | """Return a metadata dictionary that is BaseImageService compliant. | 168 | """Return a metadata dictionary that is BaseImageService compliant. |
4851 | 142 | 169 | ||
4852 | === modified file 'nova/network/api.py' | |||
4853 | --- nova/network/api.py 2011-02-22 23:50:42 +0000 | |||
4854 | +++ nova/network/api.py 2011-04-12 11:01:25 +0000 | |||
4855 | @@ -66,6 +66,21 @@ | |||
4856 | 66 | if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode): | 66 | if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode): |
4857 | 67 | fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip) | 67 | fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip) |
4858 | 68 | floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) | 68 | floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) |
4859 | 69 | # Check if the floating ip address is allocated | ||
4860 | 70 | if floating_ip['project_id'] is None: | ||
4861 | 71 | raise exception.ApiError(_("Address (%s) is not allocated") % | ||
4862 | 72 | floating_ip['address']) | ||
4863 | 73 | # Check if the floating ip address is allocated to the same project | ||
4864 | 74 | if floating_ip['project_id'] != context.project_id: | ||
4865 | 75 | LOG.warn(_("Address (%(address)s) is not allocated to your " | ||
4866 | 76 | "project (%(project)s)"), | ||
4867 | 77 | {'address': floating_ip['address'], | ||
4868 | 78 | 'project': context.project_id}) | ||
4869 | 79 | raise exception.ApiError(_("Address (%(address)s) is not " | ||
4870 | 80 | "allocated to your project" | ||
4871 | 81 | "(%(project)s)") % | ||
4872 | 82 | {'address': floating_ip['address'], | ||
4873 | 83 | 'project': context.project_id}) | ||
4874 | 69 | # NOTE(vish): Perhaps we should just pass this on to compute and | 84 | # NOTE(vish): Perhaps we should just pass this on to compute and |
4875 | 70 | # let compute communicate with network. | 85 | # let compute communicate with network. |
4876 | 71 | host = fixed_ip['network']['host'] | 86 | host = fixed_ip['network']['host'] |
4877 | 72 | 87 | ||
4878 | === modified file 'nova/network/linux_net.py' | |||
4879 | --- nova/network/linux_net.py 2011-04-03 19:04:27 +0000 | |||
4880 | +++ nova/network/linux_net.py 2011-04-12 11:01:25 +0000 | |||
4881 | @@ -391,6 +391,12 @@ | |||
4882 | 391 | 'dev', FLAGS.public_interface) | 391 | 'dev', FLAGS.public_interface) |
4883 | 392 | 392 | ||
4884 | 393 | 393 | ||
4885 | 394 | def ensure_metadata_ip(): | ||
4886 | 395 | """Sets up local metadata ip""" | ||
4887 | 396 | _execute('sudo', 'ip', 'addr', 'add', '169.254.169.254/32', | ||
4888 | 397 | 'scope', 'link', 'dev', 'lo', check_exit_code=False) | ||
4889 | 398 | |||
4890 | 399 | |||
4891 | 394 | def ensure_vlan_forward(public_ip, port, private_ip): | 400 | def ensure_vlan_forward(public_ip, port, private_ip): |
4892 | 395 | """Sets up forwarding rules for vlan""" | 401 | """Sets up forwarding rules for vlan""" |
4893 | 396 | iptables_manager.ipv4['filter'].add_rule("FORWARD", | 402 | iptables_manager.ipv4['filter'].add_rule("FORWARD", |
4894 | @@ -442,6 +448,7 @@ | |||
4895 | 442 | return interface | 448 | return interface |
4896 | 443 | 449 | ||
4897 | 444 | 450 | ||
4898 | 451 | @utils.synchronized('ensure_bridge', external=True) | ||
4899 | 445 | def ensure_bridge(bridge, interface, net_attrs=None): | 452 | def ensure_bridge(bridge, interface, net_attrs=None): |
4900 | 446 | """Create a bridge unless it already exists. | 453 | """Create a bridge unless it already exists. |
4901 | 447 | 454 | ||
4902 | @@ -495,6 +502,8 @@ | |||
4903 | 495 | fields = line.split() | 502 | fields = line.split() |
4904 | 496 | if fields and fields[0] == "0.0.0.0" and fields[-1] == interface: | 503 | if fields and fields[0] == "0.0.0.0" and fields[-1] == interface: |
4905 | 497 | gateway = fields[1] | 504 | gateway = fields[1] |
4906 | 505 | _execute('sudo', 'route', 'del', 'default', 'gw', gateway, | ||
4907 | 506 | 'dev', interface, check_exit_code=False) | ||
4908 | 498 | out, err = _execute('sudo', 'ip', 'addr', 'show', 'dev', interface, | 507 | out, err = _execute('sudo', 'ip', 'addr', 'show', 'dev', interface, |
4909 | 499 | 'scope', 'global') | 508 | 'scope', 'global') |
4910 | 500 | for line in out.split("\n"): | 509 | for line in out.split("\n"): |
4911 | @@ -504,7 +513,7 @@ | |||
4912 | 504 | _execute(*_ip_bridge_cmd('del', params, fields[-1])) | 513 | _execute(*_ip_bridge_cmd('del', params, fields[-1])) |
4913 | 505 | _execute(*_ip_bridge_cmd('add', params, bridge)) | 514 | _execute(*_ip_bridge_cmd('add', params, bridge)) |
4914 | 506 | if gateway: | 515 | if gateway: |
4916 | 507 | _execute('sudo', 'route', 'add', '0.0.0.0', 'gw', gateway) | 516 | _execute('sudo', 'route', 'add', 'default', 'gw', gateway) |
4917 | 508 | out, err = _execute('sudo', 'brctl', 'addif', bridge, interface, | 517 | out, err = _execute('sudo', 'brctl', 'addif', bridge, interface, |
4918 | 509 | check_exit_code=False) | 518 | check_exit_code=False) |
4919 | 510 | 519 | ||
4920 | 511 | 520 | ||
4921 | === modified file 'nova/network/manager.py' | |||
4922 | --- nova/network/manager.py 2011-04-03 19:04:27 +0000 | |||
4923 | +++ nova/network/manager.py 2011-04-12 11:01:25 +0000 | |||
4924 | @@ -126,6 +126,7 @@ | |||
4925 | 126 | standalone service. | 126 | standalone service. |
4926 | 127 | """ | 127 | """ |
4927 | 128 | self.driver.init_host() | 128 | self.driver.init_host() |
4928 | 129 | self.driver.ensure_metadata_ip() | ||
4929 | 129 | # Set up networking for the projects for which we're already | 130 | # Set up networking for the projects for which we're already |
4930 | 130 | # the designated network host. | 131 | # the designated network host. |
4931 | 131 | ctxt = context.get_admin_context() | 132 | ctxt = context.get_admin_context() |
4932 | 132 | 133 | ||
4933 | === added file 'nova/network/xenapi_net.py' | |||
4934 | --- nova/network/xenapi_net.py 1970-01-01 00:00:00 +0000 | |||
4935 | +++ nova/network/xenapi_net.py 2011-04-12 11:01:25 +0000 | |||
4936 | @@ -0,0 +1,85 @@ | |||
4937 | 1 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||
4938 | 2 | |||
4939 | 3 | # Copyright (c) 2011 Citrix Systems, Inc. | ||
4940 | 4 | # Copyright 2011 OpenStack LLC. | ||
4941 | 5 | # | ||
4942 | 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
4943 | 7 | # not use this file except in compliance with the License. You may obtain | ||
4944 | 8 | # a copy of the License at | ||
4945 | 9 | # | ||
4946 | 10 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
4947 | 11 | # | ||
4948 | 12 | # Unless required by applicable law or agreed to in writing, software | ||
4949 | 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
4950 | 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
4951 | 15 | # License for the specific language governing permissions and limitations | ||
4952 | 16 | # under the License. | ||
4953 | 17 | |||
4954 | 18 | """ | ||
4955 | 19 | Implements vlans, bridges, and iptables rules using linux utilities. | ||
4956 | 20 | """ | ||
4957 | 21 | |||
4958 | 22 | import os | ||
4959 | 23 | |||
4960 | 24 | from nova import db | ||
4961 | 25 | from nova import exception | ||
4962 | 26 | from nova import flags | ||
4963 | 27 | from nova import log as logging | ||
4964 | 28 | from nova import utils | ||
4965 | 29 | from nova.virt.xenapi_conn import XenAPISession | ||
4966 | 30 | from nova.virt.xenapi import network_utils | ||
4967 | 31 | |||
4968 | 32 | LOG = logging.getLogger("nova.xenapi_net") | ||
4969 | 33 | |||
4970 | 34 | FLAGS = flags.FLAGS | ||
4971 | 35 | |||
4972 | 36 | |||
4973 | 37 | def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): | ||
4974 | 38 | """Create a vlan and bridge unless they already exist.""" | ||
4975 | 39 | # Open xenapi session | ||
4976 | 40 | LOG.debug("ENTERING ensure_vlan_bridge in xenapi net") | ||
4977 | 41 | url = FLAGS.xenapi_connection_url | ||
4978 | 42 | username = FLAGS.xenapi_connection_username | ||
4979 | 43 | password = FLAGS.xenapi_connection_password | ||
4980 | 44 | session = XenAPISession(url, username, password) | ||
4981 | 45 | # Check whether bridge already exists | ||
4982 | 46 | # Retrieve network whose name_label is "bridge" | ||
4983 | 47 | network_ref = network_utils.NetworkHelper.find_network_with_name_label( | ||
4984 | 48 | session, | ||
4985 | 49 | bridge) | ||
4986 | 50 | if network_ref == None: | ||
4987 | 51 | # If bridge does not exists | ||
4988 | 52 | # 1 - create network | ||
4989 | 53 | description = "network for nova bridge %s" % bridge | ||
4990 | 54 | network_rec = {'name_label': bridge, | ||
4991 | 55 | 'name_description': description, | ||
4992 | 56 | 'other_config': {}} | ||
4993 | 57 | network_ref = session.call_xenapi('network.create', network_rec) | ||
4994 | 58 | # 2 - find PIF for VLAN | ||
4995 | 59 | expr = 'field "device" = "%s" and \ | ||
4996 | 60 | field "VLAN" = "-1"' % FLAGS.vlan_interface | ||
4997 | 61 | pifs = session.call_xenapi('PIF.get_all_records_where', expr) | ||
4998 | 62 | pif_ref = None | ||
4999 | 63 | # Multiple PIF are ok: we are dealing with a pool | ||
5000 | 64 | if len(pifs) == 0: |
The diff has been truncated for viewing.