Merge lp:~gandelman-a/nova/libvirt_patch_refresh into lp:~ubuntu-server-dev/nova/essex

Proposed by Adam Gandelman
Status: Merged
Merge reported by: Chuck Short
Merged at revision: not available
Proposed branch: lp:~gandelman-a/nova/libvirt_patch_refresh
Merge into: lp:~ubuntu-server-dev/nova/essex
Diff against target: 320169 lines
To merge this branch: bzr merge lp:~gandelman-a/nova/libvirt_patch_refresh
Reviewer Review Type Date Requested Status
Ubuntu Server Developers Pending
Review via email: mp+95024@code.launchpad.net

Description of the change

This refreshes the libvirt patch correctly and passes all tests. In the future, we really need to gate things like this making it into this repositroy on whether or not its passing CI testing. We shouldn't be committing changes to both places at once, it defeats the purpose.

To post a comment you must log in.
327. By Chuck Short

Update libvirt-console-patch again.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'debian/patches/libvirt-use-console-pipe.patch'
--- debian/patches/libvirt-use-console-pipe.patch 2012-02-28 14:11:36 +0000
+++ debian/patches/libvirt-use-console-pipe.patch 2012-02-28 19:17:20 +0000
@@ -1,7 +1,8 @@
1diff -Naurp nova.orig/Authors nova/Authors1Index: nova-src/Authors
2--- nova.orig/Authors 2012-02-28 09:08:10.785887173 -05002===================================================================
3+++ nova/Authors 2012-02-28 09:08:58.677887983 -05003--- nova-src.orig/Authors 2012-02-28 10:30:58.000000000 -0800
4@@ -147,6 +147,7 @@ Ricardo Carrillo Cruz <emaildericky@gmai4+++ nova-src/Authors 2012-02-28 10:32:36.086640111 -0800
5@@ -147,6 +147,7 @@
5 Rick Clark <rick@openstack.org>6 Rick Clark <rick@openstack.org>
6 Rick Harris <rconradharris@gmail.com>7 Rick Harris <rconradharris@gmail.com>
7 Rob Kost <kost@isi.edu>8 Rob Kost <kost@isi.edu>
@@ -9,10 +10,11 @@
9 Russell Bryant <rbryant@redhat.com>10 Russell Bryant <rbryant@redhat.com>
10 Russell Sim <russell.sim@gmail.com>11 Russell Sim <russell.sim@gmail.com>
11 Ryan Lane <rlane@wikimedia.org>12 Ryan Lane <rlane@wikimedia.org>
12diff -Naurp nova.orig/nova/tests/test_libvirt.py nova/nova/tests/test_libvirt.py13Index: nova-src/nova/tests/test_libvirt.py
13--- nova.orig/nova/tests/test_libvirt.py 2012-02-28 09:08:10.873887173 -050014===================================================================
14+++ nova/nova/tests/test_libvirt.py 2012-02-28 09:08:58.681887793 -050015--- nova-src.orig/nova/tests/test_libvirt.py 2012-02-28 10:30:58.000000000 -0800
15@@ -872,7 +872,7 @@ class LibvirtConnTestCase(test.TestCase)16+++ nova-src/nova/tests/test_libvirt.py 2012-02-28 10:32:36.086640111 -0800
17@@ -872,7 +872,7 @@
16 (lambda t: _ipv4_like(t.findall(parameter)[1].get('value'),18 (lambda t: _ipv4_like(t.findall(parameter)[1].get('value'),
17 '192.168.*.1'), True),19 '192.168.*.1'), True),
18 (lambda t: t.find('./devices/serial/source').get(20 (lambda t: t.find('./devices/serial/source').get(
@@ -21,7 +23,7 @@
21 (lambda t: t.find('./memory').text, '2097152')]23 (lambda t: t.find('./memory').text, '2097152')]
22 if rescue:24 if rescue:
23 common_checks += [25 common_checks += [
24@@ -2308,3 +2308,53 @@ class LibvirtConnectionTestCase(test.Tes26@@ -2308,3 +2308,53 @@
25 27
26 ref = self.libvirtconnection.finish_revert_migration(ins_ref, None)28 ref = self.libvirtconnection.finish_revert_migration(ins_ref, None)
27 self.assertTrue(isinstance(ref, eventlet.event.Event))29 self.assertTrue(isinstance(ref, eventlet.event.Event))
@@ -75,10 +77,89 @@
75+ os.unlink(self.ringbuffer_path)77+ os.unlink(self.ringbuffer_path)
76+ os.unlink(self.fifo_path)78+ os.unlink(self.fifo_path)
77+ os.rmdir(self.directory_path)79+ os.rmdir(self.directory_path)
78diff -Naurp nova.orig/nova/utils.py nova/nova/utils.py80Index: nova-src/nova/tests/test_utils.py
79--- nova.orig/nova/utils.py 2012-02-28 09:08:10.877887173 -050081===================================================================
80+++ nova/nova/utils.py 2012-02-28 09:08:58.685887622 -050082--- nova-src.orig/nova/tests/test_utils.py 2012-02-28 10:00:07.000000000 -0800
81@@ -33,6 +33,7 @@ import re83+++ nova-src/nova/tests/test_utils.py 2012-02-28 10:32:36.086640111 -0800
84@@ -17,6 +17,7 @@
85 import __builtin__
86 import datetime
87 import hashlib
88+import itertools
89 import os
90 import os.path
91 import socket
92@@ -25,6 +26,7 @@
93
94 import iso8601
95 import mox
96+import nose
97
98 import nova
99 from nova import exception
100@@ -813,6 +815,57 @@
101 west = utils.parse_isotime(str)
102 normed = utils.normalize_time(west)
103 self._instaneous(normed, 2012, 2, 13, 23, 53, 07, 0)
104+
105+class RingBufferTestCase(test.TestCase):
106+ """Unit test for utils.RingBuffer()."""
107+ def setUp(self):
108+ super(ingBufferTestCase, self).setUp()
109+ self.f = tempfile.NamedTemporaryFile()
110+ self.r = utils.RingBuffer(self.f.name, max_size=4)
111+
112+ def tearDown(self):
113+ super(RingBufferTestCase, self).tearDown()
114+ self.r.close()
115+ self.f.close()
116+
117+ def testEmpty(self):
118+ self.assertEquals(self.r.peek(), '')
119+
120+ def testReOpen(self):
121+ self.r.write('1')
122+ self.r.close()
123+ self.r = utils.RingBuffer(self.f.name, max_size=4)
124+ self.assertEquals(self.r.peek(), '1')
125+
126+
127+def testPermutations():
128+ """Test various permutations of writing to a RingBuffer.
129+
130+ Try all permutations of writing [0,5) bytes three times to a RingBuffer
131+ of size 4. This makes use of nose's test generator capability so cannot
132+ be a subclass of test.TestCase.
133+
134+ """
135+ def check_buffer(r, expected):
136+ nose.tools.eq_(r.peek(), expected)
137+
138+ SIZE = 4
139+ for sequence in itertools.product(range(5), range(5), range(5)):
140+ f = tempfile.NamedTemporaryFile()
141+ r = utils.RingBuffer(f.name, max_size=SIZE)
142+ source = itertools.count()
143+ expected = ''
144+
145+ def next_n(n):
146+ return ''.join(str(next(source)) for x in range(n))
147+ for entry in sequence:
148+ to_insert = next_n(entry)
149+ expected += to_insert
150+ expected = expected[max(0, len(expected) - SIZE):]
151+ r.write(to_insert)
152+ yield check_buffer, r, expected
153+ r.close()
154+ f.close()
155
156
157 class TestLockCleanup(test.TestCase):
158Index: nova-src/nova/utils.py
159===================================================================
160--- nova-src.orig/nova/utils.py 2012-02-28 10:30:58.000000000 -0800
161+++ nova-src/nova/utils.py 2012-02-28 10:32:36.090640111 -0800
162@@ -33,6 +33,7 @@
82 import shlex163 import shlex
83 import shutil164 import shutil
84 import socket165 import socket
@@ -86,7 +167,7 @@
86 import struct167 import struct
87 import sys168 import sys
88 import tempfile169 import tempfile
89@@ -56,6 +57,7 @@ from nova import log as logging170@@ -56,6 +57,7 @@
90 from nova.openstack.common import cfg171 from nova.openstack.common import cfg
91 172
92 173
@@ -94,10 +175,147 @@
94 LOG = logging.getLogger(__name__)175 LOG = logging.getLogger(__name__)
95 ISO_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"176 ISO_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S"
96 PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f"177 PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f"
97diff -Naurp nova.orig/nova/virt/libvirt/connection.py nova/nova/virt/libvirt/connection.py178@@ -1525,6 +1527,135 @@
98--- nova.orig/nova/virt/libvirt/connection.py 2012-02-28 09:08:10.877887173 -0500179 return out
99+++ nova/nova/virt/libvirt/connection.py 2012-02-28 09:09:56.549887233 -0500180 except exception.ProcessExecutionError:
100@@ -39,15 +39,19 @@ Supports KVM, LXC, QEMU, UML, and XEN.181 raise exception.FileNotFound(file_path=file_path)
182+
183+class RingBuffer(object):
184+ """Generic userspace on-disk ringbuffer implementation."""
185+ _header_max_int = (2 ** (struct.calcsize('I') * BITS_PER_BYTE)) - 1
186+ _header_format = 'II'
187+ _header_size = struct.calcsize(_header_format)
188+
189+ def __init__(self, backing_file, max_size=65536):
190+ # We need one extra byte as the buffer is kept with
191+ # one byte free to avoid the head==tail full/empty
192+ # problem
193+ max_size += 1
194+
195+ if not 0 < max_size <= RingBuffer._header_max_int:
196+ raise ValueError(_('RingBuffer size out of range'))
197+ had_already_existed = os.path.exists(backing_file)
198+ self.f = self._open(backing_file)
199+ if had_already_existed:
200+ file_size = os.fstat(self.f.fileno()).st_size
201+ if file_size:
202+ current_size = file_size - self._header_size
203+ if not 0 < current_size <= RingBuffer._header_max_int:
204+ self.f.close()
205+ raise ValueError(_('Disk RingBuffer size out of range'))
206+ self.max_size = current_size
207+
208+ # If the file doesn't contain a header, assume it is corrupt
209+ # and recreate
210+ if file_size < self._header_size:
211+ self._write_header(0, 0) # initialise to empty
212+
213+ # If head or tail point beyond the file then bomb out
214+ head, tail = self._read_header()
215+ if head >= current_size or tail >= current_size:
216+ self.f.close()
217+ raise ValueError(_('RingBuffer %s is corrupt') %
218+ backing_file)
219+ else:
220+ # File is zero bytes: treat as new file
221+ self.max_size = max_size
222+ self._initialise_empty_file()
223+ else:
224+ self.max_size = max_size
225+ self._initialise_empty_file()
226+
227+ def _initialise_empty_file(self):
228+ os.ftruncate(self.f.fileno(), self.max_size + self._header_size)
229+ self._write_header(0, 0) # head == tail means no data
230+
231+ @staticmethod
232+ def _open(filename):
233+ """Open a file without truncating it for both reading and writing in
234+ binary mode."""
235+ # Built-in open() cannot open in read/write mode without truncating.
236+ fd = os.open(filename, os.O_RDWR | os.O_CREAT, 0666)
237+ return os.fdopen(fd, 'w+')
238+
239+ def _read_header(self):
240+ self.f.seek(0)
241+ return struct.unpack(self._header_format,
242+ self.f.read(self._header_size))
243+
244+ def _write_header(self, head, tail):
245+ self.f.seek(0)
246+ self.f.write(struct.pack(self._header_format, head, tail))
247+
248+ def _seek(self, pos):
249+ """Seek to pos in data (ignoring header)."""
250+ self.f.seek(self._header_size + pos)
251+
252+ def _read_slice(self, start, end):
253+ if start == end:
254+ return ''
255+
256+ self._seek(start)
257+ return self.f.read(end - start)
258+
259+ def _write_slice(self, pos, data):
260+ self._seek(pos)
261+ self.f.write(data)
262+
263+ def peek(self):
264+ """Read the entire ringbuffer without consuming it."""
265+ head, tail = self._read_header()
266+ if head < tail:
267+ # Wraps around
268+ before_wrap = self._read_slice(tail, self.max_size)
269+ after_wrap = self._read_slice(0, head)
270+ return before_wrap + after_wrap
271+ else:
272+ # Just from here to head
273+ return self._read_slice(tail, head)
274+
275+ def write(self, data):
276+ """Write some amount of data to the ringbuffer, discarding the oldest
277+ data as max_size is exceeded."""
278+ head, tail = self._read_header()
279+ while data:
280+ # Amount of data to be written on this pass
281+ len_to_write = min(len(data), self.max_size - head)
282+
283+ # Where head will be after this write
284+ new_head = head + len_to_write
285+
286+ # In the next comparison, new_head may be self.max_size which is
287+ # logically the same point as tail == 0 and must still be within
288+ # the range tested.
289+ unwrapped_tail = tail if tail else self.max_size
290+
291+ if head < unwrapped_tail <= new_head:
292+ # Write will go past tail so tail needs to be pushed back
293+ tail = new_head + 1 # one past head to indicate full
294+ tail %= self.max_size
295+ self._write_header(head, tail)
296+
297+ # Write the data
298+ self._write_slice(head, data[:len_to_write])
299+ data = data[len_to_write:] # data now left
300+
301+ # Push head back
302+ head = new_head
303+ head %= self.max_size
304+ self._write_header(head, tail)
305+
306+ def flush(self):
307+ self.f.flush()
308+
309+ def close(self):
310+ self.f.close()
311
312
313 @contextlib.contextmanager
314Index: nova-src/nova/virt/libvirt/connection.py
315===================================================================
316--- nova-src.orig/nova/virt/libvirt/connection.py 2012-02-28 10:30:58.000000000 -0800
317+++ nova-src/nova/virt/libvirt/connection.py 2012-02-28 10:32:36.090640111 -0800
318@@ -39,15 +39,19 @@
101 319
102 """320 """
103 321
@@ -109,15 +327,15 @@
109 import os327 import os
110+import select328+import select
111 import shutil329 import shutil
330+import stat
112 import sys331 import sys
113+import stat
114 import uuid332 import uuid
115 333
116+import eventlet334+import eventlet
117 from eventlet import greenthread335 from eventlet import greenthread
118 from xml.dom import minidom336 from xml.dom import minidom
119 from xml.etree import ElementTree337 from xml.etree import ElementTree
120@@ -151,6 +155,9 @@ libvirt_opts = [338@@ -151,6 +155,9 @@
121 help='Override the default disk prefix for the devices attached'339 help='Override the default disk prefix for the devices attached'
122 ' to a server, which is dependent on libvirt_type. '340 ' to a server, which is dependent on libvirt_type. '
123 '(valid options are: sd, xvd, uvd, vd)'),341 '(valid options are: sd, xvd, uvd, vd)'),
@@ -127,7 +345,7 @@
127 ]345 ]
128 346
129 FLAGS = flags.FLAGS347 FLAGS = flags.FLAGS
130@@ -184,6 +191,57 @@ def _get_eph_disk(ephemeral):348@@ -184,6 +191,57 @@
131 return 'disk.eph' + str(ephemeral['num'])349 return 'disk.eph' + str(ephemeral['num'])
132 350
133 351
@@ -185,7 +403,7 @@
185 class LibvirtConnection(driver.ComputeDriver):403 class LibvirtConnection(driver.ComputeDriver):
186 404
187 def __init__(self, read_only):405 def __init__(self, read_only):
188@@ -217,6 +275,8 @@ class LibvirtConnection(driver.ComputeDr406@@ -217,6 +275,8 @@
189 407
190 self.image_cache_manager = imagecache.ImageCacheManager()408 self.image_cache_manager = imagecache.ImageCacheManager()
191 409
@@ -194,7 +412,7 @@
194 @property412 @property
195 def host_state(self):413 def host_state(self):
196 if not self._host_state:414 if not self._host_state:
197@@ -225,7 +285,11 @@ class LibvirtConnection(driver.ComputeDr415@@ -225,7 +285,11 @@
198 416
199 def init_host(self, host):417 def init_host(self, host):
200 # NOTE(nsokolov): moved instance restarting to ComputeManager418 # NOTE(nsokolov): moved instance restarting to ComputeManager
@@ -207,7 +425,7 @@
207 425
208 @property426 @property
209 def libvirt_xml(self):427 def libvirt_xml(self):
210@@ -302,6 +366,15 @@ class LibvirtConnection(driver.ComputeDr428@@ -302,6 +366,15 @@
211 return [self._conn.lookupByID(x).name()429 return [self._conn.lookupByID(x).name()
212 for x in self._conn.listDomainsID()430 for x in self._conn.listDomainsID()
213 if x != 0] # We skip domains with ID 0 (hypervisors).431 if x != 0] # We skip domains with ID 0 (hypervisors).
@@ -223,7 +441,7 @@
223 441
224 @staticmethod442 @staticmethod
225 def _map_to_instance_info(domain):443 def _map_to_instance_info(domain):
226@@ -427,6 +500,7 @@ class LibvirtConnection(driver.ComputeDr444@@ -427,6 +500,7 @@
227 def _cleanup(self, instance):445 def _cleanup(self, instance):
228 target = os.path.join(FLAGS.instances_path, instance['name'])446 target = os.path.join(FLAGS.instances_path, instance['name'])
229 instance_name = instance['name']447 instance_name = instance['name']
@@ -231,7 +449,7 @@
231 LOG.info(_('Deleting instance files %(target)s') % locals(),449 LOG.info(_('Deleting instance files %(target)s') % locals(),
232 instance=instance)450 instance=instance)
233 if FLAGS.libvirt_type == 'lxc':451 if FLAGS.libvirt_type == 'lxc':
234@@ -856,10 +930,10 @@ class LibvirtConnection(driver.ComputeDr452@@ -867,10 +941,10 @@
235 453
236 @exception.wrap_exception()454 @exception.wrap_exception()
237 def get_console_output(self, instance):455 def get_console_output(self, instance):
@@ -245,7 +463,7 @@
245 463
246 if FLAGS.libvirt_type == 'xen':464 if FLAGS.libvirt_type == 'xen':
247 # Xen is special465 # Xen is special
248@@ -867,14 +941,12 @@ class LibvirtConnection(driver.ComputeDr466@@ -878,14 +952,12 @@
249 'ttyconsole',467 'ttyconsole',
250 instance['name'])468 instance['name'])
251 data = self._flush_xen_console(virsh_output)469 data = self._flush_xen_console(virsh_output)
@@ -262,7 +480,7 @@
262 480
263 @staticmethod481 @staticmethod
264 def get_host_ip_addr():482 def get_host_ip_addr():
265@@ -1000,8 +1072,25 @@ class LibvirtConnection(driver.ComputeDr483@@ -1011,8 +1083,25 @@
266 container_dir = '%s/rootfs' % basepath(suffix='')484 container_dir = '%s/rootfs' % basepath(suffix='')
267 libvirt_utils.ensure_tree(container_dir)485 libvirt_utils.ensure_tree(container_dir)
268 486
@@ -290,9 +508,10 @@
290 508
291 if not disk_images:509 if not disk_images:
292 disk_images = {'image_id': instance['image_ref'],510 disk_images = {'image_id': instance['image_ref'],
293diff -Naurp nova.orig/nova/virt/libvirt.xml.template nova/nova/virt/libvirt.xml.template511Index: nova-src/nova/virt/libvirt.xml.template
294--- nova.orig/nova/virt/libvirt.xml.template 2012-02-28 09:08:10.877887173 -0500512===================================================================
295+++ nova/nova/virt/libvirt.xml.template 2012-02-28 09:08:58.685887622 -0500513--- nova-src.orig/nova/virt/libvirt.xml.template 2012-02-28 10:30:58.000000000 -0800
514+++ nova-src/nova/virt/libvirt.xml.template 2012-02-28 10:32:36.094640111 -0800
296@@ -160,8 +160,8 @@515@@ -160,8 +160,8 @@
297 516
298 #end for517 #end for
@@ -304,319768 +523,3 @@
304 <target port='1'/>523 <target port='1'/>
305 </serial>524 </serial>
306 525
307diff -Naurp nova.orig/patch nova/patch
308--- nova.orig/patch 1969-12-31 19:00:00.000000000 -0500
309+++ nova/patch 2012-02-28 09:10:28.545887239 -0500
310@@ -0,0 +1,319761 @@
311+diff -Naurp nova.orig/api/auth.py nova/api/auth.py
312+--- nova.orig/api/auth.py 1969-12-31 19:00:00.000000000 -0500
313++++ nova/api/auth.py 2012-02-28 09:08:10.821887173 -0500
314+@@ -0,0 +1,89 @@
315++# vim: tabstop=4 shiftwidth=4 softtabstop=4
316++
317++# Copyright (c) 2011 OpenStack, LLC
318++#
319++# Licensed under the Apache License, Version 2.0 (the "License"); you may
320++# not use this file except in compliance with the License. You may obtain
321++# a copy of the License at
322++#
323++# http://www.apache.org/licenses/LICENSE-2.0
324++#
325++# Unless required by applicable law or agreed to in writing, software
326++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
327++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
328++# License for the specific language governing permissions and limitations
329++# under the License.
330++"""
331++Common Auth Middleware.
332++
333++"""
334++
335++import webob.dec
336++import webob.exc
337++
338++from nova import context
339++from nova import flags
340++from nova import log as logging
341++from nova.openstack.common import cfg
342++from nova import wsgi
343++
344++
345++use_forwarded_for_opt = cfg.BoolOpt('use_forwarded_for',
346++ default=False,
347++ help='Treat X-Forwarded-For as the canonical remote address. '
348++ 'Only enable this if you have a sanitizing proxy.')
349++
350++FLAGS = flags.FLAGS
351++FLAGS.register_opt(use_forwarded_for_opt)
352++LOG = logging.getLogger(__name__)
353++
354++
355++class InjectContext(wsgi.Middleware):
356++ """Add a 'nova.context' to WSGI environ."""
357++
358++ def __init__(self, context, *args, **kwargs):
359++ self.context = context
360++ super(InjectContext, self).__init__(*args, **kwargs)
361++
362++ @webob.dec.wsgify(RequestClass=wsgi.Request)
363++ def __call__(self, req):
364++ req.environ['nova.context'] = self.context
365++ return self.application
366++
367++
368++class NovaKeystoneContext(wsgi.Middleware):
369++ """Make a request context from keystone headers"""
370++
371++ @webob.dec.wsgify(RequestClass=wsgi.Request)
372++ def __call__(self, req):
373++ user_id = req.headers.get('X_USER')
374++ user_id = req.headers.get('X_USER_ID', user_id)
375++ if user_id is None:
376++ LOG.debug("Neither X_USER_ID nor X_USER found in request")
377++ return webob.exc.HTTPUnauthorized()
378++ # get the roles
379++ roles = [r.strip() for r in req.headers.get('X_ROLE', '').split(',')]
380++ if 'X_TENANT_ID' in req.headers:
381++ # This is the new header since Keystone went to ID/Name
382++ project_id = req.headers['X_TENANT_ID']
383++ else:
384++ # This is for legacy compatibility
385++ project_id = req.headers['X_TENANT']
386++
387++ # Get the auth token
388++ auth_token = req.headers.get('X_AUTH_TOKEN',
389++ req.headers.get('X_STORAGE_TOKEN'))
390++
391++ # Build a context, including the auth_token...
392++ remote_address = req.remote_addr
393++ if FLAGS.use_forwarded_for:
394++ remote_address = req.headers.get('X-Forwarded-For', remote_address)
395++ ctx = context.RequestContext(user_id,
396++ project_id,
397++ roles=roles,
398++ auth_token=auth_token,
399++ strategy='keystone',
400++ remote_address=remote_address)
401++
402++ req.environ['nova.context'] = ctx
403++ return self.application
404+diff -Naurp nova.orig/api/direct.py nova/api/direct.py
405+--- nova.orig/api/direct.py 1969-12-31 19:00:00.000000000 -0500
406++++ nova/api/direct.py 2012-02-28 09:08:10.821887173 -0500
407+@@ -0,0 +1,378 @@
408++# vim: tabstop=4 shiftwidth=4 softtabstop=4
409++
410++# Copyright 2010 United States Government as represented by the
411++# Administrator of the National Aeronautics and Space Administration.
412++# All Rights Reserved.
413++#
414++# Licensed under the Apache License, Version 2.0 (the "License"); you may
415++# not use this file except in compliance with the License. You may obtain
416++# a copy of the License at
417++#
418++# http://www.apache.org/licenses/LICENSE-2.0
419++#
420++# Unless required by applicable law or agreed to in writing, software
421++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
422++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
423++# License for the specific language governing permissions and limitations
424++# under the License.
425++
426++"""Public HTTP interface that allows services to self-register.
427++
428++The general flow of a request is:
429++ - Request is parsed into WSGI bits.
430++ - Some middleware checks authentication.
431++ - Routing takes place based on the URL to find a controller.
432++ (/controller/method)
433++ - Parameters are parsed from the request and passed to a method on the
434++ controller as keyword arguments.
435++ - Optionally 'json' is decoded to provide all the parameters.
436++ - Actual work is done and a result is returned.
437++ - That result is turned into json and returned.
438++
439++"""
440++
441++import inspect
442++import urllib
443++
444++import routes
445++import webob
446++
447++import nova.api.openstack.wsgi
448++from nova import context
449++from nova import exception
450++from nova import utils
451++from nova import wsgi
452++
453++
454++# Global storage for registering modules.
455++ROUTES = {}
456++
457++
458++def register_service(path, handle):
459++ """Register a service handle at a given path.
460++
461++ Services registered in this way will be made available to any instances of
462++ nova.api.direct.Router.
463++
464++ :param path: `routes` path, can be a basic string like "/path"
465++ :param handle: an object whose methods will be made available via the api
466++
467++ """
468++ ROUTES[path] = handle
469++
470++
471++class Router(wsgi.Router):
472++ """A simple WSGI router configured via `register_service`.
473++
474++ This is a quick way to attach multiple services to a given endpoint.
475++ It will automatically load the routes registered in the `ROUTES` global.
476++
477++ TODO(termie): provide a paste-deploy version of this.
478++
479++ """
480++
481++ def __init__(self, mapper=None):
482++ if mapper is None:
483++ mapper = routes.Mapper()
484++
485++ self._load_registered_routes(mapper)
486++ super(Router, self).__init__(mapper=mapper)
487++
488++ def _load_registered_routes(self, mapper):
489++ for route in ROUTES:
490++ mapper.connect('/%s/{action}' % route,
491++ controller=ServiceWrapper(ROUTES[route]))
492++
493++
494++class DelegatedAuthMiddleware(wsgi.Middleware):
495++ """A simple and naive authentication middleware.
496++
497++ Designed mostly to provide basic support for alternative authentication
498++ schemes, this middleware only desires the identity of the user and will
499++ generate the appropriate nova.context.RequestContext for the rest of the
500++ application. This allows any middleware above it in the stack to
501++ authenticate however it would like while only needing to conform to a
502++ minimal interface.
503++
504++ Expects two headers to determine identity:
505++ - X-OpenStack-User
506++ - X-OpenStack-Project
507++
508++ This middleware is tied to identity management and will need to be kept
509++ in sync with any changes to the way identity is dealt with internally.
510++
511++ """
512++
513++ def process_request(self, request):
514++ os_user = request.headers['X-OpenStack-User']
515++ os_project = request.headers['X-OpenStack-Project']
516++ context_ref = context.RequestContext(user_id=os_user,
517++ project_id=os_project)
518++ request.environ['openstack.context'] = context_ref
519++
520++
521++class JsonParamsMiddleware(wsgi.Middleware):
522++ """Middleware to allow method arguments to be passed as serialized JSON.
523++
524++ Accepting arguments as JSON is useful for accepting data that may be more
525++ complex than simple primitives.
526++
527++ In this case we accept it as urlencoded data under the key 'json' as in
528++ json=<urlencoded_json> but this could be extended to accept raw JSON
529++ in the POST body.
530++
531++ Filters out the parameters `self`, `context` and anything beginning with
532++ an underscore.
533++
534++ """
535++
536++ def process_request(self, request):
537++ if 'json' not in request.params:
538++ return
539++
540++ params_json = request.params['json']
541++ params_parsed = utils.loads(params_json)
542++ params = {}
543++ for k, v in params_parsed.iteritems():
544++ if k in ('self', 'context'):
545++ continue
546++ if k.startswith('_'):
547++ continue
548++ params[k] = v
549++
550++ request.environ['openstack.params'] = params
551++
552++
553++class PostParamsMiddleware(wsgi.Middleware):
554++ """Middleware to allow method arguments to be passed as POST parameters.
555++
556++ Filters out the parameters `self`, `context` and anything beginning with
557++ an underscore.
558++
559++ """
560++
561++ def process_request(self, request):
562++ params_parsed = request.params
563++ params = {}
564++ for k, v in params_parsed.iteritems():
565++ if k in ('self', 'context'):
566++ continue
567++ if k.startswith('_'):
568++ continue
569++ params[k] = v
570++
571++ request.environ['openstack.params'] = params
572++
573++
574++class Reflection(object):
575++ """Reflection methods to list available methods.
576++
577++ This is an object that expects to be registered via register_service.
578++ These methods allow the endpoint to be self-describing. They introspect
579++ the exposed methods and provide call signatures and documentation for
580++ them allowing quick experimentation.
581++
582++ """
583++
584++ def __init__(self):
585++ self._methods = {}
586++ self._controllers = {}
587++
588++ def _gather_methods(self):
589++ """Introspect available methods and generate documentation for them."""
590++ methods = {}
591++ controllers = {}
592++ for route, handler in ROUTES.iteritems():
593++ controllers[route] = handler.__doc__.split('\n')[0]
594++ for k in dir(handler):
595++ if k.startswith('_'):
596++ continue
597++ f = getattr(handler, k)
598++ if not callable(f):
599++ continue
600++
601++ # bunch of ugly formatting stuff
602++ argspec = inspect.getargspec(f)
603++ args = [x for x in argspec[0]
604++ if x != 'self' and x != 'context']
605++ defaults = argspec[3] and argspec[3] or []
606++ args_r = list(reversed(args))
607++ defaults_r = list(reversed(defaults))
608++
609++ args_out = []
610++ while args_r:
611++ if defaults_r:
612++ args_out.append((args_r.pop(0),
613++ repr(defaults_r.pop(0))))
614++ else:
615++ args_out.append((str(args_r.pop(0)),))
616++
617++ # if the method accepts keywords
618++ if argspec[2]:
619++ args_out.insert(0, ('**%s' % argspec[2],))
620++
621++ if f.__doc__:
622++ short_doc = f.__doc__.split('\n')[0]
623++ doc = f.__doc__
624++ else:
625++ short_doc = doc = _('not available')
626++
627++ methods['/%s/%s' % (route, k)] = {
628++ 'short_doc': short_doc,
629++ 'doc': doc,
630++ 'name': k,
631++ 'args': list(reversed(args_out))}
632++
633++ self._methods = methods
634++ self._controllers = controllers
635++
636++ def get_controllers(self, context):
637++ """List available controllers."""
638++ if not self._controllers:
639++ self._gather_methods()
640++
641++ return self._controllers
642++
643++ def get_methods(self, context):
644++ """List available methods."""
645++ if not self._methods:
646++ self._gather_methods()
647++
648++ method_list = self._methods.keys()
649++ method_list.sort()
650++ methods = {}
651++ for k in method_list:
652++ methods[k] = self._methods[k]['short_doc']
653++ return methods
654++
655++ def get_method_info(self, context, method):
656++ """Get detailed information about a method."""
657++ if not self._methods:
658++ self._gather_methods()
659++ return self._methods[method]
660++
661++
662++class ServiceWrapper(object):
663++ """Wrapper to dynamically provide a WSGI controller for arbitrary objects.
664++
665++ With lightweight introspection allows public methods on the object to
666++ be accessed via simple WSGI routing and parameters and serializes the
667++ return values.
668++
669++ Automatically used be nova.api.direct.Router to wrap registered instances.
670++
671++ """
672++
673++ def __init__(self, service_handle):
674++ self.service_handle = service_handle
675++
676++ @webob.dec.wsgify(RequestClass=nova.api.openstack.wsgi.Request)
677++ def __call__(self, req):
678++ arg_dict = req.environ['wsgiorg.routing_args'][1]
679++ action = arg_dict['action']
680++ del arg_dict['action']
681++
682++ context = req.environ['openstack.context']
683++ # allow middleware up the stack to override the params
684++ params = {}
685++ if 'openstack.params' in req.environ:
686++ params = req.environ['openstack.params']
687++
688++ # TODO(termie): do some basic normalization on methods
689++ method = getattr(self.service_handle, action)
690++
691++ # NOTE(vish): make sure we have no unicode keys for py2.6.
692++ params = dict([(str(k), v) for (k, v) in params.iteritems()])
693++ result = method(context, **params)
694++
695++ if result is None or isinstance(result, basestring):
696++ return result
697++
698++ try:
699++ content_type = req.best_match_content_type()
700++ serializer = {
701++ 'application/xml': nova.api.openstack.wsgi.XMLDictSerializer(),
702++ 'application/json': nova.api.openstack.wsgi.JSONDictSerializer(),
703++ }[content_type]
704++ return serializer.serialize(result)
705++ except Exception, e:
706++ raise exception.Error(_("Returned non-serializeable type: %s")
707++ % result)
708++
709++
710++class Limited(object):
711++ __notdoc = """Limit the available methods on a given object.
712++
713++ (Not a docstring so that the docstring can be conditionally overridden.)
714++
715++ Useful when defining a public API that only exposes a subset of an
716++ internal API.
717++
718++ Expected usage of this class is to define a subclass that lists the allowed
719++ methods in the 'allowed' variable.
720++
721++ Additionally where appropriate methods can be added or overwritten, for
722++ example to provide backwards compatibility.
723++
724++ The wrapping approach has been chosen so that the wrapped API can maintain
725++ its own internal consistency, for example if it calls "self.create" it
726++ should get its own create method rather than anything we do here.
727++
728++ """
729++
730++ _allowed = None
731++
732++ def __init__(self, proxy):
733++ self._proxy = proxy
734++ if not self.__doc__: # pylint: disable=E0203
735++ self.__doc__ = proxy.__doc__
736++ if not self._allowed:
737++ self._allowed = []
738++
739++ def __getattr__(self, key):
740++ """Only return methods that are named in self._allowed."""
741++ if key not in self._allowed:
742++ raise AttributeError()
743++ return getattr(self._proxy, key)
744++
745++ def __dir__(self):
746++ """Only return methods that are named in self._allowed."""
747++ return [x for x in dir(self._proxy) if x in self._allowed]
748++
749++
750++class Proxy(object):
751++ """Pretend a Direct API endpoint is an object.
752++
753++ This is mostly useful in testing at the moment though it should be easily
754++ extendable to provide a basic API library functionality.
755++
756++ In testing we use this to stub out internal objects to verify that results
757++ from the API are serializable.
758++
759++ """
760++
761++ def __init__(self, app, prefix=None):
762++ self.app = app
763++ self.prefix = prefix
764++
765++ def __do_request(self, path, context, **kwargs):
766++ req = wsgi.Request.blank(path)
767++ req.method = 'POST'
768++ req.body = urllib.urlencode({'json': utils.dumps(kwargs)})
769++ req.environ['openstack.context'] = context
770++ resp = req.get_response(self.app)
771++ try:
772++ return utils.loads(resp.body)
773++ except Exception:
774++ return resp.body
775++
776++ def __getattr__(self, key):
777++ if self.prefix is None:
778++ return self.__class__(self.app, prefix=key)
779++
780++ def _wrapper(context, **kwargs):
781++ return self.__do_request('/%s/%s' % (self.prefix, key),
782++ context,
783++ **kwargs)
784++ _wrapper.func_name = key
785++ return _wrapper
786+diff -Naurp nova.orig/api/ec2/apirequest.py nova/api/ec2/apirequest.py
787+--- nova.orig/api/ec2/apirequest.py 1969-12-31 19:00:00.000000000 -0500
788++++ nova/api/ec2/apirequest.py 2012-02-28 09:08:10.821887173 -0500
789+@@ -0,0 +1,139 @@
790++# vim: tabstop=4 shiftwidth=4 softtabstop=4
791++
792++# Copyright 2010 United States Government as represented by the
793++# Administrator of the National Aeronautics and Space Administration.
794++# All Rights Reserved.
795++#
796++# Licensed under the Apache License, Version 2.0 (the "License"); you may
797++# not use this file except in compliance with the License. You may obtain
798++# a copy of the License at
799++#
800++# http://www.apache.org/licenses/LICENSE-2.0
801++#
802++# Unless required by applicable law or agreed to in writing, software
803++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
804++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
805++# License for the specific language governing permissions and limitations
806++# under the License.
807++
808++"""
809++APIRequest class
810++"""
811++
812++import datetime
813++# TODO(termie): replace minidom with etree
814++from xml.dom import minidom
815++
816++from nova.api.ec2 import ec2utils
817++from nova import exception
818++from nova import flags
819++from nova import log as logging
820++
821++LOG = logging.getLogger(__name__)
822++FLAGS = flags.FLAGS
823++
824++
825++def _underscore_to_camelcase(str):
826++ return ''.join([x[:1].upper() + x[1:] for x in str.split('_')])
827++
828++
829++def _underscore_to_xmlcase(str):
830++ res = _underscore_to_camelcase(str)
831++ return res[:1].lower() + res[1:]
832++
833++
834++def _database_to_isoformat(datetimeobj):
835++ """Return a xs:dateTime parsable string from datatime"""
836++ return datetimeobj.strftime("%Y-%m-%dT%H:%M:%SZ")
837++
838++
839++class APIRequest(object):
840++ def __init__(self, controller, action, version, args):
841++ self.controller = controller
842++ self.action = action
843++ self.version = version
844++ self.args = args
845++
846++ def invoke(self, context):
847++ try:
848++ method = getattr(self.controller,
849++ ec2utils.camelcase_to_underscore(self.action))
850++ except AttributeError:
851++ controller = self.controller
852++ action = self.action
853++ _error = _('Unsupported API request: controller = %(controller)s,'
854++ ' action = %(action)s') % locals()
855++ LOG.exception(_error)
856++ # TODO: Raise custom exception, trap in apiserver,
857++ # and reraise as 400 error.
858++ raise exception.InvalidRequest()
859++
860++ args = ec2utils.dict_from_dotted_str(self.args.items())
861++
862++ for key in args.keys():
863++ # NOTE(vish): Turn numeric dict keys into lists
864++ if isinstance(args[key], dict):
865++ if args[key] != {} and args[key].keys()[0].isdigit():
866++ s = args[key].items()
867++ s.sort()
868++ args[key] = [v for k, v in s]
869++
870++ result = method(context, **args)
871++ return self._render_response(result, context.request_id)
872++
873++ def _render_response(self, response_data, request_id):
874++ xml = minidom.Document()
875++
876++ response_el = xml.createElement(self.action + 'Response')
877++ response_el.setAttribute('xmlns',
878++ 'http://ec2.amazonaws.com/doc/%s/' % self.version)
879++ request_id_el = xml.createElement('requestId')
880++ request_id_el.appendChild(xml.createTextNode(request_id))
881++ response_el.appendChild(request_id_el)
882++ if response_data is True:
883++ self._render_dict(xml, response_el, {'return': 'true'})
884++ else:
885++ self._render_dict(xml, response_el, response_data)
886++
887++ xml.appendChild(response_el)
888++
889++ response = xml.toxml()
890++ xml.unlink()
891++
892++ # Don't write private key to log
893++ if self.action != "CreateKeyPair":
894++ LOG.debug(response)
895++ else:
896++ LOG.debug("CreateKeyPair: Return Private Key")
897++
898++ return response
899++
900++ def _render_dict(self, xml, el, data):
901++ try:
902++ for key in data.keys():
903++ val = data[key]
904++ el.appendChild(self._render_data(xml, key, val))
905++ except Exception:
906++ LOG.debug(data)
907++ raise
908++
909++ def _render_data(self, xml, el_name, data):
910++ el_name = _underscore_to_xmlcase(el_name)
911++ data_el = xml.createElement(el_name)
912++
913++ if isinstance(data, list):
914++ for item in data:
915++ data_el.appendChild(self._render_data(xml, 'item', item))
916++ elif isinstance(data, dict):
917++ self._render_dict(xml, data_el, data)
918++ elif hasattr(data, '__dict__'):
919++ self._render_dict(xml, data_el, data.__dict__)
920++ elif isinstance(data, bool):
921++ data_el.appendChild(xml.createTextNode(str(data).lower()))
922++ elif isinstance(data, datetime.datetime):
923++ data_el.appendChild(
924++ xml.createTextNode(_database_to_isoformat(data)))
925++ elif data is not None:
926++ data_el.appendChild(xml.createTextNode(str(data)))
927++
928++ return data_el
929+diff -Naurp nova.orig/api/ec2/cloud.py nova/api/ec2/cloud.py
930+--- nova.orig/api/ec2/cloud.py 1969-12-31 19:00:00.000000000 -0500
931++++ nova/api/ec2/cloud.py 2012-02-28 09:08:10.821887173 -0500
932+@@ -0,0 +1,1612 @@
933++# vim: tabstop=4 shiftwidth=4 softtabstop=4
934++
935++# Copyright 2010 United States Government as represented by the
936++# Administrator of the National Aeronautics and Space Administration.
937++# All Rights Reserved.
938++#
939++# Licensed under the Apache License, Version 2.0 (the "License"); you may
940++# not use this file except in compliance with the License. You may obtain
941++# a copy of the License at
942++#
943++# http://www.apache.org/licenses/LICENSE-2.0
944++#
945++# Unless required by applicable law or agreed to in writing, software
946++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
947++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
948++# License for the specific language governing permissions and limitations
949++# under the License.
950++
951++"""
952++Cloud Controller: Implementation of EC2 REST API calls, which are
953++dispatched to other nodes via AMQP RPC. State is via distributed
954++datastore.
955++"""
956++
957++import base64
958++import re
959++import time
960++import urllib
961++
962++from nova.api.ec2 import ec2utils
963++from nova.compute import instance_types
964++from nova.api.ec2 import inst_state
965++from nova import block_device
966++from nova import compute
967++from nova.compute import vm_states
968++from nova import crypto
969++from nova import db
970++from nova import exception
971++from nova import flags
972++from nova.image import s3
973++from nova import log as logging
974++from nova import network
975++from nova import rpc
976++from nova import utils
977++from nova import volume
978++from nova.api import validator
979++
980++
981++FLAGS = flags.FLAGS
982++flags.DECLARE('dhcp_domain', 'nova.network.manager')
983++
984++LOG = logging.getLogger(__name__)
985++
986++
987++def validate_ec2_id(val):
988++ if not validator.validate_str()(val):
989++ raise exception.InvalidInstanceIDMalformed(val)
990++ try:
991++ ec2utils.ec2_id_to_id(val)
992++ except exception.InvalidEc2Id:
993++ raise exception.InvalidInstanceIDMalformed(val)
994++
995++
996++def _gen_key(context, user_id, key_name):
997++ """Generate a key
998++
999++ This is a module level method because it is slow and we need to defer
1000++ it into a process pool."""
1001++ # NOTE(vish): generating key pair is slow so check for legal
1002++ # creation before creating key_pair
1003++ try:
1004++ db.key_pair_get(context, user_id, key_name)
1005++ raise exception.KeyPairExists(key_name=key_name)
1006++ except exception.NotFound:
1007++ pass
1008++ private_key, public_key, fingerprint = crypto.generate_key_pair()
1009++ key = {}
1010++ key['user_id'] = user_id
1011++ key['name'] = key_name
1012++ key['public_key'] = public_key
1013++ key['fingerprint'] = fingerprint
1014++ db.key_pair_create(context, key)
1015++ return {'private_key': private_key, 'fingerprint': fingerprint}
1016++
1017++
1018++# EC2 API can return the following values as documented in the EC2 API
1019++# http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/
1020++# ApiReference-ItemType-InstanceStateType.html
1021++# pending 0 | running 16 | shutting-down 32 | terminated 48 | stopping 64 |
1022++# stopped 80
1023++_STATE_DESCRIPTION_MAP = {
1024++ None: inst_state.PENDING,
1025++ vm_states.ACTIVE: inst_state.RUNNING,
1026++ vm_states.BUILDING: inst_state.PENDING,
1027++ vm_states.REBUILDING: inst_state.PENDING,
1028++ vm_states.DELETED: inst_state.TERMINATED,
1029++ vm_states.SOFT_DELETE: inst_state.TERMINATED,
1030++ vm_states.STOPPED: inst_state.STOPPED,
1031++ vm_states.SHUTOFF: inst_state.SHUTOFF,
1032++ vm_states.MIGRATING: inst_state.MIGRATE,
1033++ vm_states.RESIZING: inst_state.RESIZE,
1034++ vm_states.PAUSED: inst_state.PAUSE,
1035++ vm_states.SUSPENDED: inst_state.SUSPEND,
1036++ vm_states.RESCUED: inst_state.RESCUE,
1037++}
1038++
1039++
1040++def _state_description(vm_state, shutdown_terminate):
1041++ """Map the vm state to the server status string"""
1042++ if (vm_state == vm_states.SHUTOFF and
1043++ not shutdown_terminate):
1044++ name = inst_state.STOPPED
1045++ else:
1046++ name = _STATE_DESCRIPTION_MAP.get(vm_state, vm_state)
1047++
1048++ return {'code': inst_state.name_to_code(name),
1049++ 'name': name}
1050++
1051++
1052++def _parse_block_device_mapping(bdm):
1053++ """Parse BlockDeviceMappingItemType into flat hash
1054++ BlockDevicedMapping.<N>.DeviceName
1055++ BlockDevicedMapping.<N>.Ebs.SnapshotId
1056++ BlockDevicedMapping.<N>.Ebs.VolumeSize
1057++ BlockDevicedMapping.<N>.Ebs.DeleteOnTermination
1058++ BlockDevicedMapping.<N>.Ebs.NoDevice
1059++ BlockDevicedMapping.<N>.VirtualName
1060++ => remove .Ebs and allow volume id in SnapshotId
1061++ """
1062++ ebs = bdm.pop('ebs', None)
1063++ if ebs:
1064++ ec2_id = ebs.pop('snapshot_id', None)
1065++ if ec2_id:
1066++ id = ec2utils.ec2_id_to_id(ec2_id)
1067++ if ec2_id.startswith('snap-'):
1068++ bdm['snapshot_id'] = id
1069++ elif ec2_id.startswith('vol-'):
1070++ bdm['volume_id'] = id
1071++ ebs.setdefault('delete_on_termination', True)
1072++ bdm.update(ebs)
1073++ return bdm
1074++
1075++
1076++def _properties_get_mappings(properties):
1077++ return block_device.mappings_prepend_dev(properties.get('mappings', []))
1078++
1079++
1080++def _format_block_device_mapping(bdm):
1081++ """Contruct BlockDeviceMappingItemType
1082++ {'device_name': '...', 'snapshot_id': , ...}
1083++ => BlockDeviceMappingItemType
1084++ """
1085++ keys = (('deviceName', 'device_name'),
1086++ ('virtualName', 'virtual_name'))
1087++ item = {}
1088++ for name, k in keys:
1089++ if k in bdm:
1090++ item[name] = bdm[k]
1091++ if bdm.get('no_device'):
1092++ item['noDevice'] = True
1093++ if ('snapshot_id' in bdm) or ('volume_id' in bdm):
1094++ ebs_keys = (('snapshotId', 'snapshot_id'),
1095++ ('snapshotId', 'volume_id'), # snapshotId is abused
1096++ ('volumeSize', 'volume_size'),
1097++ ('deleteOnTermination', 'delete_on_termination'))
1098++ ebs = {}
1099++ for name, k in ebs_keys:
1100++ if k in bdm:
1101++ if k == 'snapshot_id':
1102++ ebs[name] = ec2utils.id_to_ec2_snap_id(bdm[k])
1103++ elif k == 'volume_id':
1104++ ebs[name] = ec2utils.id_to_ec2_vol_id(bdm[k])
1105++ else:
1106++ ebs[name] = bdm[k]
1107++ assert 'snapshotId' in ebs
1108++ item['ebs'] = ebs
1109++ return item
1110++
1111++
1112++def _format_mappings(properties, result):
1113++ """Format multiple BlockDeviceMappingItemType"""
1114++ mappings = [{'virtualName': m['virtual'], 'deviceName': m['device']}
1115++ for m in _properties_get_mappings(properties)
1116++ if block_device.is_swap_or_ephemeral(m['virtual'])]
1117++
1118++ block_device_mapping = [_format_block_device_mapping(bdm) for bdm in
1119++ properties.get('block_device_mapping', [])]
1120++
1121++ # NOTE(yamahata): overwrite mappings with block_device_mapping
1122++ for bdm in block_device_mapping:
1123++ for i in range(len(mappings)):
1124++ if bdm['deviceName'] == mappings[i]['deviceName']:
1125++ del mappings[i]
1126++ break
1127++ mappings.append(bdm)
1128++
1129++ # NOTE(yamahata): trim ebs.no_device == true. Is this necessary?
1130++ mappings = [bdm for bdm in mappings if not (bdm.get('noDevice', False))]
1131++
1132++ if mappings:
1133++ result['blockDeviceMapping'] = mappings
1134++
1135++
1136++class CloudController(object):
1137++ """ CloudController provides the critical dispatch between
1138++ inbound API calls through the endpoint and messages
1139++ sent to the other nodes.
1140++"""
1141++ def __init__(self):
1142++ self.image_service = s3.S3ImageService()
1143++ self.network_api = network.API()
1144++ self.volume_api = volume.API()
1145++ self.compute_api = compute.API(network_api=self.network_api,
1146++ volume_api=self.volume_api)
1147++ self.sgh = utils.import_object(FLAGS.security_group_handler)
1148++
1149++ def __str__(self):
1150++ return 'CloudController'
1151++
1152++ def _get_image_state(self, image):
1153++ # NOTE(vish): fallback status if image_state isn't set
1154++ state = image.get('status')
1155++ if state == 'active':
1156++ state = 'available'
1157++ return image['properties'].get('image_state', state)
1158++
1159++ def describe_availability_zones(self, context, **kwargs):
1160++ if ('zone_name' in kwargs and
1161++ 'verbose' in kwargs['zone_name'] and
1162++ context.is_admin):
1163++ return self._describe_availability_zones_verbose(context,
1164++ **kwargs)
1165++ else:
1166++ return self._describe_availability_zones(context, **kwargs)
1167++
1168++ def _describe_availability_zones(self, context, **kwargs):
1169++ ctxt = context.elevated()
1170++ enabled_services = db.service_get_all(ctxt, False)
1171++ disabled_services = db.service_get_all(ctxt, True)
1172++ available_zones = []
1173++ for zone in [service.availability_zone for service
1174++ in enabled_services]:
1175++ if not zone in available_zones:
1176++ available_zones.append(zone)
1177++ not_available_zones = []
1178++ for zone in [service.availability_zone for service in disabled_services
1179++ if not service['availability_zone'] in available_zones]:
1180++ if not zone in not_available_zones:
1181++ not_available_zones.append(zone)
1182++ result = []
1183++ for zone in available_zones:
1184++ result.append({'zoneName': zone,
1185++ 'zoneState': "available"})
1186++ for zone in not_available_zones:
1187++ result.append({'zoneName': zone,
1188++ 'zoneState': "not available"})
1189++ return {'availabilityZoneInfo': result}
1190++
1191++ def _describe_availability_zones_verbose(self, context, **kwargs):
1192++ rv = {'availabilityZoneInfo': [{'zoneName': 'nova',
1193++ 'zoneState': 'available'}]}
1194++
1195++ services = db.service_get_all(context, False)
1196++ hosts = []
1197++ for host in [service['host'] for service in services]:
1198++ if not host in hosts:
1199++ hosts.append(host)
1200++ for host in hosts:
1201++ rv['availabilityZoneInfo'].append({'zoneName': '|- %s' % host,
1202++ 'zoneState': ''})
1203++ hsvcs = [service for service in services
1204++ if service['host'] == host]
1205++ for svc in hsvcs:
1206++ alive = utils.service_is_up(svc)
1207++ art = (alive and ":-)") or "XXX"
1208++ active = 'enabled'
1209++ if svc['disabled']:
1210++ active = 'disabled'
1211++ rv['availabilityZoneInfo'].append({
1212++ 'zoneName': '| |- %s' % svc['binary'],
1213++ 'zoneState': '%s %s %s' % (active, art,
1214++ svc['updated_at'])})
1215++ return rv
1216++
1217++ def describe_regions(self, context, region_name=None, **kwargs):
1218++ if FLAGS.region_list:
1219++ regions = []
1220++ for region in FLAGS.region_list:
1221++ name, _sep, host = region.partition('=')
1222++ endpoint = '%s://%s:%s%s' % (FLAGS.ec2_scheme,
1223++ host,
1224++ FLAGS.ec2_port,
1225++ FLAGS.ec2_path)
1226++ regions.append({'regionName': name,
1227++ 'regionEndpoint': endpoint})
1228++ else:
1229++ regions = [{'regionName': 'nova',
1230++ 'regionEndpoint': '%s://%s:%s%s' % (FLAGS.ec2_scheme,
1231++ FLAGS.ec2_host,
1232++ FLAGS.ec2_port,
1233++ FLAGS.ec2_path)}]
1234++ return {'regionInfo': regions}
1235++
1236++ def describe_snapshots(self,
1237++ context,
1238++ snapshot_id=None,
1239++ owner=None,
1240++ restorable_by=None,
1241++ **kwargs):
1242++ if snapshot_id:
1243++ snapshots = []
1244++ for ec2_id in snapshot_id:
1245++ internal_id = ec2utils.ec2_id_to_id(ec2_id)
1246++ snapshot = self.volume_api.get_snapshot(
1247++ context,
1248++ snapshot_id=internal_id)
1249++ snapshots.append(snapshot)
1250++ else:
1251++ snapshots = self.volume_api.get_all_snapshots(context)
1252++ snapshots = [self._format_snapshot(context, s) for s in snapshots]
1253++ return {'snapshotSet': snapshots}
1254++
1255++ def _format_snapshot(self, context, snapshot):
1256++ s = {}
1257++ s['snapshotId'] = ec2utils.id_to_ec2_snap_id(snapshot['id'])
1258++ s['volumeId'] = ec2utils.id_to_ec2_vol_id(snapshot['volume_id'])
1259++ s['status'] = snapshot['status']
1260++ s['startTime'] = snapshot['created_at']
1261++ s['progress'] = snapshot['progress']
1262++ s['ownerId'] = snapshot['project_id']
1263++ s['volumeSize'] = snapshot['volume_size']
1264++ s['description'] = snapshot['display_description']
1265++ return s
1266++
1267++ def create_snapshot(self, context, volume_id, **kwargs):
1268++ validate_ec2_id(volume_id)
1269++ LOG.audit(_("Create snapshot of volume %s"), volume_id,
1270++ context=context)
1271++ volume_id = ec2utils.ec2_id_to_id(volume_id)
1272++ volume = self.volume_api.get(context, volume_id)
1273++ snapshot = self.volume_api.create_snapshot(
1274++ context,
1275++ volume,
1276++ None,
1277++ kwargs.get('description'))
1278++ return self._format_snapshot(context, snapshot)
1279++
1280++ def delete_snapshot(self, context, snapshot_id, **kwargs):
1281++ snapshot_id = ec2utils.ec2_id_to_id(snapshot_id)
1282++ snapshot = self.volume_api.get_snapshot(context, snapshot_id)
1283++ self.volume_api.delete_snapshot(context, snapshot)
1284++ return True
1285++
1286++ def describe_key_pairs(self, context, key_name=None, **kwargs):
1287++ key_pairs = db.key_pair_get_all_by_user(context, context.user_id)
1288++ if not key_name is None:
1289++ key_pairs = [x for x in key_pairs if x['name'] in key_name]
1290++
1291++ result = []
1292++ for key_pair in key_pairs:
1293++ # filter out the vpn keys
1294++ suffix = FLAGS.vpn_key_suffix
1295++ if context.is_admin or not key_pair['name'].endswith(suffix):
1296++ result.append({
1297++ 'keyName': key_pair['name'],
1298++ 'keyFingerprint': key_pair['fingerprint'],
1299++ })
1300++
1301++ return {'keySet': result}
1302++
1303++ def create_key_pair(self, context, key_name, **kwargs):
1304++ LOG.audit(_("Create key pair %s"), key_name, context=context)
1305++ data = _gen_key(context, context.user_id, key_name)
1306++ return {'keyName': key_name,
1307++ 'keyFingerprint': data['fingerprint'],
1308++ 'keyMaterial': data['private_key']}
1309++ # TODO(vish): when context is no longer an object, pass it here
1310++
1311++ def import_key_pair(self, context, key_name, public_key_material,
1312++ **kwargs):
1313++ LOG.audit(_("Import key %s"), key_name, context=context)
1314++ try:
1315++ db.key_pair_get(context, context.user_id, key_name)
1316++ raise exception.KeyPairExists(key_name=key_name)
1317++ except exception.NotFound:
1318++ pass
1319++ public_key = base64.b64decode(public_key_material)
1320++ fingerprint = crypto.generate_fingerprint(public_key)
1321++ key = {}
1322++ key['user_id'] = context.user_id
1323++ key['name'] = key_name
1324++ key['public_key'] = public_key
1325++ key['fingerprint'] = fingerprint
1326++ db.key_pair_create(context, key)
1327++ return {'keyName': key_name,
1328++ 'keyFingerprint': fingerprint}
1329++
1330++ def delete_key_pair(self, context, key_name, **kwargs):
1331++ LOG.audit(_("Delete key pair %s"), key_name, context=context)
1332++ try:
1333++ db.key_pair_destroy(context, context.user_id, key_name)
1334++ except exception.NotFound:
1335++ # aws returns true even if the key doesn't exist
1336++ pass
1337++ return True
1338++
1339++ def describe_security_groups(self, context, group_name=None, group_id=None,
1340++ **kwargs):
1341++ self.compute_api.ensure_default_security_group(context)
1342++ if group_name or group_id:
1343++ groups = []
1344++ if group_name:
1345++ for name in group_name:
1346++ group = db.security_group_get_by_name(context,
1347++ context.project_id,
1348++ name)
1349++ groups.append(group)
1350++ if group_id:
1351++ for gid in group_id:
1352++ group = db.security_group_get(context, gid)
1353++ groups.append(group)
1354++ elif context.is_admin:
1355++ groups = db.security_group_get_all(context)
1356++ else:
1357++ groups = db.security_group_get_by_project(context,
1358++ context.project_id)
1359++ groups = [self._format_security_group(context, g) for g in groups]
1360++
1361++ return {'securityGroupInfo':
1362++ list(sorted(groups,
1363++ key=lambda k: (k['ownerId'], k['groupName'])))}
1364++
1365++ def _format_security_group(self, context, group):
1366++ g = {}
1367++ g['groupDescription'] = group.description
1368++ g['groupName'] = group.name
1369++ g['ownerId'] = group.project_id
1370++ g['ipPermissions'] = []
1371++ for rule in group.rules:
1372++ r = {}
1373++ r['groups'] = []
1374++ r['ipRanges'] = []
1375++ if rule.group_id:
1376++ source_group = db.security_group_get(context, rule.group_id)
1377++ r['groups'] += [{'groupName': source_group.name,
1378++ 'userId': source_group.project_id}]
1379++ if rule.protocol:
1380++ r['ipProtocol'] = rule.protocol
1381++ r['fromPort'] = rule.from_port
1382++ r['toPort'] = rule.to_port
1383++ g['ipPermissions'] += [dict(r)]
1384++ else:
1385++ for protocol, min_port, max_port in (('icmp', -1, -1),
1386++ ('tcp', 1, 65535),
1387++ ('udp', 1, 65536)):
1388++ r['ipProtocol'] = protocol
1389++ r['fromPort'] = min_port
1390++ r['toPort'] = max_port
1391++ g['ipPermissions'] += [dict(r)]
1392++ else:
1393++ r['ipProtocol'] = rule.protocol
1394++ r['fromPort'] = rule.from_port
1395++ r['toPort'] = rule.to_port
1396++ r['ipRanges'] += [{'cidrIp': rule.cidr}]
1397++ g['ipPermissions'] += [r]
1398++ return g
1399++
1400++ def _rule_args_to_dict(self, context, kwargs):
1401++ rules = []
1402++ if not 'groups' in kwargs and not 'ip_ranges' in kwargs:
1403++ rule = self._rule_dict_last_step(context, **kwargs)
1404++ if rule:
1405++ rules.append(rule)
1406++ return rules
1407++ if 'ip_ranges' in kwargs:
1408++ rules = self._cidr_args_split(kwargs)
1409++ else:
1410++ rules = [kwargs]
1411++ finalset = []
1412++ for rule in rules:
1413++ if 'groups' in rule:
1414++ groups_values = self._groups_args_split(rule)
1415++ for groups_value in groups_values:
1416++ final = self._rule_dict_last_step(context, **groups_value)
1417++ finalset.append(final)
1418++ else:
1419++ final = self._rule_dict_last_step(context, **rule)
1420++ finalset.append(final)
1421++ return finalset
1422++
1423++ def _cidr_args_split(self, kwargs):
1424++ cidr_args_split = []
1425++ cidrs = kwargs['ip_ranges']
1426++ for key, cidr in cidrs.iteritems():
1427++ mykwargs = kwargs.copy()
1428++ del mykwargs['ip_ranges']
1429++ mykwargs['cidr_ip'] = cidr['cidr_ip']
1430++ cidr_args_split.append(mykwargs)
1431++ return cidr_args_split
1432++
1433++ def _groups_args_split(self, kwargs):
1434++ groups_args_split = []
1435++ groups = kwargs['groups']
1436++ for key, group in groups.iteritems():
1437++ mykwargs = kwargs.copy()
1438++ del mykwargs['groups']
1439++ if 'group_name' in group:
1440++ mykwargs['source_security_group_name'] = group['group_name']
1441++ if 'user_id' in group:
1442++ mykwargs['source_security_group_owner_id'] = group['user_id']
1443++ if 'group_id' in group:
1444++ mykwargs['source_security_group_id'] = group['group_id']
1445++ groups_args_split.append(mykwargs)
1446++ return groups_args_split
1447++
1448++ def _rule_dict_last_step(self, context, to_port=None, from_port=None,
1449++ ip_protocol=None, cidr_ip=None, user_id=None,
1450++ source_security_group_name=None,
1451++ source_security_group_owner_id=None):
1452++
1453++ values = {}
1454++
1455++ if source_security_group_name:
1456++ source_project_id = self._get_source_project_id(context,
1457++ source_security_group_owner_id)
1458++
1459++ source_security_group = db.security_group_get_by_name(
1460++ context.elevated(),
1461++ source_project_id,
1462++ source_security_group_name)
1463++ notfound = exception.SecurityGroupNotFound
1464++ if not source_security_group:
1465++ raise notfound(security_group_id=source_security_group_name)
1466++ values['group_id'] = source_security_group['id']
1467++ elif cidr_ip:
1468++ # If this fails, it throws an exception. This is what we want.
1469++ cidr_ip = urllib.unquote(cidr_ip).decode()
1470++
1471++ if not utils.is_valid_cidr(cidr_ip):
1472++ # Raise exception for non-valid address
1473++ raise exception.InvalidCidr(cidr=cidr_ip)
1474++
1475++ values['cidr'] = cidr_ip
1476++ else:
1477++ values['cidr'] = '0.0.0.0/0'
1478++
1479++ if ip_protocol and from_port and to_port:
1480++
1481++ ip_protocol = str(ip_protocol)
1482++ try:
1483++ # Verify integer conversions
1484++ from_port = int(from_port)
1485++ to_port = int(to_port)
1486++ except ValueError:
1487++ if ip_protocol.upper() == 'ICMP':
1488++ raise exception.InvalidInput(reason="Type and"
1489++ " Code must be integers for ICMP protocol type")
1490++ else:
1491++ raise exception.InvalidInput(reason="To and From ports "
1492++ "must be integers")
1493++
1494++ if ip_protocol.upper() not in ['TCP', 'UDP', 'ICMP']:
1495++ raise exception.InvalidIpProtocol(protocol=ip_protocol)
1496++
1497++ # Verify that from_port must always be less than
1498++ # or equal to to_port
1499++ if from_port > to_port:
1500++ raise exception.InvalidPortRange(from_port=from_port,
1501++ to_port=to_port, msg="Former value cannot"
1502++ " be greater than the later")
1503++
1504++ # Verify valid TCP, UDP port ranges
1505++ if (ip_protocol.upper() in ['TCP', 'UDP'] and
1506++ (from_port < 1 or to_port > 65535)):
1507++ raise exception.InvalidPortRange(from_port=from_port,
1508++ to_port=to_port, msg="Valid TCP ports should"
1509++ " be between 1-65535")
1510++
1511++ # Verify ICMP type and code
1512++ if (ip_protocol.upper() == "ICMP" and
1513++ (from_port < -1 or to_port > 255)):
1514++ raise exception.InvalidPortRange(from_port=from_port,
1515++ to_port=to_port, msg="For ICMP, the"
1516++ " type:code must be valid")
1517++
1518++ values['protocol'] = ip_protocol
1519++ values['from_port'] = from_port
1520++ values['to_port'] = to_port
1521++ else:
1522++ # If cidr based filtering, protocol and ports are mandatory
1523++ if 'cidr' in values:
1524++ return None
1525++
1526++ return values
1527++
1528++ def _security_group_rule_exists(self, security_group, values):
1529++ """Indicates whether the specified rule values are already
1530++ defined in the given security group.
1531++ """
1532++ for rule in security_group.rules:
1533++ is_duplicate = True
1534++ keys = ('group_id', 'cidr', 'from_port', 'to_port', 'protocol')
1535++ for key in keys:
1536++ if rule.get(key) != values.get(key):
1537++ is_duplicate = False
1538++ break
1539++ if is_duplicate:
1540++ return rule['id']
1541++ return False
1542++
1543++ def revoke_security_group_ingress(self, context, group_name=None,
1544++ group_id=None, **kwargs):
1545++ if not group_name and not group_id:
1546++ err = "Not enough parameters, need group_name or group_id"
1547++ raise exception.EC2APIError(_(err))
1548++ self.compute_api.ensure_default_security_group(context)
1549++ notfound = exception.SecurityGroupNotFound
1550++ if group_name:
1551++ security_group = db.security_group_get_by_name(context,
1552++ context.project_id,
1553++ group_name)
1554++ if not security_group:
1555++ raise notfound(security_group_id=group_name)
1556++ if group_id:
1557++ security_group = db.security_group_get(context, group_id)
1558++ if not security_group:
1559++ raise notfound(security_group_id=group_id)
1560++
1561++ msg = "Revoke security group ingress %s"
1562++ LOG.audit(_(msg), security_group['name'], context=context)
1563++ prevalues = []
1564++ try:
1565++ prevalues = kwargs['ip_permissions']
1566++ except KeyError:
1567++ prevalues.append(kwargs)
1568++ rule_id = None
1569++ rule_ids = []
1570++ for values in prevalues:
1571++ rulesvalues = self._rule_args_to_dict(context, values)
1572++ if not rulesvalues:
1573++ err = "%s Not enough parameters to build a valid rule"
1574++ raise exception.EC2APIError(_(err % rulesvalues))
1575++
1576++ for values_for_rule in rulesvalues:
1577++ values_for_rule['parent_group_id'] = security_group.id
1578++ rule_id = self._security_group_rule_exists(security_group,
1579++ values_for_rule)
1580++ if rule_id:
1581++ db.security_group_rule_destroy(context, rule_id)
1582++ rule_ids.append(rule_id)
1583++ if rule_id:
1584++ # NOTE(vish): we removed a rule, so refresh
1585++ self.compute_api.trigger_security_group_rules_refresh(
1586++ context,
1587++ security_group_id=security_group['id'])
1588++ self.sgh.trigger_security_group_rule_destroy_refresh(
1589++ context, rule_ids)
1590++ return True
1591++ raise exception.EC2APIError(_("No rule for the specified parameters."))
1592++
1593++ # TODO(soren): This has only been tested with Boto as the client.
1594++ # Unfortunately, it seems Boto is using an old API
1595++ # for these operations, so support for newer API versions
1596++ # is sketchy.
1597++ def authorize_security_group_ingress(self, context, group_name=None,
1598++ group_id=None, **kwargs):
1599++ if not group_name and not group_id:
1600++ err = "Not enough parameters, need group_name or group_id"
1601++ raise exception.EC2APIError(_(err))
1602++ self.compute_api.ensure_default_security_group(context)
1603++ notfound = exception.SecurityGroupNotFound
1604++ if group_name:
1605++ security_group = db.security_group_get_by_name(context,
1606++ context.project_id,
1607++ group_name)
1608++ if not security_group:
1609++ raise notfound(security_group_id=group_name)
1610++ if group_id:
1611++ security_group = db.security_group_get(context, group_id)
1612++ if not security_group:
1613++ raise notfound(security_group_id=group_id)
1614++
1615++ msg = "Authorize security group ingress %s"
1616++ LOG.audit(_(msg), security_group['name'], context=context)
1617++ prevalues = []
1618++ try:
1619++ prevalues = kwargs['ip_permissions']
1620++ except KeyError:
1621++ prevalues.append(kwargs)
1622++ postvalues = []
1623++ for values in prevalues:
1624++ rulesvalues = self._rule_args_to_dict(context, values)
1625++ if not rulesvalues:
1626++ err = "%s Not enough parameters to build a valid rule"
1627++ raise exception.EC2APIError(_(err % rulesvalues))
1628++ for values_for_rule in rulesvalues:
1629++ values_for_rule['parent_group_id'] = security_group.id
1630++ if self._security_group_rule_exists(security_group,
1631++ values_for_rule):
1632++ err = '%s - This rule already exists in group'
1633++ raise exception.EC2APIError(_(err) % values_for_rule)
1634++ postvalues.append(values_for_rule)
1635++
1636++ rule_ids = []
1637++ for values_for_rule in postvalues:
1638++ security_group_rule = db.security_group_rule_create(
1639++ context,
1640++ values_for_rule)
1641++ rule_ids.append(security_group_rule['id'])
1642++
1643++ if postvalues:
1644++ self.compute_api.trigger_security_group_rules_refresh(
1645++ context,
1646++ security_group_id=security_group['id'])
1647++ self.sgh.trigger_security_group_rule_create_refresh(
1648++ context, rule_ids)
1649++ return True
1650++
1651++ raise exception.EC2APIError(_("No rule for the specified parameters."))
1652++
1653++ def _get_source_project_id(self, context, source_security_group_owner_id):
1654++ if source_security_group_owner_id:
1655++ # Parse user:project for source group.
1656++ source_parts = source_security_group_owner_id.split(':')
1657++
1658++ # If no project name specified, assume it's same as user name.
1659++ # Since we're looking up by project name, the user name is not
1660++ # used here. It's only read for EC2 API compatibility.
1661++ if len(source_parts) == 2:
1662++ source_project_id = source_parts[1]
1663++ else:
1664++ source_project_id = source_parts[0]
1665++ else:
1666++ source_project_id = context.project_id
1667++
1668++ return source_project_id
1669++
1670++ def create_security_group(self, context, group_name, group_description):
1671++ if not re.match('^[a-zA-Z0-9_\- ]+$', str(group_name)):
1672++ # Some validation to ensure that values match API spec.
1673++ # - Alphanumeric characters, spaces, dashes, and underscores.
1674++ # TODO(Daviey): LP: #813685 extend beyond group_name checking, and
1675++ # probably create a param validator that can be used elsewhere.
1676++ err = _("Value (%s) for parameter GroupName is invalid."
1677++ " Content limited to Alphanumeric characters, "
1678++ "spaces, dashes, and underscores.") % group_name
1679++ # err not that of master ec2 implementation, as they fail to raise.
1680++ raise exception.InvalidParameterValue(err=err)
1681++
1682++ if len(str(group_name)) > 255:
1683++ err = _("Value (%s) for parameter GroupName is invalid."
1684++ " Length exceeds maximum of 255.") % group_name
1685++ raise exception.InvalidParameterValue(err=err)
1686++
1687++ LOG.audit(_("Create Security Group %s"), group_name, context=context)
1688++ self.compute_api.ensure_default_security_group(context)
1689++ if db.security_group_exists(context, context.project_id, group_name):
1690++ msg = _('group %s already exists')
1691++ raise exception.EC2APIError(msg % group_name)
1692++
1693++ group = {'user_id': context.user_id,
1694++ 'project_id': context.project_id,
1695++ 'name': group_name,
1696++ 'description': group_description}
1697++ group_ref = db.security_group_create(context, group)
1698++
1699++ self.sgh.trigger_security_group_create_refresh(context, group)
1700++
1701++ return {'securityGroupSet': [self._format_security_group(context,
1702++ group_ref)]}
1703++
1704++ def delete_security_group(self, context, group_name=None, group_id=None,
1705++ **kwargs):
1706++ if not group_name and not group_id:
1707++ err = "Not enough parameters, need group_name or group_id"
1708++ raise exception.EC2APIError(_(err))
1709++ notfound = exception.SecurityGroupNotFound
1710++ if group_name:
1711++ security_group = db.security_group_get_by_name(context,
1712++ context.project_id,
1713++ group_name)
1714++ if not security_group:
1715++ raise notfound(security_group_id=group_name)
1716++ elif group_id:
1717++ security_group = db.security_group_get(context, group_id)
1718++ if not security_group:
1719++ raise notfound(security_group_id=group_id)
1720++ if db.security_group_in_use(context, security_group.id):
1721++ raise exception.InvalidGroup(reason="In Use")
1722++ LOG.audit(_("Delete security group %s"), group_name, context=context)
1723++ db.security_group_destroy(context, security_group.id)
1724++
1725++ self.sgh.trigger_security_group_destroy_refresh(context,
1726++ security_group.id)
1727++ return True
1728++
1729++ def get_console_output(self, context, instance_id, **kwargs):
1730++ LOG.audit(_("Get console output for instance %s"), instance_id,
1731++ context=context)
1732++ # instance_id may be passed in as a list of instances
1733++ if isinstance(instance_id, list):
1734++ ec2_id = instance_id[0]
1735++ else:
1736++ ec2_id = instance_id
1737++ validate_ec2_id(ec2_id)
1738++ instance_id = ec2utils.ec2_id_to_id(ec2_id)
1739++ instance = self.compute_api.get(context, instance_id)
1740++ output = self.compute_api.get_console_output(context, instance)
1741++ now = utils.utcnow()
1742++ return {"InstanceId": ec2_id,
1743++ "Timestamp": now,
1744++ "output": base64.b64encode(output)}
1745++
1746++ def describe_volumes(self, context, volume_id=None, **kwargs):
1747++ if volume_id:
1748++ volumes = []
1749++ for ec2_id in volume_id:
1750++ validate_ec2_id(ec2_id)
1751++ internal_id = ec2utils.ec2_id_to_id(ec2_id)
1752++ volume = self.volume_api.get(context, internal_id)
1753++ volumes.append(volume)
1754++ else:
1755++ volumes = self.volume_api.get_all(context)
1756++ volumes = [self._format_volume(context, v) for v in volumes]
1757++ return {'volumeSet': volumes}
1758++
1759++ def _format_volume(self, context, volume):
1760++ instance_ec2_id = None
1761++ instance_data = None
1762++ if volume.get('instance', None):
1763++ instance_id = volume['instance']['id']
1764++ instance_ec2_id = ec2utils.id_to_ec2_id(instance_id)
1765++ instance_data = '%s[%s]' % (instance_ec2_id,
1766++ volume['instance']['host'])
1767++ v = {}
1768++ v['volumeId'] = ec2utils.id_to_ec2_vol_id(volume['id'])
1769++ v['status'] = volume['status']
1770++ v['size'] = volume['size']
1771++ v['availabilityZone'] = volume['availability_zone']
1772++ v['createTime'] = volume['created_at']
1773++ if context.is_admin:
1774++ v['status'] = '%s (%s, %s, %s, %s)' % (
1775++ volume['status'],
1776++ volume['project_id'],
1777++ volume['host'],
1778++ instance_data,
1779++ volume['mountpoint'])
1780++ if volume['attach_status'] == 'attached':
1781++ v['attachmentSet'] = [{'attachTime': volume['attach_time'],
1782++ 'deleteOnTermination': False,
1783++ 'device': volume['mountpoint'],
1784++ 'instanceId': instance_ec2_id,
1785++ 'status': 'attached',
1786++ 'volumeId': v['volumeId']}]
1787++ else:
1788++ v['attachmentSet'] = [{}]
1789++ if volume.get('snapshot_id') is not None:
1790++ v['snapshotId'] = ec2utils.id_to_ec2_snap_id(volume['snapshot_id'])
1791++ else:
1792++ v['snapshotId'] = None
1793++
1794++ return v
1795++
1796++ def create_volume(self, context, **kwargs):
1797++ size = kwargs.get('size')
1798++ if kwargs.get('snapshot_id') is not None:
1799++ snapshot_id = ec2utils.ec2_id_to_id(kwargs['snapshot_id'])
1800++ snapshot = self.volume_api.get_snapshot(context, snapshot_id)
1801++ LOG.audit(_("Create volume from snapshot %s"), snapshot_id,
1802++ context=context)
1803++ else:
1804++ snapshot = None
1805++ LOG.audit(_("Create volume of %s GB"), size, context=context)
1806++
1807++ availability_zone = kwargs.get('availability_zone', None)
1808++
1809++ volume = self.volume_api.create(context,
1810++ size,
1811++ None,
1812++ None,
1813++ snapshot,
1814++ availability_zone=availability_zone)
1815++ # TODO(vish): Instance should be None at db layer instead of
1816++ # trying to lazy load, but for now we turn it into
1817++ # a dict to avoid an error.
1818++ return self._format_volume(context, dict(volume))
1819++
1820++ def delete_volume(self, context, volume_id, **kwargs):
1821++ validate_ec2_id(volume_id)
1822++ volume_id = ec2utils.ec2_id_to_id(volume_id)
1823++ volume = self.volume_api.get(context, volume_id)
1824++ self.volume_api.delete(context, volume)
1825++ return True
1826++
1827++ def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
1828++ validate_ec2_id(instance_id)
1829++ validate_ec2_id(volume_id)
1830++ volume_id = ec2utils.ec2_id_to_id(volume_id)
1831++ instance_id = ec2utils.ec2_id_to_id(instance_id)
1832++ instance = self.compute_api.get(context, instance_id)
1833++ msg = _("Attach volume %(volume_id)s to instance %(instance_id)s"
1834++ " at %(device)s") % locals()
1835++ LOG.audit(msg, context=context)
1836++ self.compute_api.attach_volume(context, instance, volume_id, device)
1837++ volume = self.volume_api.get(context, volume_id)
1838++ return {'attachTime': volume['attach_time'],
1839++ 'device': volume['mountpoint'],
1840++ 'instanceId': ec2utils.id_to_ec2_id(instance_id),
1841++ 'requestId': context.request_id,
1842++ 'status': volume['attach_status'],
1843++ 'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)}
1844++
1845++ def detach_volume(self, context, volume_id, **kwargs):
1846++ validate_ec2_id(volume_id)
1847++ volume_id = ec2utils.ec2_id_to_id(volume_id)
1848++ LOG.audit(_("Detach volume %s"), volume_id, context=context)
1849++ volume = self.volume_api.get(context, volume_id)
1850++ instance = self.compute_api.detach_volume(context, volume_id=volume_id)
1851++ return {'attachTime': volume['attach_time'],
1852++ 'device': volume['mountpoint'],
1853++ 'instanceId': ec2utils.id_to_ec2_id(instance['id']),
1854++ 'requestId': context.request_id,
1855++ 'status': volume['attach_status'],
1856++ 'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)}
1857++
1858++ def _format_kernel_id(self, context, instance_ref, result, key):
1859++ kernel_uuid = instance_ref['kernel_id']
1860++ if kernel_uuid is None or kernel_uuid == '':
1861++ return
1862++ kernel_id = self._get_image_id(context, kernel_uuid)
1863++ result[key] = ec2utils.image_ec2_id(kernel_id, 'aki')
1864++
1865++ def _format_ramdisk_id(self, context, instance_ref, result, key):
1866++ ramdisk_uuid = instance_ref['ramdisk_id']
1867++ if ramdisk_uuid is None or ramdisk_uuid == '':
1868++ return
1869++ ramdisk_id = self._get_image_id(context, ramdisk_uuid)
1870++ result[key] = ec2utils.image_ec2_id(ramdisk_id, 'ari')
1871++
1872++ def describe_instance_attribute(self, context, instance_id, attribute,
1873++ **kwargs):
1874++ def _unsupported_attribute(instance, result):
1875++ raise exception.EC2APIError(_('attribute not supported: %s') %
1876++ attribute)
1877++
1878++ def _format_attr_block_device_mapping(instance, result):
1879++ tmp = {}
1880++ self._format_instance_root_device_name(instance, tmp)
1881++ self._format_instance_bdm(context, instance_id,
1882++ tmp['rootDeviceName'], result)
1883++
1884++ def _format_attr_disable_api_termination(instance, result):
1885++ result['disableApiTermination'] = instance['disable_terminate']
1886++
1887++ def _format_attr_group_set(instance, result):
1888++ CloudController._format_group_set(instance, result)
1889++
1890++ def _format_attr_instance_initiated_shutdown_behavior(instance,
1891++ result):
1892++ if instance['shutdown_terminate']:
1893++ result['instanceInitiatedShutdownBehavior'] = 'terminate'
1894++ else:
1895++ result['instanceInitiatedShutdownBehavior'] = 'stop'
1896++
1897++ def _format_attr_instance_type(instance, result):
1898++ self._format_instance_type(instance, result)
1899++
1900++ def _format_attr_kernel(instance, result):
1901++ self._format_kernel_id(context, instance, result, 'kernel')
1902++
1903++ def _format_attr_ramdisk(instance, result):
1904++ self._format_ramdisk_id(context, instance, result, 'ramdisk')
1905++
1906++ def _format_attr_root_device_name(instance, result):
1907++ self._format_instance_root_device_name(instance, result)
1908++
1909++ def _format_attr_source_dest_check(instance, result):
1910++ _unsupported_attribute(instance, result)
1911++
1912++ def _format_attr_user_data(instance, result):
1913++ result['userData'] = base64.b64decode(instance['user_data'])
1914++
1915++ attribute_formatter = {
1916++ 'blockDeviceMapping': _format_attr_block_device_mapping,
1917++ 'disableApiTermination': _format_attr_disable_api_termination,
1918++ 'groupSet': _format_attr_group_set,
1919++ 'instanceInitiatedShutdownBehavior':
1920++ _format_attr_instance_initiated_shutdown_behavior,
1921++ 'instanceType': _format_attr_instance_type,
1922++ 'kernel': _format_attr_kernel,
1923++ 'ramdisk': _format_attr_ramdisk,
1924++ 'rootDeviceName': _format_attr_root_device_name,
1925++ 'sourceDestCheck': _format_attr_source_dest_check,
1926++ 'userData': _format_attr_user_data,
1927++ }
1928++
1929++ fn = attribute_formatter.get(attribute)
1930++ if fn is None:
1931++ raise exception.EC2APIError(
1932++ _('attribute not supported: %s') % attribute)
1933++
1934++ ec2_instance_id = instance_id
1935++ validate_ec2_id(instance_id)
1936++ instance_id = ec2utils.ec2_id_to_id(ec2_instance_id)
1937++ instance = self.compute_api.get(context, instance_id)
1938++ result = {'instance_id': ec2_instance_id}
1939++ fn(instance, result)
1940++ return result
1941++
1942++ def describe_instances(self, context, **kwargs):
1943++ # Optional DescribeInstances argument
1944++ instance_id = kwargs.get('instance_id', None)
1945++ return self._format_describe_instances(context,
1946++ instance_id=instance_id)
1947++
1948++ def describe_instances_v6(self, context, **kwargs):
1949++ # Optional DescribeInstancesV6 argument
1950++ instance_id = kwargs.get('instance_id', None)
1951++ return self._format_describe_instances(context,
1952++ instance_id=instance_id, use_v6=True)
1953++
1954++ def _format_describe_instances(self, context, **kwargs):
1955++ return {'reservationSet': self._format_instances(context, **kwargs)}
1956++
1957++ def _format_run_instances(self, context, reservation_id):
1958++ i = self._format_instances(context, reservation_id=reservation_id)
1959++ assert len(i) == 1
1960++ return i[0]
1961++
1962++ def _format_terminate_instances(self, context, instance_id,
1963++ previous_states):
1964++ instances_set = []
1965++ for (ec2_id, previous_state) in zip(instance_id, previous_states):
1966++ i = {}
1967++ i['instanceId'] = ec2_id
1968++ i['previousState'] = _state_description(previous_state['vm_state'],
1969++ previous_state['shutdown_terminate'])
1970++ try:
1971++ internal_id = ec2utils.ec2_id_to_id(ec2_id)
1972++ instance = self.compute_api.get(context, internal_id)
1973++ i['shutdownState'] = _state_description(instance['vm_state'],
1974++ instance['shutdown_terminate'])
1975++ except exception.NotFound:
1976++ i['shutdownState'] = _state_description(vm_states.DELETED,
1977++ True)
1978++ instances_set.append(i)
1979++ return {'instancesSet': instances_set}
1980++
1981++ def _format_instance_bdm(self, context, instance_id, root_device_name,
1982++ result):
1983++ """Format InstanceBlockDeviceMappingResponseItemType"""
1984++ root_device_type = 'instance-store'
1985++ mapping = []
1986++ for bdm in db.block_device_mapping_get_all_by_instance(context,
1987++ instance_id):
1988++ volume_id = bdm['volume_id']
1989++ if (volume_id is None or bdm['no_device']):
1990++ continue
1991++
1992++ if (bdm['device_name'] == root_device_name and
1993++ (bdm['snapshot_id'] or bdm['volume_id'])):
1994++ assert not bdm['virtual_name']
1995++ root_device_type = 'ebs'
1996++
1997++ vol = self.volume_api.get(context, volume_id)
1998++ LOG.debug(_("vol = %s\n"), vol)
1999++ # TODO(yamahata): volume attach time
2000++ ebs = {'volumeId': volume_id,
2001++ 'deleteOnTermination': bdm['delete_on_termination'],
2002++ 'attachTime': vol['attach_time'] or '-',
2003++ 'status': vol['status'], }
2004++ res = {'deviceName': bdm['device_name'],
2005++ 'ebs': ebs, }
2006++ mapping.append(res)
2007++
2008++ if mapping:
2009++ result['blockDeviceMapping'] = mapping
2010++ result['rootDeviceType'] = root_device_type
2011++
2012++ @staticmethod
2013++ def _format_instance_root_device_name(instance, result):
2014++ result['rootDeviceName'] = (instance.get('root_device_name') or
2015++ block_device.DEFAULT_ROOT_DEV_NAME)
2016++
2017++ @staticmethod
2018++ def _format_instance_type(instance, result):
2019++ if instance['instance_type']:
2020++ result['instanceType'] = instance['instance_type'].get('name')
2021++ else:
2022++ result['instanceType'] = None
2023++
2024++ @staticmethod
2025++ def _format_group_set(instance, result):
2026++ security_group_names = []
2027++ if instance.get('security_groups'):
2028++ for security_group in instance['security_groups']:
2029++ security_group_names.append(security_group['name'])
2030++ result['groupSet'] = utils.convert_to_list_dict(
2031++ security_group_names, 'groupId')
2032++
2033++ def _format_instances(self, context, instance_id=None, use_v6=False,
2034++ **search_opts):
2035++ # TODO(termie): this method is poorly named as its name does not imply
2036++ # that it will be making a variety of database calls
2037++ # rather than simply formatting a bunch of instances that
2038++ # were handed to it
2039++ reservations = {}
2040++ # NOTE(vish): instance_id is an optional list of ids to filter by
2041++ if instance_id:
2042++ instances = []
2043++ for ec2_id in instance_id:
2044++ internal_id = ec2utils.ec2_id_to_id(ec2_id)
2045++ try:
2046++ instance = self.compute_api.get(context, internal_id)
2047++ except exception.NotFound:
2048++ continue
2049++ instances.append(instance)
2050++ else:
2051++ try:
2052++ # always filter out deleted instances
2053++ search_opts['deleted'] = False
2054++ instances = self.compute_api.get_all(context,
2055++ search_opts=search_opts)
2056++ except exception.NotFound:
2057++ instances = []
2058++ for instance in instances:
2059++ if not context.is_admin:
2060++ if instance['image_ref'] == str(FLAGS.vpn_image_id):
2061++ continue
2062++ i = {}
2063++ instance_id = instance['id']
2064++ ec2_id = ec2utils.id_to_ec2_id(instance_id)
2065++ i['instanceId'] = ec2_id
2066++ image_uuid = instance['image_ref']
2067++ image_id = self._get_image_id(context, image_uuid)
2068++ i['imageId'] = ec2utils.image_ec2_id(image_id)
2069++ self._format_kernel_id(context, instance, i, 'kernelId')
2070++ self._format_ramdisk_id(context, instance, i, 'ramdiskId')
2071++ i['instanceState'] = _state_description(
2072++ instance['vm_state'], instance['shutdown_terminate'])
2073++
2074++ fixed_ip = None
2075++ floating_ip = None
2076++ ip_info = ec2utils.get_ip_info_for_instance(context, instance)
2077++ if ip_info['fixed_ips']:
2078++ fixed_ip = ip_info['fixed_ips'][0]
2079++ if ip_info['floating_ips']:
2080++ floating_ip = ip_info['floating_ips'][0]
2081++ if ip_info['fixed_ip6s']:
2082++ i['dnsNameV6'] = ip_info['fixed_ip6s'][0]
2083++ i['privateDnsName'] = instance['hostname']
2084++ i['privateIpAddress'] = fixed_ip
2085++ i['publicDnsName'] = floating_ip
2086++ i['ipAddress'] = floating_ip or fixed_ip
2087++ i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
2088++ i['keyName'] = instance['key_name']
2089++
2090++ if context.is_admin:
2091++ i['keyName'] = '%s (%s, %s)' % (i['keyName'],
2092++ instance['project_id'],
2093++ instance['host'])
2094++ i['productCodesSet'] = utils.convert_to_list_dict([],
2095++ 'product_codes')
2096++ self._format_instance_type(instance, i)
2097++ i['launchTime'] = instance['created_at']
2098++ i['amiLaunchIndex'] = instance['launch_index']
2099++ self._format_instance_root_device_name(instance, i)
2100++ self._format_instance_bdm(context, instance_id,
2101++ i['rootDeviceName'], i)
2102++ host = instance['host']
2103++ services = db.service_get_all_by_host(context.elevated(), host)
2104++ zone = ec2utils.get_availability_zone_by_host(services, host)
2105++ i['placement'] = {'availabilityZone': zone}
2106++ if instance['reservation_id'] not in reservations:
2107++ r = {}
2108++ r['reservationId'] = instance['reservation_id']
2109++ r['ownerId'] = instance['project_id']
2110++ self._format_group_set(instance, r)
2111++ r['instancesSet'] = []
2112++ reservations[instance['reservation_id']] = r
2113++ reservations[instance['reservation_id']]['instancesSet'].append(i)
2114++
2115++ return list(reservations.values())
2116++
2117++ def describe_addresses(self, context, **kwargs):
2118++ return self.format_addresses(context)
2119++
2120++ def format_addresses(self, context):
2121++ addresses = []
2122++ floaters = self.network_api.get_floating_ips_by_project(context)
2123++ for floating_ip_ref in floaters:
2124++ if floating_ip_ref['project_id'] is None:
2125++ continue
2126++ address = floating_ip_ref['address']
2127++ ec2_id = None
2128++ if floating_ip_ref['fixed_ip_id']:
2129++ fixed_id = floating_ip_ref['fixed_ip_id']
2130++ fixed = self.network_api.get_fixed_ip(context, fixed_id)
2131++ if fixed['instance_id'] is not None:
2132++ ec2_id = ec2utils.id_to_ec2_id(fixed['instance_id'])
2133++ address_rv = {'public_ip': address,
2134++ 'instance_id': ec2_id}
2135++ if context.is_admin:
2136++ details = "%s (%s)" % (address_rv['instance_id'],
2137++ floating_ip_ref['project_id'])
2138++ address_rv['instance_id'] = details
2139++ addresses.append(address_rv)
2140++ return {'addressesSet': addresses}
2141++
2142++ def allocate_address(self, context, **kwargs):
2143++ LOG.audit(_("Allocate address"), context=context)
2144++ try:
2145++ public_ip = self.network_api.allocate_floating_ip(context)
2146++ return {'publicIp': public_ip}
2147++ except rpc.RemoteError as ex:
2148++ # NOTE(tr3buchet) - why does this block exist?
2149++ if ex.exc_type == 'NoMoreFloatingIps':
2150++ raise exception.NoMoreFloatingIps()
2151++ else:
2152++ raise
2153++
2154++ def release_address(self, context, public_ip, **kwargs):
2155++ LOG.audit(_("Release address %s"), public_ip, context=context)
2156++ self.network_api.release_floating_ip(context, address=public_ip)
2157++ return {'return': "true"}
2158++
2159++ def associate_address(self, context, instance_id, public_ip, **kwargs):
2160++ LOG.audit(_("Associate address %(public_ip)s to"
2161++ " instance %(instance_id)s") % locals(), context=context)
2162++ instance_id = ec2utils.ec2_id_to_id(instance_id)
2163++ instance = self.compute_api.get(context, instance_id)
2164++ self.compute_api.associate_floating_ip(context,
2165++ instance,
2166++ address=public_ip)
2167++ return {'return': "true"}
2168++
2169++ def disassociate_address(self, context, public_ip, **kwargs):
2170++ LOG.audit(_("Disassociate address %s"), public_ip, context=context)
2171++ self.network_api.disassociate_floating_ip(context, address=public_ip)
2172++ return {'return': "true"}
2173++
2174++ def run_instances(self, context, **kwargs):
2175++ max_count = int(kwargs.get('max_count', 1))
2176++ if kwargs.get('kernel_id'):
2177++ kernel = self._get_image(context, kwargs['kernel_id'])
2178++ kwargs['kernel_id'] = self._get_image_uuid(context, kernel['id'])
2179++ if kwargs.get('ramdisk_id'):
2180++ ramdisk = self._get_image(context, kwargs['ramdisk_id'])
2181++ kwargs['ramdisk_id'] = self._get_image_uuid(context,
2182++ ramdisk['id'])
2183++ for bdm in kwargs.get('block_device_mapping', []):
2184++ _parse_block_device_mapping(bdm)
2185++
2186++ image = self._get_image(context, kwargs['image_id'])
2187++ image_uuid = self._get_image_uuid(context, image['id'])
2188++
2189++ if image:
2190++ image_state = self._get_image_state(image)
2191++ else:
2192++ raise exception.ImageNotFound(image_id=kwargs['image_id'])
2193++
2194++ if image_state != 'available':
2195++ raise exception.EC2APIError(_('Image must be available'))
2196++
2197++ (instances, resv_id) = self.compute_api.create(context,
2198++ instance_type=instance_types.get_instance_type_by_name(
2199++ kwargs.get('instance_type', None)),
2200++ image_href=image_uuid,
2201++ min_count=int(kwargs.get('min_count', max_count)),
2202++ max_count=max_count,
2203++ kernel_id=kwargs.get('kernel_id'),
2204++ ramdisk_id=kwargs.get('ramdisk_id'),
2205++ key_name=kwargs.get('key_name'),
2206++ user_data=kwargs.get('user_data'),
2207++ security_group=kwargs.get('security_group'),
2208++ availability_zone=kwargs.get('placement', {}).get(
2209++ 'availability_zone'),
2210++ block_device_mapping=kwargs.get('block_device_mapping', {}))
2211++ return self._format_run_instances(context, resv_id)
2212++
2213++ def terminate_instances(self, context, instance_id, **kwargs):
2214++ """Terminate each instance in instance_id, which is a list of ec2 ids.
2215++ instance_id is a kwarg so its name cannot be modified."""
2216++ LOG.debug(_("Going to start terminating instances"))
2217++ previous_states = []
2218++ for ec2_id in instance_id:
2219++ validate_ec2_id(ec2_id)
2220++ _instance_id = ec2utils.ec2_id_to_id(ec2_id)
2221++ instance = self.compute_api.get(context, _instance_id)
2222++ previous_states.append(instance)
2223++ self.compute_api.delete(context, instance)
2224++ return self._format_terminate_instances(context,
2225++ instance_id,
2226++ previous_states)
2227++
2228++ def reboot_instances(self, context, instance_id, **kwargs):
2229++ """instance_id is a list of instance ids"""
2230++ LOG.audit(_("Reboot instance %r"), instance_id, context=context)
2231++ for ec2_id in instance_id:
2232++ validate_ec2_id(ec2_id)
2233++ _instance_id = ec2utils.ec2_id_to_id(ec2_id)
2234++ instance = self.compute_api.get(context, _instance_id)
2235++ self.compute_api.reboot(context, instance, 'HARD')
2236++ return True
2237++
2238++ def stop_instances(self, context, instance_id, **kwargs):
2239++ """Stop each instances in instance_id.
2240++ Here instance_id is a list of instance ids"""
2241++ LOG.debug(_("Going to stop instances"))
2242++ for ec2_id in instance_id:
2243++ validate_ec2_id(ec2_id)
2244++ _instance_id = ec2utils.ec2_id_to_id(ec2_id)
2245++ instance = self.compute_api.get(context, _instance_id)
2246++ self.compute_api.stop(context, instance)
2247++ return True
2248++
2249++ def start_instances(self, context, instance_id, **kwargs):
2250++ """Start each instances in instance_id.
2251++ Here instance_id is a list of instance ids"""
2252++ LOG.debug(_("Going to start instances"))
2253++ for ec2_id in instance_id:
2254++ validate_ec2_id(ec2_id)
2255++ _instance_id = ec2utils.ec2_id_to_id(ec2_id)
2256++ instance = self.compute_api.get(context, _instance_id)
2257++ self.compute_api.start(context, instance)
2258++ return True
2259++
2260++ def _get_image(self, context, ec2_id):
2261++ try:
2262++ internal_id = ec2utils.ec2_id_to_id(ec2_id)
2263++ image = self.image_service.show(context, internal_id)
2264++ except (exception.InvalidEc2Id, exception.ImageNotFound):
2265++ try:
2266++ return self.image_service.show_by_name(context, ec2_id)
2267++ except exception.NotFound:
2268++ raise exception.ImageNotFound(image_id=ec2_id)
2269++ image_type = ec2_id.split('-')[0]
2270++ if ec2utils.image_type(image.get('container_format')) != image_type:
2271++ raise exception.ImageNotFound(image_id=ec2_id)
2272++ return image
2273++
2274++ # NOTE(bcwaldon): We need access to the image uuid since we directly
2275++ # call the compute api from this class
2276++ def _get_image_uuid(self, context, internal_id):
2277++ return self.image_service.get_image_uuid(context, internal_id)
2278++
2279++ # NOTE(bcwaldon): We also need to be able to map image uuids to integers
2280++ def _get_image_id(self, context, image_uuid):
2281++ return self.image_service.get_image_id(context, image_uuid)
2282++
2283++ def _format_image(self, image):
2284++ """Convert from format defined by GlanceImageService to S3 format."""
2285++ i = {}
2286++ image_type = ec2utils.image_type(image.get('container_format'))
2287++ ec2_id = ec2utils.image_ec2_id(image.get('id'), image_type)
2288++ name = image.get('name')
2289++ i['imageId'] = ec2_id
2290++ kernel_id = image['properties'].get('kernel_id')
2291++ if kernel_id:
2292++ i['kernelId'] = ec2utils.image_ec2_id(kernel_id, 'aki')
2293++ ramdisk_id = image['properties'].get('ramdisk_id')
2294++ if ramdisk_id:
2295++ i['ramdiskId'] = ec2utils.image_ec2_id(ramdisk_id, 'ari')
2296++ i['imageOwnerId'] = image['properties'].get('owner_id')
2297++ if name:
2298++ i['imageLocation'] = "%s (%s)" % (image['properties'].
2299++ get('image_location'), name)
2300++ else:
2301++ i['imageLocation'] = image['properties'].get('image_location')
2302++
2303++ i['imageState'] = self._get_image_state(image)
2304++ i['description'] = image.get('description')
2305++ display_mapping = {'aki': 'kernel',
2306++ 'ari': 'ramdisk',
2307++ 'ami': 'machine'}
2308++ i['imageType'] = display_mapping.get(image_type)
2309++ i['isPublic'] = not not image.get('is_public')
2310++ i['architecture'] = image['properties'].get('architecture')
2311++
2312++ properties = image['properties']
2313++ root_device_name = block_device.properties_root_device_name(properties)
2314++ root_device_type = 'instance-store'
2315++ for bdm in properties.get('block_device_mapping', []):
2316++ if (bdm.get('device_name') == root_device_name and
2317++ ('snapshot_id' in bdm or 'volume_id' in bdm) and
2318++ not bdm.get('no_device')):
2319++ root_device_type = 'ebs'
2320++ i['rootDeviceName'] = (root_device_name or
2321++ block_device.DEFAULT_ROOT_DEV_NAME)
2322++ i['rootDeviceType'] = root_device_type
2323++
2324++ _format_mappings(properties, i)
2325++
2326++ return i
2327++
2328++ def describe_images(self, context, image_id=None, **kwargs):
2329++ # NOTE: image_id is a list!
2330++ if image_id:
2331++ images = []
2332++ for ec2_id in image_id:
2333++ try:
2334++ image = self._get_image(context, ec2_id)
2335++ except exception.NotFound:
2336++ raise exception.ImageNotFound(image_id=ec2_id)
2337++ images.append(image)
2338++ else:
2339++ images = self.image_service.detail(context)
2340++ images = [self._format_image(i) for i in images]
2341++ return {'imagesSet': images}
2342++
2343++ def deregister_image(self, context, image_id, **kwargs):
2344++ LOG.audit(_("De-registering image %s"), image_id, context=context)
2345++ image = self._get_image(context, image_id)
2346++ internal_id = image['id']
2347++ self.image_service.delete(context, internal_id)
2348++ return {'imageId': image_id}
2349++
2350++ def _register_image(self, context, metadata):
2351++ image = self.image_service.create(context, metadata)
2352++ image_type = ec2utils.image_type(image.get('container_format'))
2353++ image_id = ec2utils.image_ec2_id(image['id'], image_type)
2354++ return image_id
2355++
2356++ def register_image(self, context, image_location=None, **kwargs):
2357++ if image_location is None and 'name' in kwargs:
2358++ image_location = kwargs['name']
2359++ metadata = {'properties': {'image_location': image_location}}
2360++
2361++ if 'root_device_name' in kwargs:
2362++ metadata['properties']['root_device_name'] = kwargs.get(
2363++ 'root_device_name')
2364++
2365++ mappings = [_parse_block_device_mapping(bdm) for bdm in
2366++ kwargs.get('block_device_mapping', [])]
2367++ if mappings:
2368++ metadata['properties']['block_device_mapping'] = mappings
2369++
2370++ image_id = self._register_image(context, metadata)
2371++ msg = _("Registered image %(image_location)s with"
2372++ " id %(image_id)s") % locals()
2373++ LOG.audit(msg, context=context)
2374++ return {'imageId': image_id}
2375++
2376++ def describe_image_attribute(self, context, image_id, attribute, **kwargs):
2377++ def _block_device_mapping_attribute(image, result):
2378++ _format_mappings(image['properties'], result)
2379++
2380++ def _launch_permission_attribute(image, result):
2381++ result['launchPermission'] = []
2382++ if image['is_public']:
2383++ result['launchPermission'].append({'group': 'all'})
2384++
2385++ def _root_device_name_attribute(image, result):
2386++ _prop_root_dev_name = block_device.properties_root_device_name
2387++ result['rootDeviceName'] = _prop_root_dev_name(image['properties'])
2388++ if result['rootDeviceName'] is None:
2389++ result['rootDeviceName'] = block_device.DEFAULT_ROOT_DEV_NAME
2390++
2391++ supported_attributes = {
2392++ 'blockDeviceMapping': _block_device_mapping_attribute,
2393++ 'launchPermission': _launch_permission_attribute,
2394++ 'rootDeviceName': _root_device_name_attribute,
2395++ }
2396++
2397++ fn = supported_attributes.get(attribute)
2398++ if fn is None:
2399++ raise exception.EC2APIError(_('attribute not supported: %s')
2400++ % attribute)
2401++ try:
2402++ image = self._get_image(context, image_id)
2403++ except exception.NotFound:
2404++ raise exception.ImageNotFound(image_id=image_id)
2405++
2406++ result = {'imageId': image_id}
2407++ fn(image, result)
2408++ return result
2409++
2410++ def modify_image_attribute(self, context, image_id, attribute,
2411++ operation_type, **kwargs):
2412++ # TODO(devcamcar): Support users and groups other than 'all'.
2413++ if attribute != 'launchPermission':
2414++ raise exception.EC2APIError(_('attribute not supported: %s')
2415++ % attribute)
2416++ if not 'user_group' in kwargs:
2417++ raise exception.EC2APIError(_('user or group not specified'))
2418++ if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all':
2419++ raise exception.EC2APIError(_('only group "all" is supported'))
2420++ if not operation_type in ['add', 'remove']:
2421++ msg = _('operation_type must be add or remove')
2422++ raise exception.EC2APIError(msg)
2423++ LOG.audit(_("Updating image %s publicity"), image_id, context=context)
2424++
2425++ try:
2426++ image = self._get_image(context, image_id)
2427++ except exception.NotFound:
2428++ raise exception.ImageNotFound(image_id=image_id)
2429++ internal_id = image['id']
2430++ del(image['id'])
2431++
2432++ image['is_public'] = (operation_type == 'add')
2433++ return self.image_service.update(context, internal_id, image)
2434++
2435++ def update_image(self, context, image_id, **kwargs):
2436++ internal_id = ec2utils.ec2_id_to_id(image_id)
2437++ result = self.image_service.update(context, internal_id, dict(kwargs))
2438++ return result
2439++
2440++ # TODO(yamahata): race condition
2441++ # At the moment there is no way to prevent others from
2442++ # manipulating instances/volumes/snapshots.
2443++ # As other code doesn't take it into consideration, here we don't
2444++ # care of it for now. Ostrich algorithm
2445++ def create_image(self, context, instance_id, **kwargs):
2446++ # NOTE(yamahata): name/description are ignored by register_image(),
2447++ # do so here
2448++ no_reboot = kwargs.get('no_reboot', False)
2449++ validate_ec2_id(instance_id)
2450++ ec2_instance_id = instance_id
2451++ instance_id = ec2utils.ec2_id_to_id(ec2_instance_id)
2452++ instance = self.compute_api.get(context, instance_id)
2453++
2454++ # stop the instance if necessary
2455++ restart_instance = False
2456++ if not no_reboot:
2457++ vm_state = instance['vm_state']
2458++
2459++ # if the instance is in subtle state, refuse to proceed.
2460++ if vm_state not in (vm_states.ACTIVE, vm_states.SHUTOFF,
2461++ vm_states.STOPPED):
2462++ raise exception.InstanceNotRunning(instance_id=ec2_instance_id)
2463++
2464++ if vm_state in (vm_states.ACTIVE, vm_states.SHUTOFF):
2465++ restart_instance = True
2466++ self.compute_api.stop(context, instance)
2467++
2468++ # wait instance for really stopped
2469++ start_time = time.time()
2470++ while vm_state != vm_states.STOPPED:
2471++ time.sleep(1)
2472++ instance = self.compute_api.get(context, instance_id)
2473++ vm_state = instance['vm_state']
2474++ # NOTE(yamahata): timeout and error. 1 hour for now for safety.
2475++ # Is it too short/long?
2476++ # Or is there any better way?
2477++ timeout = 1 * 60 * 60 * 60
2478++ if time.time() > start_time + timeout:
2479++ raise exception.EC2APIError(
2480++ _('Couldn\'t stop instance with in %d sec') % timeout)
2481++
2482++ src_image = self._get_image(context, instance['image_ref'])
2483++ properties = src_image['properties']
2484++ if instance['root_device_name']:
2485++ properties['root_device_name'] = instance['root_device_name']
2486++
2487++ mapping = []
2488++ bdms = db.block_device_mapping_get_all_by_instance(context,
2489++ instance_id)
2490++ for bdm in bdms:
2491++ if bdm.no_device:
2492++ continue
2493++ m = {}
2494++ for attr in ('device_name', 'snapshot_id', 'volume_id',
2495++ 'volume_size', 'delete_on_termination', 'no_device',
2496++ 'virtual_name'):
2497++ val = getattr(bdm, attr)
2498++ if val is not None:
2499++ m[attr] = val
2500++
2501++ volume_id = m.get('volume_id')
2502++ if m.get('snapshot_id') and volume_id:
2503++ # create snapshot based on volume_id
2504++ volume = self.volume_api.get(context, volume_id)
2505++ # NOTE(yamahata): Should we wait for snapshot creation?
2506++ # Linux LVM snapshot creation completes in
2507++ # short time, it doesn't matter for now.
2508++ snapshot = self.volume_api.create_snapshot_force(
2509++ context, volume, volume['display_name'],
2510++ volume['display_description'])
2511++ m['snapshot_id'] = snapshot['id']
2512++ del m['volume_id']
2513++
2514++ if m:
2515++ mapping.append(m)
2516++
2517++ for m in _properties_get_mappings(properties):
2518++ virtual_name = m['virtual']
2519++ if virtual_name in ('ami', 'root'):
2520++ continue
2521++
2522++ assert block_device.is_swap_or_ephemeral(virtual_name)
2523++ device_name = m['device']
2524++ if device_name in [b['device_name'] for b in mapping
2525++ if not b.get('no_device', False)]:
2526++ continue
2527++
2528++ # NOTE(yamahata): swap and ephemeral devices are specified in
2529++ # AMI, but disabled for this instance by user.
2530++ # So disable those device by no_device.
2531++ mapping.append({'device_name': device_name, 'no_device': True})
2532++
2533++ if mapping:
2534++ properties['block_device_mapping'] = mapping
2535++
2536++ for attr in ('status', 'location', 'id'):
2537++ src_image.pop(attr, None)
2538++
2539++ image_id = self._register_image(context, src_image)
2540++
2541++ if restart_instance:
2542++ self.compute_api.start(context, instance_id=instance_id)
2543++
2544++ return {'imageId': image_id}
2545+diff -Naurp nova.orig/api/ec2/ec2utils.py nova/api/ec2/ec2utils.py
2546+--- nova.orig/api/ec2/ec2utils.py 1969-12-31 19:00:00.000000000 -0500
2547++++ nova/api/ec2/ec2utils.py 2012-02-28 09:08:10.821887173 -0500
2548+@@ -0,0 +1,208 @@
2549++# vim: tabstop=4 shiftwidth=4 softtabstop=4
2550++
2551++# Copyright 2010 United States Government as represented by the
2552++# Administrator of the National Aeronautics and Space Administration.
2553++# All Rights Reserved.
2554++#
2555++# Licensed under the Apache License, Version 2.0 (the "License"); you may
2556++# not use this file except in compliance with the License. You may obtain
2557++# a copy of the License at
2558++#
2559++# http://www.apache.org/licenses/LICENSE-2.0
2560++#
2561++# Unless required by applicable law or agreed to in writing, software
2562++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2563++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2564++# License for the specific language governing permissions and limitations
2565++# under the License.
2566++
2567++import re
2568++
2569++from nova import exception
2570++from nova import flags
2571++from nova import log as logging
2572++from nova import network
2573++from nova.network import model as network_model
2574++
2575++
2576++FLAGS = flags.FLAGS
2577++LOG = logging.getLogger(__name__)
2578++
2579++
2580++def image_type(image_type):
2581++ """Converts to a three letter image type.
2582++
2583++ aki, kernel => aki
2584++ ari, ramdisk => ari
2585++ anything else => ami
2586++
2587++ """
2588++ if image_type == 'kernel':
2589++ return 'aki'
2590++ if image_type == 'ramdisk':
2591++ return 'ari'
2592++ if image_type not in ['aki', 'ari']:
2593++ return 'ami'
2594++ return image_type
2595++
2596++
2597++def ec2_id_to_id(ec2_id):
2598++ """Convert an ec2 ID (i-[base 16 number]) to an instance id (int)"""
2599++ try:
2600++ return int(ec2_id.split('-')[-1], 16)
2601++ except ValueError:
2602++ raise exception.InvalidEc2Id(ec2_id=ec2_id)
2603++
2604++
2605++def image_ec2_id(image_id, image_type='ami'):
2606++ """Returns image ec2_id using id and three letter type."""
2607++ template = image_type + '-%08x'
2608++ try:
2609++ return id_to_ec2_id(image_id, template=template)
2610++ except ValueError:
2611++ #TODO(wwolf): once we have ec2_id -> glance_id mapping
2612++ # in place, this wont be necessary
2613++ return "ami-00000000"
2614++
2615++
2616++def get_ip_info_for_instance_from_nw_info(nw_info):
2617++ ip_info = dict(fixed_ips=[], fixed_ip6s=[], floating_ips=[])
2618++ for vif in nw_info:
2619++ vif_fixed_ips = vif.fixed_ips()
2620++
2621++ fixed_ips = [ip['address']
2622++ for ip in vif_fixed_ips if ip['version'] == 4]
2623++ fixed_ip6s = [ip['address']
2624++ for ip in vif_fixed_ips if ip['version'] == 6]
2625++ floating_ips = [ip['address']
2626++ for ip in vif.floating_ips()]
2627++ ip_info['fixed_ips'].extend(fixed_ips)
2628++ ip_info['fixed_ip6s'].extend(fixed_ip6s)
2629++ ip_info['floating_ips'].extend(floating_ips)
2630++
2631++ return ip_info
2632++
2633++
2634++def get_ip_info_for_instance(context, instance):
2635++ """Return a dictionary of IP information for an instance"""
2636++
2637++ cached_nwinfo = instance['info_cache']['network_info']
2638++ # Make sure empty response is turned into []
2639++ if not cached_nwinfo:
2640++ cached_nwinfo = []
2641++ nw_info = network_model.NetworkInfo.hydrate(cached_nwinfo)
2642++ return get_ip_info_for_instance_from_nw_info(nw_info)
2643++
2644++
2645++def get_availability_zone_by_host(services, host):
2646++ if len(services) > 0:
2647++ return services[0]['availability_zone']
2648++ return 'unknown zone'
2649++
2650++
2651++def id_to_ec2_id(instance_id, template='i-%08x'):
2652++ """Convert an instance ID (int) to an ec2 ID (i-[base 16 number])"""
2653++ return template % int(instance_id)
2654++
2655++
2656++def id_to_ec2_snap_id(instance_id):
2657++ """Convert an snapshot ID (int) to an ec2 snapshot ID
2658++ (snap-[base 16 number])"""
2659++ return id_to_ec2_id(instance_id, 'snap-%08x')
2660++
2661++
2662++def id_to_ec2_vol_id(instance_id):
2663++ """Convert an volume ID (int) to an ec2 volume ID (vol-[base 16 number])"""
2664++ return id_to_ec2_id(instance_id, 'vol-%08x')
2665++
2666++
2667++_c2u = re.compile('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))')
2668++
2669++
2670++def camelcase_to_underscore(str):
2671++ return _c2u.sub(r'_\1', str).lower().strip('_')
2672++
2673++
2674++def _try_convert(value):
2675++ """Return a non-string from a string or unicode, if possible.
2676++
2677++ ============= =====================================================
2678++ When value is returns
2679++ ============= =====================================================
2680++ zero-length ''
2681++ 'None' None
2682++ 'True' True case insensitive
2683++ 'False' False case insensitive
2684++ '0', '-0' 0
2685++ 0xN, -0xN int from hex (positive) (N is any number)
2686++ 0bN, -0bN int from binary (positive) (N is any number)
2687++ * try conversion to int, float, complex, fallback value
2688++
2689++ """
2690++ if len(value) == 0:
2691++ return ''
2692++ if value == 'None':
2693++ return None
2694++ lowered_value = value.lower()
2695++ if lowered_value == 'true':
2696++ return True
2697++ if lowered_value == 'false':
2698++ return False
2699++ valueneg = value[1:] if value[0] == '-' else value
2700++ if valueneg == '0':
2701++ return 0
2702++ if valueneg == '':
2703++ return value
2704++ if valueneg[0] == '0':
2705++ if valueneg[1] in 'xX':
2706++ return int(value, 16)
2707++ elif valueneg[1] in 'bB':
2708++ return int(value, 2)
2709++ else:
2710++ try:
2711++ return int(value, 8)
2712++ except ValueError:
2713++ pass
2714++ try:
2715++ return int(value)
2716++ except ValueError:
2717++ pass
2718++ try:
2719++ return float(value)
2720++ except ValueError:
2721++ pass
2722++ try:
2723++ return complex(value)
2724++ except ValueError:
2725++ return value
2726++
2727++
2728++def dict_from_dotted_str(items):
2729++ """parse multi dot-separated argument into dict.
2730++ EBS boot uses multi dot-separated arguments like
2731++ BlockDeviceMapping.1.DeviceName=snap-id
2732++ Convert the above into
2733++ {'block_device_mapping': {'1': {'device_name': snap-id}}}
2734++ """
2735++ args = {}
2736++ for key, value in items:
2737++ parts = key.split(".")
2738++ key = str(camelcase_to_underscore(parts[0]))
2739++ if isinstance(value, str) or isinstance(value, unicode):
2740++ # NOTE(vish): Automatically convert strings back
2741++ # into their respective values
2742++ value = _try_convert(value)
2743++
2744++ if len(parts) > 1:
2745++ d = args.get(key, {})
2746++ args[key] = d
2747++ for k in parts[1:-1]:
2748++ k = camelcase_to_underscore(k)
2749++ v = d.get(k, {})
2750++ d[k] = v
2751++ d = v
2752++ d[camelcase_to_underscore(parts[-1])] = value
2753++ else:
2754++ args[key] = value
2755++
2756++ return args
2757+diff -Naurp nova.orig/api/ec2/faults.py nova/api/ec2/faults.py
2758+--- nova.orig/api/ec2/faults.py 1969-12-31 19:00:00.000000000 -0500
2759++++ nova/api/ec2/faults.py 2012-02-28 09:08:10.821887173 -0500
2760+@@ -0,0 +1,64 @@
2761++# vim: tabstop=4 shiftwidth=4 softtabstop=4
2762++
2763++# Licensed under the Apache License, Version 2.0 (the "License"); you may
2764++# not use this file except in compliance with the License. You may obtain
2765++# a copy of the License at
2766++#
2767++# http://www.apache.org/licenses/LICENSE-2.0
2768++#
2769++# Unless required by applicable law or agreed to in writing, software
2770++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2771++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2772++# License for the specific language governing permissions and limitations
2773++# under the License.
2774++
2775++import webob.dec
2776++import webob.exc
2777++
2778++from nova import context
2779++from nova import flags
2780++from nova import utils
2781++
2782++FLAGS = flags.FLAGS
2783++
2784++
2785++class Fault(webob.exc.HTTPException):
2786++ """Captures exception and return REST Response."""
2787++
2788++ def __init__(self, exception):
2789++ """Create a response for the given webob.exc.exception."""
2790++ self.wrapped_exc = exception
2791++
2792++ @webob.dec.wsgify
2793++ def __call__(self, req):
2794++ """Generate a WSGI response based on the exception passed to ctor."""
2795++ code = self.wrapped_exc.status_int
2796++ message = self.wrapped_exc.explanation
2797++
2798++ if code == 501:
2799++ message = "The requested function is not supported"
2800++ code = str(code)
2801++
2802++ if 'AWSAccessKeyId' not in req.params:
2803++ raise webob.exc.HTTPBadRequest()
2804++ user_id, _sep, project_id = req.params['AWSAccessKeyId'].partition(':')
2805++ project_id = project_id or user_id
2806++ remote_address = getattr(req, 'remote_address', '127.0.0.1')
2807++ if FLAGS.use_forwarded_for:
2808++ remote_address = req.headers.get('X-Forwarded-For', remote_address)
2809++
2810++ ctxt = context.RequestContext(user_id,
2811++ project_id,
2812++ remote_address=remote_address)
2813++
2814++ resp = webob.Response()
2815++ resp.status = self.wrapped_exc.status_int
2816++ resp.headers['Content-Type'] = 'text/xml'
2817++ resp.body = str('<?xml version="1.0"?>\n'
2818++ '<Response><Errors><Error><Code>%s</Code>'
2819++ '<Message>%s</Message></Error></Errors>'
2820++ '<RequestID>%s</RequestID></Response>' %
2821++ (utils.utf8(code), utils.utf8(message),
2822++ utils.utf8(ctxt.request_id)))
2823++
2824++ return resp
2825+diff -Naurp nova.orig/api/ec2/__init__.py nova/api/ec2/__init__.py
2826+--- nova.orig/api/ec2/__init__.py 1969-12-31 19:00:00.000000000 -0500
2827++++ nova/api/ec2/__init__.py 2012-02-28 09:08:10.821887173 -0500
2828+@@ -0,0 +1,651 @@
2829++# vim: tabstop=4 shiftwidth=4 softtabstop=4
2830++
2831++# Copyright 2010 United States Government as represented by the
2832++# Administrator of the National Aeronautics and Space Administration.
2833++# All Rights Reserved.
2834++#
2835++# Licensed under the Apache License, Version 2.0 (the "License"); you may
2836++# not use this file except in compliance with the License. You may obtain
2837++# a copy of the License at
2838++#
2839++# http://www.apache.org/licenses/LICENSE-2.0
2840++#
2841++# Unless required by applicable law or agreed to in writing, software
2842++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
2843++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
2844++# License for the specific language governing permissions and limitations
2845++# under the License.
2846++"""
2847++Starting point for routing EC2 requests.
2848++
2849++"""
2850++
2851++import urlparse
2852++
2853++from eventlet.green import httplib
2854++import webob
2855++import webob.dec
2856++import webob.exc
2857++
2858++from nova.api.ec2 import apirequest
2859++from nova.api.ec2 import ec2utils
2860++from nova.api.ec2 import faults
2861++from nova.api import validator
2862++from nova.auth import manager
2863++from nova import context
2864++from nova import exception
2865++from nova import flags
2866++from nova import log as logging
2867++from nova.openstack.common import cfg
2868++from nova import utils
2869++from nova import wsgi
2870++
2871++
2872++LOG = logging.getLogger(__name__)
2873++
2874++ec2_opts = [
2875++ cfg.IntOpt('lockout_attempts',
2876++ default=5,
2877++ help='Number of failed auths before lockout.'),
2878++ cfg.IntOpt('lockout_minutes',
2879++ default=15,
2880++ help='Number of minutes to lockout if triggered.'),
2881++ cfg.IntOpt('lockout_window',
2882++ default=15,
2883++ help='Number of minutes for lockout window.'),
2884++ cfg.StrOpt('keystone_ec2_url',
2885++ default='http://localhost:5000/v2.0/ec2tokens',
2886++ help='URL to get token from ec2 request.'),
2887++ ]
2888++
2889++FLAGS = flags.FLAGS
2890++FLAGS.register_opts(ec2_opts)
2891++
2892++flags.DECLARE('use_forwarded_for', 'nova.api.auth')
2893++
2894++
2895++def ec2_error(req, request_id, code, message):
2896++ """Helper to send an ec2_compatible error"""
2897++ LOG.error(_('%(code)s: %(message)s') % locals())
2898++ resp = webob.Response()
2899++ resp.status = 400
2900++ resp.headers['Content-Type'] = 'text/xml'
2901++ resp.body = str('<?xml version="1.0"?>\n'
2902++ '<Response><Errors><Error><Code>%s</Code>'
2903++ '<Message>%s</Message></Error></Errors>'
2904++ '<RequestID>%s</RequestID></Response>' %
2905++ (utils.utf8(code), utils.utf8(message),
2906++ utils.utf8(request_id)))
2907++ return resp
2908++
2909++
2910++## Fault Wrapper around all EC2 requests ##
2911++class FaultWrapper(wsgi.Middleware):
2912++ """Calls the middleware stack, captures any exceptions into faults."""
2913++
2914++ @webob.dec.wsgify(RequestClass=wsgi.Request)
2915++ def __call__(self, req):
2916++ try:
2917++ return req.get_response(self.application)
2918++ except Exception as ex:
2919++ LOG.exception(_("FaultWrapper: %s"), unicode(ex))
2920++ return faults.Fault(webob.exc.HTTPInternalServerError())
2921++
2922++
2923++class RequestLogging(wsgi.Middleware):
2924++ """Access-Log akin logging for all EC2 API requests."""
2925++
2926++ @webob.dec.wsgify(RequestClass=wsgi.Request)
2927++ def __call__(self, req):
2928++ start = utils.utcnow()
2929++ rv = req.get_response(self.application)
2930++ self.log_request_completion(rv, req, start)
2931++ return rv
2932++
2933++ def log_request_completion(self, response, request, start):
2934++ apireq = request.environ.get('ec2.request', None)
2935++ if apireq:
2936++ controller = apireq.controller
2937++ action = apireq.action
2938++ else:
2939++ controller = None
2940++ action = None
2941++ ctxt = request.environ.get('nova.context', None)
2942++ delta = utils.utcnow() - start
2943++ seconds = delta.seconds
2944++ microseconds = delta.microseconds
2945++ LOG.info(
2946++ "%s.%ss %s %s %s %s:%s %s [%s] %s %s",
2947++ seconds,
2948++ microseconds,
2949++ request.remote_addr,
2950++ request.method,
2951++ "%s%s" % (request.script_name, request.path_info),
2952++ controller,
2953++ action,
2954++ response.status_int,
2955++ request.user_agent,
2956++ request.content_type,
2957++ response.content_type,
2958++ context=ctxt)
2959++
2960++
2961++class Lockout(wsgi.Middleware):
2962++ """Lockout for x minutes on y failed auths in a z minute period.
2963++
2964++ x = lockout_timeout flag
2965++ y = lockout_window flag
2966++ z = lockout_attempts flag
2967++
2968++ Uses memcached if lockout_memcached_servers flag is set, otherwise it
2969++ uses a very simple in-process cache. Due to the simplicity of
2970++ the implementation, the timeout window is started with the first
2971++ failed request, so it will block if there are x failed logins within
2972++ that period.
2973++
2974++ There is a possible race condition where simultaneous requests could
2975++ sneak in before the lockout hits, but this is extremely rare and would
2976++ only result in a couple of extra failed attempts."""
2977++
2978++ def __init__(self, application):
2979++ """middleware can use fake for testing."""
2980++ if FLAGS.memcached_servers:
2981++ import memcache
2982++ else:
2983++ from nova.testing.fake import memcache
2984++ self.mc = memcache.Client(FLAGS.memcached_servers,
2985++ debug=0)
2986++ super(Lockout, self).__init__(application)
2987++
2988++ @webob.dec.wsgify(RequestClass=wsgi.Request)
2989++ def __call__(self, req):
2990++ access_key = str(req.params['AWSAccessKeyId'])
2991++ failures_key = "authfailures-%s" % access_key
2992++ failures = int(self.mc.get(failures_key) or 0)
2993++ if failures >= FLAGS.lockout_attempts:
2994++ detail = _("Too many failed authentications.")
2995++ raise webob.exc.HTTPForbidden(detail=detail)
2996++ res = req.get_response(self.application)
2997++ if res.status_int == 403:
2998++ failures = self.mc.incr(failures_key)
2999++ if failures is None:
3000++ # NOTE(vish): To use incr, failures has to be a string.
3001++ self.mc.set(failures_key, '1', time=FLAGS.lockout_window * 60)
3002++ elif failures >= FLAGS.lockout_attempts:
3003++ lock_mins = FLAGS.lockout_minutes
3004++ msg = _('Access key %(access_key)s has had %(failures)d'
3005++ ' failed authentications and will be locked out'
3006++ ' for %(lock_mins)d minutes.') % locals()
3007++ LOG.warn(msg)
3008++ self.mc.set(failures_key, str(failures),
3009++ time=FLAGS.lockout_minutes * 60)
3010++ return res
3011++
3012++
3013++class EC2Token(wsgi.Middleware):
3014++ """Deprecated, only here to make merging easier."""
3015++
3016++ @webob.dec.wsgify(RequestClass=wsgi.Request)
3017++ def __call__(self, req):
3018++ # Read request signature and access id.
3019++ try:
3020++ signature = req.params['Signature']
3021++ access = req.params['AWSAccessKeyId']
3022++ except KeyError, e:
3023++ LOG.exception(e)
3024++ raise webob.exc.HTTPBadRequest()
3025++
3026++ # Make a copy of args for authentication and signature verification.
3027++ auth_params = dict(req.params)
3028++ # Not part of authentication args
3029++ auth_params.pop('Signature')
3030++
3031++ if "ec2" in FLAGS.keystone_ec2_url:
3032++ LOG.warning("Configuration setting for keystone_ec2_url needs "
3033++ "to be updated to /tokens only. The /ec2 prefix is "
3034++ "being deprecated")
3035++ # Authenticate the request.
3036++ creds = {'ec2Credentials': {'access': access,
3037++ 'signature': signature,
3038++ 'host': req.host,
3039++ 'verb': req.method,
3040++ 'path': req.path,
3041++ 'params': auth_params,
3042++ }}
3043++ else:
3044++ # Authenticate the request.
3045++ creds = {'auth': {'OS-KSEC2:ec2Credentials': {'access': access,
3046++ 'signature': signature,
3047++ 'host': req.host,
3048++ 'verb': req.method,
3049++ 'path': req.path,
3050++ 'params': auth_params,
3051++ }}}
3052++ creds_json = utils.dumps(creds)
3053++ headers = {'Content-Type': 'application/json'}
3054++
3055++ # Disable "has no x member" pylint error
3056++ # for httplib and urlparse
3057++ # pylint: disable-msg=E1101
3058++ o = urlparse.urlparse(FLAGS.keystone_ec2_url)
3059++ if o.scheme == "http":
3060++ conn = httplib.HTTPConnection(o.netloc)
3061++ else:
3062++ conn = httplib.HTTPSConnection(o.netloc)
3063++ conn.request('POST', o.path, body=creds_json, headers=headers)
3064++ response = conn.getresponse().read()
3065++ conn.close()
3066++
3067++ # NOTE(vish): We could save a call to keystone by
3068++ # having keystone return token, tenant,
3069++ # user, and roles from this call.
3070++
3071++ result = utils.loads(response)
3072++ try:
3073++ token_id = result['access']['token']['id']
3074++ except (AttributeError, KeyError), e:
3075++ LOG.exception(e)
3076++ raise webob.exc.HTTPBadRequest()
3077++
3078++ # Authenticated!
3079++ req.headers['X-Auth-Token'] = token_id
3080++ return self.application
3081++
3082++
3083++class EC2KeystoneAuth(wsgi.Middleware):
3084++ """Authenticate an EC2 request with keystone and convert to context."""
3085++
3086++ @webob.dec.wsgify(RequestClass=wsgi.Request)
3087++ def __call__(self, req):
3088++ request_id = context.generate_request_id()
3089++ signature = req.params.get('Signature')
3090++ if not signature:
3091++ msg = _("Signature not provided")
3092++ return ec2_error(req, request_id, "Unauthorized", msg)
3093++ access = req.params.get('AWSAccessKeyId')
3094++ if not access:
3095++ msg = _("Access key not provided")
3096++ return ec2_error(req, request_id, "Unauthorized", msg)
3097++
3098++ # Make a copy of args for authentication and signature verification.
3099++ auth_params = dict(req.params)
3100++ # Not part of authentication args
3101++ auth_params.pop('Signature')
3102++
3103++ cred_dict = {
3104++ 'access': access,
3105++ 'signature': signature,
3106++ 'host': req.host,
3107++ 'verb': req.method,
3108++ 'path': req.path,
3109++ 'params': auth_params,
3110++ }
3111++ if "ec2" in FLAGS.keystone_ec2_url:
3112++ creds = {'ec2Credentials': cred_dict}
3113++ else:
3114++ creds = {'auth': {'OS-KSEC2:ec2Credentials': cred_dict}}
3115++ creds_json = utils.dumps(creds)
3116++ headers = {'Content-Type': 'application/json'}
3117++
3118++ o = urlparse.urlparse(FLAGS.keystone_ec2_url)
3119++ if o.scheme == "http":
3120++ conn = httplib.HTTPConnection(o.netloc)
3121++ else:
3122++ conn = httplib.HTTPSConnection(o.netloc)
3123++ conn.request('POST', o.path, body=creds_json, headers=headers)
3124++ response = conn.getresponse()
3125++ data = response.read()
3126++ if response.status != 200:
3127++ if response.status == 401:
3128++ msg = response.reason
3129++ else:
3130++ msg = _("Failure communicating with keystone")
3131++ return ec2_error(req, request_id, "Unauthorized", msg)
3132++ result = utils.loads(data)
3133++ conn.close()
3134++
3135++ try:
3136++ token_id = result['access']['token']['id']
3137++ user_id = result['access']['user']['id']
3138++ project_id = result['access']['token']['tenant']['id']
3139++ roles = [role['name'] for role
3140++ in result['access']['user']['roles']]
3141++ except (AttributeError, KeyError), e:
3142++ LOG.exception("Keystone failure: %s" % e)
3143++ msg = _("Failure communicating with keystone")
3144++ return ec2_error(req, request_id, "Unauthorized", msg)
3145++
3146++ remote_address = req.remote_addr
3147++ if FLAGS.use_forwarded_for:
3148++ remote_address = req.headers.get('X-Forwarded-For',
3149++ remote_address)
3150++ ctxt = context.RequestContext(user_id,
3151++ project_id,
3152++ roles=roles,
3153++ auth_token=token_id,
3154++ strategy='keystone',
3155++ remote_address=remote_address)
3156++
3157++ req.environ['nova.context'] = ctxt
3158++
3159++ return self.application
3160++
3161++
3162++class NoAuth(wsgi.Middleware):
3163++ """Add user:project as 'nova.context' to WSGI environ."""
3164++
3165++ @webob.dec.wsgify(RequestClass=wsgi.Request)
3166++ def __call__(self, req):
3167++ if 'AWSAccessKeyId' not in req.params:
3168++ raise webob.exc.HTTPBadRequest()
3169++ user_id, _sep, project_id = req.params['AWSAccessKeyId'].partition(':')
3170++ project_id = project_id or user_id
3171++ remote_address = req.remote_addr
3172++ if FLAGS.use_forwarded_for:
3173++ remote_address = req.headers.get('X-Forwarded-For', remote_address)
3174++ ctx = context.RequestContext(user_id,
3175++ project_id,
3176++ is_admin=True,
3177++ remote_address=remote_address)
3178++
3179++ req.environ['nova.context'] = ctx
3180++ return self.application
3181++
3182++
3183++class Authenticate(wsgi.Middleware):
3184++ """Authenticate an EC2 request and add 'nova.context' to WSGI environ."""
3185++
3186++ @webob.dec.wsgify(RequestClass=wsgi.Request)
3187++ def __call__(self, req):
3188++ # Read request signature and access id.
3189++ try:
3190++ signature = req.params['Signature']
3191++ access = req.params['AWSAccessKeyId']
3192++ except KeyError:
3193++ raise webob.exc.HTTPBadRequest()
3194++
3195++ # Make a copy of args for authentication and signature verification.
3196++ auth_params = dict(req.params)
3197++ # Not part of authentication args
3198++ auth_params.pop('Signature')
3199++
3200++ # Authenticate the request.
3201++ authman = manager.AuthManager()
3202++ try:
3203++ (user, project) = authman.authenticate(
3204++ access,
3205++ signature,
3206++ auth_params,
3207++ req.method,
3208++ req.host,
3209++ req.path)
3210++ # Be explicit for what exceptions are 403, the rest bubble as 500
3211++ except (exception.NotFound, exception.NotAuthorized,
3212++ exception.InvalidSignature) as ex:
3213++ LOG.audit(_("Authentication Failure: %s"), unicode(ex))
3214++ raise webob.exc.HTTPForbidden()
3215++
3216++ # Authenticated!
3217++ remote_address = req.remote_addr
3218++ if FLAGS.use_forwarded_for:
3219++ remote_address = req.headers.get('X-Forwarded-For', remote_address)
3220++ roles = authman.get_active_roles(user, project)
3221++ ctxt = context.RequestContext(user_id=user.id,
3222++ project_id=project.id,
3223++ is_admin=user.is_admin(),
3224++ roles=roles,
3225++ remote_address=remote_address)
3226++ req.environ['nova.context'] = ctxt
3227++ uname = user.name
3228++ pname = project.name
3229++ msg = _('Authenticated Request For %(uname)s:%(pname)s)') % locals()
3230++ LOG.audit(msg, context=req.environ['nova.context'])
3231++ return self.application
3232++
3233++
3234++class Requestify(wsgi.Middleware):
3235++
3236++ def __init__(self, app, controller):
3237++ super(Requestify, self).__init__(app)
3238++ self.controller = utils.import_class(controller)()
3239++
3240++ @webob.dec.wsgify(RequestClass=wsgi.Request)
3241++ def __call__(self, req):
3242++ non_args = ['Action', 'Signature', 'AWSAccessKeyId', 'SignatureMethod',
3243++ 'SignatureVersion', 'Version', 'Timestamp']
3244++ args = dict(req.params)
3245++ try:
3246++ # Raise KeyError if omitted
3247++ action = req.params['Action']
3248++ # Fix bug lp:720157 for older (version 1) clients
3249++ version = req.params['SignatureVersion']
3250++ if int(version) == 1:
3251++ non_args.remove('SignatureMethod')
3252++ if 'SignatureMethod' in args:
3253++ args.pop('SignatureMethod')
3254++ for non_arg in non_args:
3255++ # Remove, but raise KeyError if omitted
3256++ args.pop(non_arg)
3257++ except KeyError, e:
3258++ raise webob.exc.HTTPBadRequest()
3259++
3260++ LOG.debug(_('action: %s'), action)
3261++ for key, value in args.items():
3262++ LOG.debug(_('arg: %(key)s\t\tval: %(value)s') % locals())
3263++
3264++ # Success!
3265++ api_request = apirequest.APIRequest(self.controller, action,
3266++ req.params['Version'], args)
3267++ req.environ['ec2.request'] = api_request
3268++ return self.application
3269++
3270++
3271++class Authorizer(wsgi.Middleware):
3272++
3273++ """Authorize an EC2 API request.
3274++
3275++ Return a 401 if ec2.controller and ec2.action in WSGI environ may not be
3276++ executed in nova.context.
3277++ """
3278++
3279++ def __init__(self, application):
3280++ super(Authorizer, self).__init__(application)
3281++ self.action_roles = {
3282++ 'CloudController': {
3283++ 'DescribeAvailabilityZones': ['all'],
3284++ 'DescribeRegions': ['all'],
3285++ 'DescribeSnapshots': ['all'],
3286++ 'DescribeKeyPairs': ['all'],
3287++ 'CreateKeyPair': ['all'],
3288++ 'DeleteKeyPair': ['all'],
3289++ 'DescribeSecurityGroups': ['all'],
3290++ 'ImportKeyPair': ['all'],
3291++ 'AuthorizeSecurityGroupIngress': ['netadmin'],
3292++ 'RevokeSecurityGroupIngress': ['netadmin'],
3293++ 'CreateSecurityGroup': ['netadmin'],
3294++ 'DeleteSecurityGroup': ['netadmin'],
3295++ 'GetConsoleOutput': ['projectmanager', 'sysadmin'],
3296++ 'DescribeVolumes': ['projectmanager', 'sysadmin'],
3297++ 'CreateVolume': ['projectmanager', 'sysadmin'],
3298++ 'AttachVolume': ['projectmanager', 'sysadmin'],
3299++ 'DetachVolume': ['projectmanager', 'sysadmin'],
3300++ 'DescribeInstances': ['all'],
3301++ 'DescribeAddresses': ['all'],
3302++ 'AllocateAddress': ['netadmin'],
3303++ 'ReleaseAddress': ['netadmin'],
3304++ 'AssociateAddress': ['netadmin'],
3305++ 'DisassociateAddress': ['netadmin'],
3306++ 'RunInstances': ['projectmanager', 'sysadmin'],
3307++ 'TerminateInstances': ['projectmanager', 'sysadmin'],
3308++ 'RebootInstances': ['projectmanager', 'sysadmin'],
3309++ 'UpdateInstance': ['projectmanager', 'sysadmin'],
3310++ 'StartInstances': ['projectmanager', 'sysadmin'],
3311++ 'StopInstances': ['projectmanager', 'sysadmin'],
3312++ 'DeleteVolume': ['projectmanager', 'sysadmin'],
3313++ 'DescribeImages': ['all'],
3314++ 'DeregisterImage': ['projectmanager', 'sysadmin'],
3315++ 'RegisterImage': ['projectmanager', 'sysadmin'],
3316++ 'DescribeImageAttribute': ['all'],
3317++ 'ModifyImageAttribute': ['projectmanager', 'sysadmin'],
3318++ 'UpdateImage': ['projectmanager', 'sysadmin'],
3319++ 'CreateImage': ['projectmanager', 'sysadmin'],
3320++ },
3321++ 'AdminController': {
3322++ # All actions have the same permission: ['none'] (the default)
3323++ # superusers will be allowed to run them
3324++ # all others will get HTTPUnauthorized.
3325++ },
3326++ }
3327++
3328++ @webob.dec.wsgify(RequestClass=wsgi.Request)
3329++ def __call__(self, req):
3330++ context = req.environ['nova.context']
3331++ controller = req.environ['ec2.request'].controller.__class__.__name__
3332++ action = req.environ['ec2.request'].action
3333++ allowed_roles = self.action_roles[controller].get(action, ['none'])
3334++ if self._matches_any_role(context, allowed_roles):
3335++ return self.application
3336++ else:
3337++ LOG.audit(_('Unauthorized request for controller=%(controller)s '
3338++ 'and action=%(action)s') % locals(), context=context)
3339++ raise webob.exc.HTTPUnauthorized()
3340++
3341++ def _matches_any_role(self, context, roles):
3342++ """Return True if any role in roles is allowed in context."""
3343++ if context.is_admin:
3344++ return True
3345++ if 'all' in roles:
3346++ return True
3347++ if 'none' in roles:
3348++ return False
3349++ return any(role in context.roles for role in roles)
3350++
3351++
3352++class Validator(wsgi.Middleware):
3353++
3354++ def validate_ec2_id(val):
3355++ if not validator.validate_str()(val):
3356++ return False
3357++ try:
3358++ ec2utils.ec2_id_to_id(val)
3359++ except exception.InvalidEc2Id:
3360++ return False
3361++ return True
3362++
3363++ validator.validate_ec2_id = validate_ec2_id
3364++
3365++ validator.DEFAULT_VALIDATOR = {
3366++ 'instance_id': validator.validate_ec2_id,
3367++ 'volume_id': validator.validate_ec2_id,
3368++ 'image_id': validator.validate_ec2_id,
3369++ 'attribute': validator.validate_str(),
3370++ 'image_location': validator.validate_image_path,
3371++ 'public_ip': validator.validate_ipv4,
3372++ 'region_name': validator.validate_str(),
3373++ 'group_name': validator.validate_str(max_length=255),
3374++ 'group_description': validator.validate_str(max_length=255),
3375++ 'size': validator.validate_int(),
3376++ 'user_data': validator.validate_user_data
3377++ }
3378++
3379++ def __init__(self, application):
3380++ super(Validator, self).__init__(application)
3381++
3382++ @webob.dec.wsgify(RequestClass=wsgi.Request)
3383++ def __call__(self, req):
3384++ if validator.validate(req.environ['ec2.request'].args,
3385++ validator.DEFAULT_VALIDATOR):
3386++ return self.application
3387++ else:
3388++ raise webob.exc.HTTPBadRequest()
3389++
3390++
3391++class Executor(wsgi.Application):
3392++
3393++ """Execute an EC2 API request.
3394++
3395++ Executes 'ec2.action' upon 'ec2.controller', passing 'nova.context' and
3396++ 'ec2.action_args' (all variables in WSGI environ.) Returns an XML
3397++ response, or a 400 upon failure.
3398++ """
3399++
3400++ @webob.dec.wsgify(RequestClass=wsgi.Request)
3401++ def __call__(self, req):
3402++ context = req.environ['nova.context']
3403++ request_id = context.request_id
3404++ api_request = req.environ['ec2.request']
3405++ result = None
3406++ try:
3407++ result = api_request.invoke(context)
3408++ except exception.InstanceNotFound as ex:
3409++ LOG.info(_('InstanceNotFound raised: %s'), unicode(ex),
3410++ context=context)
3411++ ec2_id = ec2utils.id_to_ec2_id(ex.kwargs['instance_id'])
3412++ message = ex.message % {'instance_id': ec2_id}
3413++ return ec2_error(req, request_id, type(ex).__name__, message)
3414++ except exception.VolumeNotFound as ex:
3415++ LOG.info(_('VolumeNotFound raised: %s'), unicode(ex),
3416++ context=context)
3417++ ec2_id = ec2utils.id_to_ec2_vol_id(ex.kwargs['volume_id'])
3418++ message = ex.message % {'volume_id': ec2_id}
3419++ return ec2_error(req, request_id, type(ex).__name__, message)
3420++ except exception.SnapshotNotFound as ex:
3421++ LOG.info(_('SnapshotNotFound raised: %s'), unicode(ex),
3422++ context=context)
3423++ ec2_id = ec2utils.id_to_ec2_snap_id(ex.kwargs['snapshot_id'])
3424++ message = ex.message % {'snapshot_id': ec2_id}
3425++ return ec2_error(req, request_id, type(ex).__name__, message)
3426++ except exception.NotFound as ex:
3427++ LOG.info(_('NotFound raised: %s'), unicode(ex), context=context)
3428++ return ec2_error(req, request_id, type(ex).__name__, unicode(ex))
3429++ except exception.EC2APIError as ex:
3430++ LOG.exception(_('EC2APIError raised: %s'), unicode(ex),
3431++ context=context)
3432++ if ex.code:
3433++ return ec2_error(req, request_id, ex.code, unicode(ex))
3434++ else:
3435++ return ec2_error(req, request_id, type(ex).__name__,
3436++ unicode(ex))
3437++ except exception.KeyPairExists as ex:
3438++ LOG.debug(_('KeyPairExists raised: %s'), unicode(ex),
3439++ context=context)
3440++ return ec2_error(req, request_id, type(ex).__name__, unicode(ex))
3441++ except exception.InvalidParameterValue as ex:
3442++ LOG.debug(_('InvalidParameterValue raised: %s'), unicode(ex),
3443++ context=context)
3444++ return ec2_error(req, request_id, type(ex).__name__, unicode(ex))
3445++ except exception.InvalidPortRange as ex:
3446++ LOG.debug(_('InvalidPortRange raised: %s'), unicode(ex),
3447++ context=context)
3448++ return ec2_error(req, request_id, type(ex).__name__, unicode(ex))
3449++ except exception.NotAuthorized as ex:
3450++ LOG.info(_('NotAuthorized raised: %s'), unicode(ex),
3451++ context=context)
3452++ return ec2_error(req, request_id, type(ex).__name__, unicode(ex))
3453++ except exception.InvalidRequest as ex:
3454++ LOG.debug(_('InvalidRequest raised: %s'), unicode(ex),
3455++ context=context)
3456++ return ec2_error(req, request_id, type(ex).__name__, unicode(ex))
3457++ except exception.InvalidInstanceIDMalformed as ex:
3458++ LOG.debug(_('ValidatorError raised: %s'), unicode(ex),
3459++ context=context)
3460++ #EC2 Compatibility
3461++ return self._error(req, context, "InvalidInstanceID.Malformed",
3462++ unicode(ex))
3463++ except Exception as ex:
3464++ env = req.environ.copy()
3465++ for k in env.keys():
3466++ if not isinstance(env[k], basestring):
3467++ env.pop(k)
3468++
3469++ LOG.exception(_('Unexpected error raised: %s'), unicode(ex))
3470++ LOG.error(_('Environment: %s') % utils.dumps(env))
3471++ return ec2_error(req, request_id, 'UnknownError',
3472++ _('An unknown error has occurred. '
3473++ 'Please try your request again.'))
3474++ else:
3475++ resp = webob.Response()
3476++ resp.status = 200
3477++ resp.headers['Content-Type'] = 'text/xml'
3478++ resp.body = str(result)
3479++ return resp
3480+diff -Naurp nova.orig/api/ec2/inst_state.py nova/api/ec2/inst_state.py
3481+--- nova.orig/api/ec2/inst_state.py 1969-12-31 19:00:00.000000000 -0500
3482++++ nova/api/ec2/inst_state.py 2012-02-28 09:08:10.821887173 -0500
3483+@@ -0,0 +1,60 @@
3484++# vim: tabstop=4 shiftwidth=4 softtabstop=4
3485++
3486++# Copyright 2011 Isaku Yamahata <yamahata at valinux co jp>
3487++# All Rights Reserved.
3488++#
3489++# Licensed under the Apache License, Version 2.0 (the "License"); you may
3490++# not use this file except in compliance with the License. You may obtain
3491++# a copy of the License at
3492++#
3493++# http://www.apache.org/licenses/LICENSE-2.0
3494++#
3495++# Unless required by applicable law or agreed to in writing, software
3496++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3497++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3498++# License for the specific language governing permissions and limitations
3499++# under the License.
3500++
3501++PENDING_CODE = 0
3502++RUNNING_CODE = 16
3503++SHUTTING_DOWN_CODE = 32
3504++TERMINATED_CODE = 48
3505++STOPPING_CODE = 64
3506++STOPPED_CODE = 80
3507++
3508++PENDING = 'pending'
3509++RUNNING = 'running'
3510++SHUTTING_DOWN = 'shutting-down'
3511++TERMINATED = 'terminated'
3512++STOPPING = 'stopping'
3513++STOPPED = 'stopped'
3514++
3515++# non-ec2 value
3516++SHUTOFF = 'shutoff'
3517++MIGRATE = 'migrate'
3518++RESIZE = 'resize'
3519++PAUSE = 'pause'
3520++SUSPEND = 'suspend'
3521++RESCUE = 'rescue'
3522++
3523++# EC2 API instance status code
3524++_NAME_TO_CODE = {
3525++ PENDING: PENDING_CODE,
3526++ RUNNING: RUNNING_CODE,
3527++ SHUTTING_DOWN: SHUTTING_DOWN_CODE,
3528++ TERMINATED: TERMINATED_CODE,
3529++ STOPPING: STOPPING_CODE,
3530++ STOPPED: STOPPED_CODE,
3531++
3532++ # approximation
3533++ SHUTOFF: TERMINATED_CODE,
3534++ MIGRATE: RUNNING_CODE,
3535++ RESIZE: RUNNING_CODE,
3536++ PAUSE: STOPPED_CODE,
3537++ SUSPEND: STOPPED_CODE,
3538++ RESCUE: RUNNING_CODE,
3539++}
3540++
3541++
3542++def name_to_code(name):
3543++ return _NAME_TO_CODE.get(name, PENDING_CODE)
3544+diff -Naurp nova.orig/api/__init__.py nova/api/__init__.py
3545+--- nova.orig/api/__init__.py 1969-12-31 19:00:00.000000000 -0500
3546++++ nova/api/__init__.py 2012-02-28 09:08:10.817887173 -0500
3547+@@ -0,0 +1,17 @@
3548++# vim: tabstop=4 shiftwidth=4 softtabstop=4
3549++
3550++# Copyright 2010 United States Government as represented by the
3551++# Administrator of the National Aeronautics and Space Administration.
3552++# All Rights Reserved.
3553++#
3554++# Licensed under the Apache License, Version 2.0 (the "License"); you may
3555++# not use this file except in compliance with the License. You may obtain
3556++# a copy of the License at
3557++#
3558++# http://www.apache.org/licenses/LICENSE-2.0
3559++#
3560++# Unless required by applicable law or agreed to in writing, software
3561++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3562++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3563++# License for the specific language governing permissions and limitations
3564++# under the License.
3565+diff -Naurp nova.orig/api/manager.py nova/api/manager.py
3566+--- nova.orig/api/manager.py 1969-12-31 19:00:00.000000000 -0500
3567++++ nova/api/manager.py 2012-02-28 09:08:10.821887173 -0500
3568+@@ -0,0 +1,42 @@
3569++# vim: tabstop=4 shiftwidth=4 softtabstop=4
3570++
3571++# Copyright 2010 United States Government as represented by the
3572++# Administrator of the National Aeronautics and Space Administration.
3573++# All Rights Reserved.
3574++#
3575++# Licensed under the Apache License, Version 2.0 (the "License"); you may
3576++# not use this file except in compliance with the License. You may obtain
3577++# a copy of the License at
3578++#
3579++# http://www.apache.org/licenses/LICENSE-2.0
3580++#
3581++# Unless required by applicable law or agreed to in writing, software
3582++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3583++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3584++# License for the specific language governing permissions and limitations
3585++# under the License.
3586++
3587++from nova import flags
3588++from nova import manager
3589++from nova import utils
3590++
3591++FLAGS = flags.FLAGS
3592++
3593++
3594++class MetadataManager(manager.Manager):
3595++ """Metadata Manager.
3596++
3597++ This class manages the Metadata API service initialization. Currently, it
3598++ just adds an iptables filter rule for the metadata service.
3599++ """
3600++ def __init__(self, *args, **kwargs):
3601++ super(MetadataManager, self).__init__(*args, **kwargs)
3602++ self.network_driver = utils.import_object(FLAGS.network_driver)
3603++
3604++ def init_host(self):
3605++ """Perform any initialization.
3606++
3607++ Currently, we only add an iptables filter rule for the metadata
3608++ service.
3609++ """
3610++ self.network_driver.metadata_accept()
3611+diff -Naurp nova.orig/api/metadata/handler.py nova/api/metadata/handler.py
3612+--- nova.orig/api/metadata/handler.py 1969-12-31 19:00:00.000000000 -0500
3613++++ nova/api/metadata/handler.py 2012-02-28 09:08:10.821887173 -0500
3614+@@ -0,0 +1,260 @@
3615++# vim: tabstop=4 shiftwidth=4 softtabstop=4
3616++
3617++# Copyright 2010 United States Government as represented by the
3618++# Administrator of the National Aeronautics and Space Administration.
3619++# All Rights Reserved.
3620++#
3621++# Licensed under the Apache License, Version 2.0 (the "License"); you may
3622++# not use this file except in compliance with the License. You may obtain
3623++# a copy of the License at
3624++#
3625++# http://www.apache.org/licenses/LICENSE-2.0
3626++#
3627++# Unless required by applicable law or agreed to in writing, software
3628++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3629++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3630++# License for the specific language governing permissions and limitations
3631++# under the License.
3632++
3633++"""Metadata request handler."""
3634++
3635++import base64
3636++
3637++import webob.dec
3638++import webob.exc
3639++
3640++from nova.api.ec2 import ec2utils
3641++from nova import block_device
3642++from nova import compute
3643++from nova import context
3644++from nova import db
3645++from nova import exception
3646++from nova import flags
3647++from nova import log as logging
3648++from nova import network
3649++from nova import volume
3650++from nova import wsgi
3651++
3652++
3653++LOG = logging.getLogger(__name__)
3654++FLAGS = flags.FLAGS
3655++flags.DECLARE('use_forwarded_for', 'nova.api.auth')
3656++flags.DECLARE('dhcp_domain', 'nova.network.manager')
3657++
3658++_DEFAULT_MAPPINGS = {'ami': 'sda1',
3659++ 'ephemeral0': 'sda2',
3660++ 'root': block_device.DEFAULT_ROOT_DEV_NAME,
3661++ 'swap': 'sda3'}
3662++
3663++
3664++class Versions(wsgi.Application):
3665++
3666++ @webob.dec.wsgify(RequestClass=wsgi.Request)
3667++ def __call__(self, req):
3668++ """Respond to a request for all versions."""
3669++ # available api versions
3670++ versions = [
3671++ '1.0',
3672++ '2007-01-19',
3673++ '2007-03-01',
3674++ '2007-08-29',
3675++ '2007-10-10',
3676++ '2007-12-15',
3677++ '2008-02-01',
3678++ '2008-09-01',
3679++ '2009-04-04',
3680++ ]
3681++ return ''.join('%s\n' % v for v in versions)
3682++
3683++
3684++class MetadataRequestHandler(wsgi.Application):
3685++ """Serve metadata."""
3686++
3687++ def __init__(self):
3688++ self.network_api = network.API()
3689++ self.compute_api = compute.API(
3690++ network_api=self.network_api,
3691++ volume_api=volume.API())
3692++
3693++ def _get_mpi_data(self, context, project_id):
3694++ result = {}
3695++ search_opts = {'project_id': project_id, 'deleted': False}
3696++ for instance in self.compute_api.get_all(context,
3697++ search_opts=search_opts):
3698++ ip_info = ec2utils.get_ip_info_for_instance(context, instance)
3699++ # only look at ipv4 addresses
3700++ fixed_ips = ip_info['fixed_ips']
3701++ if fixed_ips:
3702++ line = '%s slots=%d' % (fixed_ips[0], instance['vcpus'])
3703++ key = str(instance['key_name'])
3704++ if key in result:
3705++ result[key].append(line)
3706++ else:
3707++ result[key] = [line]
3708++ return result
3709++
3710++ def _format_instance_mapping(self, ctxt, instance_ref):
3711++ root_device_name = instance_ref['root_device_name']
3712++ if root_device_name is None:
3713++ return _DEFAULT_MAPPINGS
3714++
3715++ mappings = {}
3716++ mappings['ami'] = block_device.strip_dev(root_device_name)
3717++ mappings['root'] = root_device_name
3718++ default_ephemeral_device = instance_ref.get('default_ephemeral_device')
3719++ if default_ephemeral_device:
3720++ mappings['ephemeral0'] = default_ephemeral_device
3721++ default_swap_device = instance_ref.get('default_swap_device')
3722++ if default_swap_device:
3723++ mappings['swap'] = default_swap_device
3724++ ebs_devices = []
3725++
3726++ # 'ephemeralN', 'swap' and ebs
3727++ for bdm in db.block_device_mapping_get_all_by_instance(
3728++ ctxt, instance_ref['id']):
3729++ if bdm['no_device']:
3730++ continue
3731++
3732++ # ebs volume case
3733++ if (bdm['volume_id'] or bdm['snapshot_id']):
3734++ ebs_devices.append(bdm['device_name'])
3735++ continue
3736++
3737++ virtual_name = bdm['virtual_name']
3738++ if not virtual_name:
3739++ continue
3740++
3741++ if block_device.is_swap_or_ephemeral(virtual_name):
3742++ mappings[virtual_name] = bdm['device_name']
3743++
3744++ # NOTE(yamahata): I'm not sure how ebs device should be numbered.
3745++ # Right now sort by device name for deterministic
3746++ # result.
3747++ if ebs_devices:
3748++ nebs = 0
3749++ ebs_devices.sort()
3750++ for ebs in ebs_devices:
3751++ mappings['ebs%d' % nebs] = ebs
3752++ nebs += 1
3753++
3754++ return mappings
3755++
3756++ def get_metadata(self, address):
3757++ if not address:
3758++ raise exception.FixedIpNotFoundForAddress(address=address)
3759++
3760++ ctxt = context.get_admin_context()
3761++ try:
3762++ fixed_ip = self.network_api.get_fixed_ip_by_address(ctxt, address)
3763++ instance_ref = db.instance_get(ctxt, fixed_ip['instance_id'])
3764++ except exception.NotFound:
3765++ return None
3766++
3767++ mpi = self._get_mpi_data(ctxt, instance_ref['project_id'])
3768++ hostname = "%s.%s" % (instance_ref['hostname'], FLAGS.dhcp_domain)
3769++ host = instance_ref['host']
3770++ services = db.service_get_all_by_host(ctxt.elevated(), host)
3771++ availability_zone = ec2utils.get_availability_zone_by_host(services,
3772++ host)
3773++
3774++ ip_info = ec2utils.get_ip_info_for_instance(ctxt, instance_ref)
3775++ floating_ips = ip_info['floating_ips']
3776++ floating_ip = floating_ips and floating_ips[0] or ''
3777++
3778++ ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])
3779++ image_ec2_id = ec2utils.image_ec2_id(instance_ref['image_ref'])
3780++ security_groups = db.security_group_get_by_instance(ctxt,
3781++ instance_ref['id'])
3782++ security_groups = [x['name'] for x in security_groups]
3783++ mappings = self._format_instance_mapping(ctxt, instance_ref)
3784++ data = {
3785++ 'user-data': base64.b64decode(instance_ref['user_data']),
3786++ 'meta-data': {
3787++ 'ami-id': image_ec2_id,
3788++ 'ami-launch-index': instance_ref['launch_index'],
3789++ 'ami-manifest-path': 'FIXME',
3790++ 'block-device-mapping': mappings,
3791++ 'hostname': hostname,
3792++ 'instance-action': 'none',
3793++ 'instance-id': ec2_id,
3794++ 'instance-type': instance_ref['instance_type']['name'],
3795++ 'local-hostname': hostname,
3796++ 'local-ipv4': address,
3797++ 'placement': {'availability-zone': availability_zone},
3798++ 'public-hostname': hostname,
3799++ 'public-ipv4': floating_ip,
3800++ 'reservation-id': instance_ref['reservation_id'],
3801++ 'security-groups': security_groups,
3802++ 'mpi': mpi}}
3803++
3804++ # public-keys should be in meta-data only if user specified one
3805++ if instance_ref['key_name']:
3806++ data['meta-data']['public-keys'] = {
3807++ '0': {'_name': instance_ref['key_name'],
3808++ 'openssh-key': instance_ref['key_data']}}
3809++
3810++ for image_type in ['kernel', 'ramdisk']:
3811++ if instance_ref.get('%s_id' % image_type):
3812++ ec2_id = ec2utils.image_ec2_id(
3813++ instance_ref['%s_id' % image_type],
3814++ ec2utils.image_type(image_type))
3815++ data['meta-data']['%s-id' % image_type] = ec2_id
3816++
3817++ if False: # TODO(vish): store ancestor ids
3818++ data['ancestor-ami-ids'] = []
3819++ if False: # TODO(vish): store product codes
3820++ data['product-codes'] = []
3821++ return data
3822++
3823++ def print_data(self, data):
3824++ if isinstance(data, dict):
3825++ output = ''
3826++ for key in data:
3827++ if key == '_name':
3828++ continue
3829++ output += key
3830++ if isinstance(data[key], dict):
3831++ if '_name' in data[key]:
3832++ output += '=' + str(data[key]['_name'])
3833++ else:
3834++ output += '/'
3835++ output += '\n'
3836++ # Cut off last \n
3837++ return output[:-1]
3838++ elif isinstance(data, list):
3839++ return '\n'.join(data)
3840++ else:
3841++ return str(data)
3842++
3843++ def lookup(self, path, data):
3844++ items = path.split('/')
3845++ for item in items:
3846++ if item:
3847++ if not isinstance(data, dict):
3848++ return data
3849++ if not item in data:
3850++ return None
3851++ data = data[item]
3852++ return data
3853++
3854++ @webob.dec.wsgify(RequestClass=wsgi.Request)
3855++ def __call__(self, req):
3856++ remote_address = req.remote_addr
3857++ if FLAGS.use_forwarded_for:
3858++ remote_address = req.headers.get('X-Forwarded-For', remote_address)
3859++ try:
3860++ meta_data = self.get_metadata(remote_address)
3861++ except Exception:
3862++ LOG.exception(_('Failed to get metadata for ip: %s'),
3863++ remote_address)
3864++ msg = _('An unknown error has occurred. '
3865++ 'Please try your request again.')
3866++ exc = webob.exc.HTTPInternalServerError(explanation=unicode(msg))
3867++ return exc
3868++ if meta_data is None:
3869++ LOG.error(_('Failed to get metadata for ip: %s'), remote_address)
3870++ raise webob.exc.HTTPNotFound()
3871++ data = self.lookup(req.path_info, meta_data)
3872++ if data is None:
3873++ raise webob.exc.HTTPNotFound()
3874++ return self.print_data(data)
3875+diff -Naurp nova.orig/api/metadata/__init__.py nova/api/metadata/__init__.py
3876+--- nova.orig/api/metadata/__init__.py 1969-12-31 19:00:00.000000000 -0500
3877++++ nova/api/metadata/__init__.py 2012-02-28 09:08:10.821887173 -0500
3878+@@ -0,0 +1,25 @@
3879++# vim: tabstop=4 shiftwidth=4 softtabstop=4
3880++
3881++# Copyright 2011 Openstack, LLC.
3882++#
3883++# Licensed under the Apache License, Version 2.0 (the "License"); you may
3884++# not use this file except in compliance with the License. You may obtain
3885++# a copy of the License at
3886++#
3887++# http://www.apache.org/licenses/LICENSE-2.0
3888++#
3889++# Unless required by applicable law or agreed to in writing, software
3890++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3891++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3892++# License for the specific language governing permissions and limitations
3893++# under the License.
3894++
3895++"""
3896++:mod:`nova.api.metadata` -- Nova Metadata Server
3897++================================================
3898++
3899++.. automodule:: nova.api.metadata
3900++ :platform: Unix
3901++ :synopsis: Metadata Server for Nova
3902++.. moduleauthor:: Vishvananda Ishaya <vishvananda@gmail.com>
3903++"""
3904+diff -Naurp nova.orig/api/openstack/auth.py nova/api/openstack/auth.py
3905+--- nova.orig/api/openstack/auth.py 1969-12-31 19:00:00.000000000 -0500
3906++++ nova/api/openstack/auth.py 2012-02-28 09:08:10.821887173 -0500
3907+@@ -0,0 +1,263 @@
3908++# vim: tabstop=4 shiftwidth=4 softtabstop=4
3909++
3910++# Copyright 2010 OpenStack LLC.
3911++# All Rights Reserved.
3912++#
3913++# Licensed under the Apache License, Version 2.0 (the "License"); you may
3914++# not use this file except in compliance with the License. You may obtain
3915++# a copy of the License at
3916++#
3917++# http://www.apache.org/licenses/LICENSE-2.0
3918++#
3919++# Unless required by applicable law or agreed to in writing, software
3920++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
3921++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
3922++# License for the specific language governing permissions and limitations
3923++# under the License.
3924++
3925++import hashlib
3926++import os
3927++import time
3928++
3929++import webob.dec
3930++import webob.exc
3931++
3932++from nova.api.openstack import common
3933++from nova.api.openstack import wsgi
3934++from nova.auth import manager
3935++from nova import context
3936++from nova import exception
3937++from nova import flags
3938++from nova import log as logging
3939++from nova import utils
3940++from nova import wsgi as base_wsgi
3941++
3942++LOG = logging.getLogger(__name__)
3943++FLAGS = flags.FLAGS
3944++flags.DECLARE('use_forwarded_for', 'nova.api.auth')
3945++
3946++
3947++class NoAuthMiddleware(base_wsgi.Middleware):
3948++ """Return a fake token if one isn't specified."""
3949++
3950++ @webob.dec.wsgify(RequestClass=wsgi.Request)
3951++ def __call__(self, req):
3952++ if 'X-Auth-Token' not in req.headers:
3953++ user_id = req.headers.get('X-Auth-User', 'admin')
3954++ project_id = req.headers.get('X-Auth-Project-Id', 'admin')
3955++ os_url = os.path.join(req.url, project_id)
3956++ res = webob.Response()
3957++ # NOTE(vish): This is expecting and returning Auth(1.1), whereas
3958++ # keystone uses 2.0 auth. We should probably allow
3959++ # 2.0 auth here as well.
3960++ res.headers['X-Auth-Token'] = '%s:%s' % (user_id, project_id)
3961++ res.headers['X-Server-Management-Url'] = os_url
3962++ res.content_type = 'text/plain'
3963++ res.status = '204'
3964++ return res
3965++
3966++ token = req.headers['X-Auth-Token']
3967++ user_id, _sep, project_id = token.partition(':')
3968++ project_id = project_id or user_id
3969++ remote_address = getattr(req, 'remote_address', '127.0.0.1')
3970++ if FLAGS.use_forwarded_for:
3971++ remote_address = req.headers.get('X-Forwarded-For', remote_address)
3972++ ctx = context.RequestContext(user_id,
3973++ project_id,
3974++ is_admin=True,
3975++ remote_address=remote_address)
3976++
3977++ req.environ['nova.context'] = ctx
3978++ return self.application
3979++
3980++
3981++class AuthMiddleware(base_wsgi.Middleware):
3982++ """Authorize the openstack API request or return an HTTP Forbidden."""
3983++
3984++ def __init__(self, application, db_driver=None):
3985++ if not db_driver:
3986++ db_driver = FLAGS.db_driver
3987++ self.db = utils.import_object(db_driver)
3988++ self.auth = manager.AuthManager()
3989++ super(AuthMiddleware, self).__init__(application)
3990++
3991++ @webob.dec.wsgify(RequestClass=wsgi.Request)
3992++ def __call__(self, req):
3993++ if not self.has_authentication(req):
3994++ return self.authenticate(req)
3995++ user_id = self.get_user_by_authentication(req)
3996++ if not user_id:
3997++ token = req.headers["X-Auth-Token"]
3998++ msg = _("%(user_id)s could not be found with token '%(token)s'")
3999++ LOG.warn(msg % locals())
4000++ return wsgi.Fault(webob.exc.HTTPUnauthorized())
4001++
4002++ # Get all valid projects for the user
4003++ projects = self.auth.get_projects(user_id)
4004++ if not projects:
4005++ return wsgi.Fault(webob.exc.HTTPUnauthorized())
4006++
4007++ project_id = ""
4008++ path_parts = req.path.split('/')
4009++ # TODO(wwolf): this v1.1 check will be temporary as
4010++ # keystone should be taking this over at some point
4011++ if len(path_parts) > 1 and path_parts[1] in ('v1.1', 'v2'):
4012++ project_id = path_parts[2]
4013++ # Check that the project for project_id exists, and that user
4014++ # is authorized to use it
4015++ try:
4016++ self.auth.get_project(project_id)
4017++ except exception.ProjectNotFound:
4018++ return wsgi.Fault(webob.exc.HTTPUnauthorized())
4019++ if project_id not in [p.id for p in projects]:
4020++ return wsgi.Fault(webob.exc.HTTPUnauthorized())
4021++ else:
4022++ # As a fallback, set project_id from the headers, which is the v1.0
4023++ # behavior. As a last resort, be forgiving to the user and set
4024++ # project_id based on a valid project of theirs.
4025++ try:
4026++ project_id = req.headers["X-Auth-Project-Id"]
4027++ except KeyError:
4028++ project_id = projects[0].id
4029++
4030++ is_admin = self.auth.is_admin(user_id)
4031++ remote_address = getattr(req, 'remote_address', '127.0.0.1')
4032++ if FLAGS.use_forwarded_for:
4033++ remote_address = req.headers.get('X-Forwarded-For', remote_address)
4034++ ctx = context.RequestContext(user_id,
4035++ project_id,
4036++ is_admin=is_admin,
4037++ remote_address=remote_address)
4038++ req.environ['nova.context'] = ctx
4039++
4040++ if not is_admin and not self.auth.is_project_member(user_id,
4041++ project_id):
4042++ msg = _("%(user_id)s must be an admin or a "
4043++ "member of %(project_id)s")
4044++ LOG.warn(msg % locals())
4045++ return wsgi.Fault(webob.exc.HTTPUnauthorized())
4046++
4047++ return self.application
4048++
4049++ def has_authentication(self, req):
4050++ return 'X-Auth-Token' in req.headers
4051++
4052++ def get_user_by_authentication(self, req):
4053++ return self.authorize_token(req.headers["X-Auth-Token"])
4054++
4055++ def authenticate(self, req):
4056++ # Unless the request is explicitly made against /<version>/ don't
4057++ # honor it
4058++ path_info = req.path_info
4059++ if len(path_info) > 1:
4060++ msg = _("Authentication requests must be made against a version "
4061++ "root (e.g. /v2).")
4062++ LOG.warn(msg)
4063++ return wsgi.Fault(webob.exc.HTTPUnauthorized(explanation=msg))
4064++
4065++ def _get_auth_header(key):
4066++ """Ensures that the KeyError returned is meaningful."""
4067++ try:
4068++ return req.headers[key]
4069++ except KeyError as ex:
4070++ raise KeyError(key)
4071++ try:
4072++ username = _get_auth_header('X-Auth-User')
4073++ key = _get_auth_header('X-Auth-Key')
4074++ except KeyError as ex:
4075++ msg = _("Could not find %s in request.") % ex
4076++ LOG.warn(msg)
4077++ return wsgi.Fault(webob.exc.HTTPUnauthorized(explanation=msg))
4078++
4079++ token, user = self._authorize_user(username, key, req)
4080++ if user and token:
4081++ res = webob.Response()
4082++ res.headers['X-Auth-Token'] = token['token_hash']
4083++ _x_server_url = 'X-Server-Management-Url'
4084++ _server_url = 'server_management_url'
4085++ res.headers[_x_server_url] = token[_server_url]
4086++
4087++ if token['storage_url']:
4088++ _x_storage_url = 'X-Storage-Url'
4089++ _storage_url = 'storage_url'
4090++ res.headers[_x_storage_url] = token[_storage_url]
4091++
4092++ if token['cdn_management_url']:
4093++ _x_cdn_url = 'X-CDN-Management-Url'
4094++ _cdn_url = 'cdn_management_url'
4095++ res.headers[_x_cdn_url] = token[_cdn_url]
4096++
4097++ res.content_type = 'text/plain'
4098++ res.status = '204'
4099++ LOG.debug(_("Successfully authenticated '%s'") % username)
4100++ return res
4101++ else:
4102++ return wsgi.Fault(webob.exc.HTTPUnauthorized())
4103++
4104++ def authorize_token(self, token_hash):
4105++ """ retrieves user information from the datastore given a token
4106++
4107++ If the token has expired, returns None
4108++ If the token is not found, returns None
4109++ Otherwise returns dict(id=(the authorized user's id))
4110++
4111++ This method will also remove the token if the timestamp is older than
4112++ 2 days ago.
4113++ """
4114++ ctxt = context.get_admin_context()
4115++ try:
4116++ token = self.db.auth_token_get(ctxt, token_hash)
4117++ except exception.NotFound:
4118++ return None
4119++ if token:
4120++ delta = utils.utcnow() - token['created_at']
4121++ if delta.days >= 2:
4122++ self.db.auth_token_destroy(ctxt, token['token_hash'])
4123++ else:
4124++ return token['user_id']
4125++ return None
4126++
4127++ def _authorize_user(self, username, key, req):
4128++ """Generates a new token and assigns it to a user.
4129++
4130++ username - string
4131++ key - string API key
4132++ req - wsgi.Request object
4133++ """
4134++ ctxt = context.get_admin_context()
4135++
4136++ project_id = req.headers.get('X-Auth-Project-Id')
4137++ if project_id is None:
4138++ # If the project_id is not provided in the headers, be forgiving to
4139++ # the user and set project_id based on a valid project of theirs.
4140++ user = self.auth.get_user_from_access_key(key)
4141++ projects = self.auth.get_projects(user.id)
4142++ if not projects:
4143++ raise webob.exc.HTTPUnauthorized()
4144++ project_id = projects[0].id
4145++
4146++ try:
4147++ user = self.auth.get_user_from_access_key(key)
4148++ except exception.NotFound:
4149++ LOG.warn(_("User not found with provided API key."))
4150++ user = None
4151++
4152++ if user and user.name == username:
4153++ token_hash = hashlib.sha1('%s%s%f' % (username, key,
4154++ time.time())).hexdigest()
4155++ token_dict = {}
4156++ token_dict['token_hash'] = token_hash
4157++ token_dict['cdn_management_url'] = ''
4158++ os_url = req.url.strip('/')
4159++ os_url += '/' + project_id
4160++ token_dict['server_management_url'] = os_url
4161++ token_dict['storage_url'] = ''
4162++ token_dict['user_id'] = user.id
4163++ token = self.db.auth_token_create(ctxt, token_dict)
4164++ return token, user
4165++ elif user and user.name != username:
4166++ msg = _("Provided API key is valid, but not for user "
4167++ "'%(username)s'") % locals()
4168++ LOG.warn(msg)
4169++
4170++ return None, None
4171+diff -Naurp nova.orig/api/openstack/common.py nova/api/openstack/common.py
4172+--- nova.orig/api/openstack/common.py 1969-12-31 19:00:00.000000000 -0500
4173++++ nova/api/openstack/common.py 2012-02-28 09:08:10.821887173 -0500
4174+@@ -0,0 +1,493 @@
4175++# vim: tabstop=4 shiftwidth=4 softtabstop=4
4176++
4177++# Copyright 2010 OpenStack LLC.
4178++# All Rights Reserved.
4179++#
4180++# Licensed under the Apache License, Version 2.0 (the "License"); you may
4181++# not use this file except in compliance with the License. You may obtain
4182++# a copy of the License at
4183++#
4184++# http://www.apache.org/licenses/LICENSE-2.0
4185++#
4186++# Unless required by applicable law or agreed to in writing, software
4187++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
4188++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4189++# License for the specific language governing permissions and limitations
4190++# under the License.
4191++
4192++import functools
4193++import os
4194++import re
4195++import urlparse
4196++
4197++import webob
4198++from xml.dom import minidom
4199++
4200++from nova.api.openstack import wsgi
4201++from nova.api.openstack import xmlutil
4202++from nova.compute import vm_states
4203++from nova.compute import task_states
4204++from nova import exception
4205++from nova import flags
4206++from nova import log as logging
4207++from nova import network
4208++from nova.network import model as network_model
4209++from nova import quota
4210++
4211++
4212++LOG = logging.getLogger(__name__)
4213++FLAGS = flags.FLAGS
4214++
4215++
4216++XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1'
4217++
4218++
4219++_STATE_MAP = {
4220++ vm_states.ACTIVE: {
4221++ 'default': 'ACTIVE',
4222++ task_states.REBOOTING: 'REBOOT',
4223++ task_states.REBOOTING_HARD: 'HARD_REBOOT',
4224++ task_states.UPDATING_PASSWORD: 'PASSWORD',
4225++ task_states.RESIZE_VERIFY: 'VERIFY_RESIZE',
4226++ },
4227++ vm_states.BUILDING: {
4228++ 'default': 'BUILD',
4229++ },
4230++ vm_states.REBUILDING: {
4231++ 'default': 'REBUILD',
4232++ },
4233++ vm_states.STOPPED: {
4234++ 'default': 'STOPPED',
4235++ },
4236++ vm_states.SHUTOFF: {
4237++ 'default': 'SHUTOFF',
4238++ },
4239++ vm_states.MIGRATING: {
4240++ 'default': 'MIGRATING',
4241++ },
4242++ vm_states.RESIZING: {
4243++ 'default': 'RESIZE',
4244++ task_states.RESIZE_REVERTING: 'REVERT_RESIZE',
4245++ },
4246++ vm_states.PAUSED: {
4247++ 'default': 'PAUSED',
4248++ },
4249++ vm_states.SUSPENDED: {
4250++ 'default': 'SUSPENDED',
4251++ },
4252++ vm_states.RESCUED: {
4253++ 'default': 'RESCUE',
4254++ },
4255++ vm_states.ERROR: {
4256++ 'default': 'ERROR',
4257++ },
4258++ vm_states.DELETED: {
4259++ 'default': 'DELETED',
4260++ },
4261++ vm_states.SOFT_DELETE: {
4262++ 'default': 'DELETED',
4263++ },
4264++}
4265++
4266++
4267++def status_from_state(vm_state, task_state='default'):
4268++ """Given vm_state and task_state, return a status string."""
4269++ task_map = _STATE_MAP.get(vm_state, dict(default='UNKNOWN_STATE'))
4270++ status = task_map.get(task_state, task_map['default'])
4271++ LOG.debug("Generated %(status)s from vm_state=%(vm_state)s "
4272++ "task_state=%(task_state)s." % locals())
4273++ return status
4274++
4275++
4276++def vm_state_from_status(status):
4277++ """Map the server status string to a vm state."""
4278++ for state, task_map in _STATE_MAP.iteritems():
4279++ status_string = task_map.get("default")
4280++ if status.lower() == status_string.lower():
4281++ return state
4282++
4283++
4284++def get_pagination_params(request):
4285++ """Return marker, limit tuple from request.
4286++
4287++ :param request: `wsgi.Request` possibly containing 'marker' and 'limit'
4288++ GET variables. 'marker' is the id of the last element
4289++ the client has seen, and 'limit' is the maximum number
4290++ of items to return. If 'limit' is not specified, 0, or
4291++ > max_limit, we default to max_limit. Negative values
4292++ for either marker or limit will cause
4293++ exc.HTTPBadRequest() exceptions to be raised.
4294++
4295++ """
4296++ params = {}
4297++ if 'limit' in request.GET:
4298++ params['limit'] = _get_limit_param(request)
4299++ if 'marker' in request.GET:
4300++ params['marker'] = _get_marker_param(request)
4301++ return params
4302++
4303++
4304++def _get_limit_param(request):
4305++ """Extract integer limit from request or fail"""
4306++ try:
4307++ limit = int(request.GET['limit'])
4308++ except ValueError:
4309++ msg = _('limit param must be an integer')
4310++ raise webob.exc.HTTPBadRequest(explanation=msg)
4311++ if limit < 0:
4312++ msg = _('limit param must be positive')
4313++ raise webob.exc.HTTPBadRequest(explanation=msg)
4314++ return limit
4315++
4316++
4317++def _get_marker_param(request):
4318++ """Extract marker id from request or fail"""
4319++ return request.GET['marker']
4320++
4321++
4322++def limited(items, request, max_limit=FLAGS.osapi_max_limit):
4323++ """
4324++ Return a slice of items according to requested offset and limit.
4325++
4326++ @param items: A sliceable entity
4327++ @param request: `wsgi.Request` possibly containing 'offset' and 'limit'
4328++ GET variables. 'offset' is where to start in the list,
4329++ and 'limit' is the maximum number of items to return. If
4330++ 'limit' is not specified, 0, or > max_limit, we default
4331++ to max_limit. Negative values for either offset or limit
4332++ will cause exc.HTTPBadRequest() exceptions to be raised.
4333++ @kwarg max_limit: The maximum number of items to return from 'items'
4334++ """
4335++ try:
4336++ offset = int(request.GET.get('offset', 0))
4337++ except ValueError:
4338++ msg = _('offset param must be an integer')
4339++ raise webob.exc.HTTPBadRequest(explanation=msg)
4340++
4341++ try:
4342++ limit = int(request.GET.get('limit', max_limit))
4343++ except ValueError:
4344++ msg = _('limit param must be an integer')
4345++ raise webob.exc.HTTPBadRequest(explanation=msg)
4346++
4347++ if limit < 0:
4348++ msg = _('limit param must be positive')
4349++ raise webob.exc.HTTPBadRequest(explanation=msg)
4350++
4351++ if offset < 0:
4352++ msg = _('offset param must be positive')
4353++ raise webob.exc.HTTPBadRequest(explanation=msg)
4354++
4355++ limit = min(max_limit, limit or max_limit)
4356++ range_end = offset + limit
4357++ return items[offset:range_end]
4358++
4359++
4360++def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit):
4361++ """Return a slice of items according to the requested marker and limit."""
4362++ params = get_pagination_params(request)
4363++
4364++ limit = params.get('limit', max_limit)
4365++ marker = params.get('marker')
4366++
4367++ limit = min(max_limit, limit)
4368++ start_index = 0
4369++ if marker:
4370++ start_index = -1
4371++ for i, item in enumerate(items):
4372++ if item['id'] == marker or item.get('uuid') == marker:
4373++ start_index = i + 1
4374++ break
4375++ if start_index < 0:
4376++ msg = _('marker [%s] not found') % marker
4377++ raise webob.exc.HTTPBadRequest(explanation=msg)
4378++ range_end = start_index + limit
4379++ return items[start_index:range_end]
4380++
4381++
4382++def get_id_from_href(href):
4383++ """Return the id or uuid portion of a url.
4384++
4385++ Given: 'http://www.foo.com/bar/123?q=4'
4386++ Returns: '123'
4387++
4388++ Given: 'http://www.foo.com/bar/abc123?q=4'
4389++ Returns: 'abc123'
4390++
4391++ """
4392++ return urlparse.urlsplit("%s" % href).path.split('/')[-1]
4393++
4394++
4395++def remove_version_from_href(href):
4396++ """Removes the first api version from the href.
4397++
4398++ Given: 'http://www.nova.com/v1.1/123'
4399++ Returns: 'http://www.nova.com/123'
4400++
4401++ Given: 'http://www.nova.com/v1.1'
4402++ Returns: 'http://www.nova.com'
4403++
4404++ """
4405++ parsed_url = urlparse.urlsplit(href)
4406++ url_parts = parsed_url.path.split('/', 2)
4407++
4408++ # NOTE: this should match vX.X or vX
4409++ expression = re.compile(r'^v([0-9]+|[0-9]+\.[0-9]+)(/.*|$)')
4410++ if expression.match(url_parts[1]):
4411++ del url_parts[1]
4412++
4413++ new_path = '/'.join(url_parts)
4414++
4415++ if new_path == parsed_url.path:
4416++ msg = _('href %s does not contain version') % href
4417++ LOG.debug(msg)
4418++ raise ValueError(msg)
4419++
4420++ parsed_url = list(parsed_url)
4421++ parsed_url[2] = new_path
4422++ return urlparse.urlunsplit(parsed_url)
4423++
4424++
4425++def get_version_from_href(href):
4426++ """Returns the api version in the href.
4427++
4428++ Returns the api version in the href.
4429++ If no version is found, '2' is returned
4430++
4431++ Given: 'http://www.nova.com/123'
4432++ Returns: '2'
4433++
4434++ Given: 'http://www.nova.com/v1.1'
4435++ Returns: '1.1'
4436++
4437++ """
4438++ try:
4439++ expression = r'/v([0-9]+|[0-9]+\.[0-9]+)(/|$)'
4440++ return re.findall(expression, href)[0][0]
4441++ except IndexError:
4442++ return '2'
4443++
4444++
4445++def check_img_metadata_quota_limit(context, metadata):
4446++ if metadata is None:
4447++ return
4448++ num_metadata = len(metadata)
4449++ quota_metadata = quota.allowed_metadata_items(context, num_metadata)
4450++ if quota_metadata < num_metadata:
4451++ expl = _("Image metadata limit exceeded")
4452++ raise webob.exc.HTTPRequestEntityTooLarge(explanation=expl,
4453++ headers={'Retry-After': 0})
4454++
4455++
4456++def dict_to_query_str(params):
4457++ # TODO: we should just use urllib.urlencode instead of this
4458++ # But currently we don't work with urlencoded url's
4459++ param_str = ""
4460++ for key, val in params.iteritems():
4461++ param_str = param_str + '='.join([str(key), str(val)]) + '&'
4462++
4463++ return param_str.rstrip('&')
4464++
4465++
4466++def get_networks_for_instance_from_nw_info(nw_info):
4467++ networks = {}
4468++
4469++ for vif in nw_info:
4470++ ips = vif.fixed_ips()
4471++ floaters = vif.floating_ips()
4472++ label = vif['network']['label']
4473++ if label not in networks:
4474++ networks[label] = {'ips': [], 'floating_ips': []}
4475++
4476++ networks[label]['ips'].extend(ips)
4477++ networks[label]['floating_ips'].extend(floaters)
4478++ return networks
4479++
4480++
4481++def get_nw_info_for_instance(context, instance):
4482++ cached_nwinfo = instance['info_cache'].get('network_info') or []
4483++ return network_model.NetworkInfo.hydrate(cached_nwinfo)
4484++
4485++
4486++def get_networks_for_instance(context, instance):
4487++ """Returns a prepared nw_info list for passing into the view
4488++ builders
4489++
4490++ We end up with a data structure like:
4491++ {'public': {'ips': [{'addr': '10.0.0.1', 'version': 4},
4492++ {'addr': '2001::1', 'version': 6}],
4493++ 'floating_ips': [{'addr': '172.16.0.1', 'version': 4},
4494++ {'addr': '172.16.2.1', 'version': 4}]},
4495++ ...}
4496++ """
4497++ nw_info = get_nw_info_for_instance(context, instance)
4498++ return get_networks_for_instance_from_nw_info(nw_info)
4499++
4500++
4501++def raise_http_conflict_for_instance_invalid_state(exc, action):
4502++ """Return a webob.exc.HTTPConflict instance containing a message
4503++ appropriate to return via the API based on the original
4504++ InstanceInvalidState exception.
4505++ """
4506++ attr = exc.kwargs.get('attr')
4507++ state = exc.kwargs.get('state')
4508++ if attr and state:
4509++ msg = _("Cannot '%(action)s' while instance is in %(attr)s %(state)s")
4510++ else:
4511++ # At least give some meaningful message
4512++ msg = _("Instance is in an invalid state for '%(action)s'")
4513++ raise webob.exc.HTTPConflict(explanation=msg % locals())
4514++
4515++
4516++class MetadataDeserializer(wsgi.MetadataXMLDeserializer):
4517++ def deserialize(self, text):
4518++ dom = minidom.parseString(text)
4519++ metadata_node = self.find_first_child_named(dom, "metadata")
4520++ metadata = self.extract_metadata(metadata_node)
4521++ return {'body': {'metadata': metadata}}
4522++
4523++
4524++class MetaItemDeserializer(wsgi.MetadataXMLDeserializer):
4525++ def deserialize(self, text):
4526++ dom = minidom.parseString(text)
4527++ metadata_item = self.extract_metadata(dom)
4528++ return {'body': {'meta': metadata_item}}
4529++
4530++
4531++class MetadataXMLDeserializer(wsgi.XMLDeserializer):
4532++
4533++ def extract_metadata(self, metadata_node):
4534++ """Marshal the metadata attribute of a parsed request"""
4535++ if metadata_node is None:
4536++ return {}
4537++ metadata = {}
4538++ for meta_node in self.find_children_named(metadata_node, "meta"):
4539++ key = meta_node.getAttribute("key")
4540++ metadata[key] = self.extract_text(meta_node)
4541++ return metadata
4542++
4543++ def _extract_metadata_container(self, datastring):
4544++ dom = minidom.parseString(datastring)
4545++ metadata_node = self.find_first_child_named(dom, "metadata")
4546++ metadata = self.extract_metadata(metadata_node)
4547++ return {'body': {'metadata': metadata}}
4548++
4549++ def create(self, datastring):
4550++ return self._extract_metadata_container(datastring)
4551++
4552++ def update_all(self, datastring):
4553++ return self._extract_metadata_container(datastring)
4554++
4555++ def update(self, datastring):
4556++ dom = minidom.parseString(datastring)
4557++ metadata_item = self.extract_metadata(dom)
4558++ return {'body': {'meta': metadata_item}}
4559++
4560++
4561++metadata_nsmap = {None: xmlutil.XMLNS_V11}
4562++
4563++
4564++class MetaItemTemplate(xmlutil.TemplateBuilder):
4565++ def construct(self):
4566++ sel = xmlutil.Selector('meta', xmlutil.get_items, 0)
4567++ root = xmlutil.TemplateElement('meta', selector=sel)
4568++ root.set('key', 0)
4569++ root.text = 1
4570++ return xmlutil.MasterTemplate(root, 1, nsmap=metadata_nsmap)
4571++
4572++
4573++class MetadataTemplateElement(xmlutil.TemplateElement):
4574++ def will_render(self, datum):
4575++ return True
4576++
4577++
4578++class MetadataTemplate(xmlutil.TemplateBuilder):
4579++ def construct(self):
4580++ root = MetadataTemplateElement('metadata', selector='metadata')
4581++ elem = xmlutil.SubTemplateElement(root, 'meta',
4582++ selector=xmlutil.get_items)
4583++ elem.set('key', 0)
4584++ elem.text = 1
4585++ return xmlutil.MasterTemplate(root, 1, nsmap=metadata_nsmap)
4586++
4587++
4588++def check_snapshots_enabled(f):
4589++ @functools.wraps(f)
4590++ def inner(*args, **kwargs):
4591++ if not FLAGS.allow_instance_snapshots:
4592++ LOG.warn(_('Rejecting snapshot request, snapshots currently'
4593++ ' disabled'))
4594++ msg = _("Instance snapshots are not permitted at this time.")
4595++ raise webob.exc.HTTPBadRequest(explanation=msg)
4596++ return f(*args, **kwargs)
4597++ return inner
4598++
4599++
4600++class ViewBuilder(object):
4601++ """Model API responses as dictionaries."""
4602++
4603++ _collection_name = None
4604++
4605++ def _get_links(self, request, identifier):
4606++ return [{
4607++ "rel": "self",
4608++ "href": self._get_href_link(request, identifier),
4609++ },
4610++ {
4611++ "rel": "bookmark",
4612++ "href": self._get_bookmark_link(request, identifier),
4613++ }]
4614++
4615++ def _get_next_link(self, request, identifier):
4616++ """Return href string with proper limit and marker params."""
4617++ params = request.params.copy()
4618++ params["marker"] = identifier
4619++ prefix = self._update_link_prefix(request.application_url,
4620++ FLAGS.osapi_compute_link_prefix)
4621++ url = os.path.join(prefix,
4622++ request.environ["nova.context"].project_id,
4623++ self._collection_name)
4624++ return "%s?%s" % (url, dict_to_query_str(params))
4625++
4626++ def _get_href_link(self, request, identifier):
4627++ """Return an href string pointing to this object."""
4628++ prefix = self._update_link_prefix(request.application_url,
4629++ FLAGS.osapi_compute_link_prefix)
4630++ return os.path.join(prefix,
4631++ request.environ["nova.context"].project_id,
4632++ self._collection_name,
4633++ str(identifier))
4634++
4635++ def _get_bookmark_link(self, request, identifier):
4636++ """Create a URL that refers to a specific resource."""
4637++ base_url = remove_version_from_href(request.application_url)
4638++ base_url = self._update_link_prefix(base_url,
4639++ FLAGS.osapi_compute_link_prefix)
4640++ return os.path.join(base_url,
4641++ request.environ["nova.context"].project_id,
4642++ self._collection_name,
4643++ str(identifier))
4644++
4645++ def _get_collection_links(self, request, items, id_key="uuid"):
4646++ """Retrieve 'next' link, if applicable."""
4647++ links = []
4648++ limit = int(request.params.get("limit", 0))
4649++ if limit and limit == len(items):
4650++ last_item = items[-1]
4651++ if id_key in last_item:
4652++ last_item_id = last_item[id_key]
4653++ else:
4654++ last_item_id = last_item["id"]
4655++ links.append({
4656++ "rel": "next",
4657++ "href": self._get_next_link(request, last_item_id),
4658++ })
4659++ return links
4660++
4661++ def _update_link_prefix(self, orig_url, prefix):
4662++ if not prefix:
4663++ return orig_url
4664++ url_parts = list(urlparse.urlsplit(orig_url))
4665++ prefix_parts = list(urlparse.urlsplit(prefix))
4666++ url_parts[0:2] = prefix_parts[0:2]
4667++ return urlparse.urlunsplit(url_parts)
4668+diff -Naurp nova.orig/api/openstack/compute/consoles.py nova/api/openstack/compute/consoles.py
4669+--- nova.orig/api/openstack/compute/consoles.py 1969-12-31 19:00:00.000000000 -0500
4670++++ nova/api/openstack/compute/consoles.py 2012-02-28 09:08:10.821887173 -0500
4671+@@ -0,0 +1,131 @@
4672++# vim: tabstop=4 shiftwidth=4 softtabstop=4
4673++
4674++# Copyright 2010 OpenStack LLC.
4675++# All Rights Reserved.
4676++#
4677++# Licensed under the Apache License, Version 2.0 (the "License"); you may
4678++# not use this file except in compliance with the License. You may obtain
4679++# a copy of the License at
4680++#
4681++# http://www.apache.org/licenses/LICENSE-2.0
4682++#
4683++# Unless required by applicable law or agreed to in writing, software
4684++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
4685++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4686++# License for the specific language governing permissions and limitations
4687++# under the License.
4688++
4689++import webob
4690++from webob import exc
4691++
4692++from nova.api.openstack import wsgi
4693++from nova.api.openstack import xmlutil
4694++from nova import console
4695++from nova import exception
4696++
4697++
4698++def _translate_keys(cons):
4699++ """Coerces a console instance into proper dictionary format """
4700++ pool = cons['pool']
4701++ info = {'id': cons['id'],
4702++ 'console_type': pool['console_type']}
4703++ return dict(console=info)
4704++
4705++
4706++def _translate_detail_keys(cons):
4707++ """Coerces a console instance into proper dictionary format with
4708++ correctly mapped attributes """
4709++ pool = cons['pool']
4710++ info = {'id': cons['id'],
4711++ 'console_type': pool['console_type'],
4712++ 'password': cons['password'],
4713++ 'instance_name': cons['instance_name'],
4714++ 'port': cons['port'],
4715++ 'host': pool['public_hostname']}
4716++ return dict(console=info)
4717++
4718++
4719++class ConsoleTemplate(xmlutil.TemplateBuilder):
4720++ def construct(self):
4721++ root = xmlutil.TemplateElement('console', selector='console')
4722++
4723++ id_elem = xmlutil.SubTemplateElement(root, 'id', selector='id')
4724++ id_elem.text = xmlutil.Selector()
4725++
4726++ port_elem = xmlutil.SubTemplateElement(root, 'port', selector='port')
4727++ port_elem.text = xmlutil.Selector()
4728++
4729++ host_elem = xmlutil.SubTemplateElement(root, 'host', selector='host')
4730++ host_elem.text = xmlutil.Selector()
4731++
4732++ passwd_elem = xmlutil.SubTemplateElement(root, 'password',
4733++ selector='password')
4734++ passwd_elem.text = xmlutil.Selector()
4735++
4736++ constype_elem = xmlutil.SubTemplateElement(root, 'console_type',
4737++ selector='console_type')
4738++ constype_elem.text = xmlutil.Selector()
4739++
4740++ return xmlutil.MasterTemplate(root, 1)
4741++
4742++
4743++class ConsolesTemplate(xmlutil.TemplateBuilder):
4744++ def construct(self):
4745++ root = xmlutil.TemplateElement('consoles')
4746++ console = xmlutil.SubTemplateElement(root, 'console',
4747++ selector='consoles')
4748++ console.append(ConsoleTemplate())
4749++
4750++ return xmlutil.MasterTemplate(root, 1)
4751++
4752++
4753++class Controller(object):
4754++ """The Consoles controller for the Openstack API"""
4755++
4756++ def __init__(self):
4757++ self.console_api = console.API()
4758++
4759++ @wsgi.serializers(xml=ConsolesTemplate)
4760++ def index(self, req, server_id):
4761++ """Returns a list of consoles for this instance"""
4762++ consoles = self.console_api.get_consoles(
4763++ req.environ['nova.context'],
4764++ server_id)
4765++ return dict(consoles=[_translate_keys(console)
4766++ for console in consoles])
4767++
4768++ def create(self, req, server_id):
4769++ """Creates a new console"""
4770++ self.console_api.create_console(
4771++ req.environ['nova.context'],
4772++ server_id)
4773++
4774++ @wsgi.serializers(xml=ConsoleTemplate)
4775++ def show(self, req, server_id, id):
4776++ """Shows in-depth information on a specific console"""
4777++ try:
4778++ console = self.console_api.get_console(
4779++ req.environ['nova.context'],
4780++ server_id,
4781++ int(id))
4782++ except exception.NotFound:
4783++ raise exc.HTTPNotFound()
4784++ return _translate_detail_keys(console)
4785++
4786++ def update(self, req, server_id, id):
4787++ """You can't update a console"""
4788++ raise exc.HTTPNotImplemented()
4789++
4790++ def delete(self, req, server_id, id):
4791++ """Deletes a console"""
4792++ try:
4793++ self.console_api.delete_console(req.environ['nova.context'],
4794++ server_id,
4795++ int(id))
4796++ except exception.NotFound:
4797++ raise exc.HTTPNotFound()
4798++ return webob.Response(status_int=202)
4799++
4800++
4801++def create_resource():
4802++ return wsgi.Resource(Controller())
4803+diff -Naurp nova.orig/api/openstack/compute/contrib/accounts.py nova/api/openstack/compute/contrib/accounts.py
4804+--- nova.orig/api/openstack/compute/contrib/accounts.py 1969-12-31 19:00:00.000000000 -0500
4805++++ nova/api/openstack/compute/contrib/accounts.py 2012-02-28 09:08:10.821887173 -0500
4806+@@ -0,0 +1,100 @@
4807++# Copyright 2011 OpenStack LLC.
4808++# All Rights Reserved.
4809++#
4810++# Licensed under the Apache License, Version 2.0 (the "License"); you may
4811++# not use this file except in compliance with the License. You may obtain
4812++# a copy of the License at
4813++#
4814++# http://www.apache.org/licenses/LICENSE-2.0
4815++#
4816++# Unless required by applicable law or agreed to in writing, software
4817++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
4818++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
4819++# License for the specific language governing permissions and limitations
4820++# under the License.
4821++
4822++import webob.exc
4823++
4824++from nova.api.openstack import extensions
4825++from nova.api.openstack import wsgi
4826++from nova.api.openstack import xmlutil
4827++from nova.auth import manager
4828++from nova import exception
4829++from nova import flags
4830++from nova import log as logging
4831++
4832++
4833++FLAGS = flags.FLAGS
4834++LOG = logging.getLogger(__name__)
4835++authorize = extensions.extension_authorizer('compute', 'accounts')
4836++
4837++
4838++class AccountTemplate(xmlutil.TemplateBuilder):
4839++ def construct(self):
4840++ root = xmlutil.TemplateElement('account', selector='account')
4841++ root.set('id', 'id')
4842++ root.set('name', 'name')
4843++ root.set('description', 'description')
4844++ root.set('manager', 'manager')
4845++
4846++ return xmlutil.MasterTemplate(root, 1)
4847++
4848++
4849++def _translate_keys(account):
4850++ return dict(id=account.id,
4851++ name=account.name,
4852++ description=account.description,
4853++ manager=account.project_manager_id)
4854++
4855++
4856++class Controller(object):
4857++
4858++ def __init__(self):
4859++ self.manager = manager.AuthManager()
4860++
4861++ def index(self, req):
4862++ raise webob.exc.HTTPNotImplemented()
4863++
4864++ @wsgi.serializers(xml=AccountTemplate)
4865++ def show(self, req, id):
4866++ """Return data about the given account id"""
4867++ authorize(req.environ['nova.context'])
4868++ account = self.manager.get_project(id)
4869++ return dict(account=_translate_keys(account))
4870++
4871++ def delete(self, req, id):
4872++ authorize(req.environ['nova.context'])
4873++ self.manager.delete_project(id)
4874++ return {}
4875++
4876++ def create(self, req, body):
4877++ """We use update with create-or-update semantics
4878++ because the id comes from an external source"""
4879++ raise webob.exc.HTTPNotImplemented()
4880++
4881++ @wsgi.serializers(xml=AccountTemplate)
4882++ def update(self, req, id, body):
4883++ """This is really create or update."""
4884++ authorize(req.environ['nova.context'])
4885++ description = body['account'].get('description')
4886++ manager = body['account'].get('manager')
4887++ try:
4888++ account = self.manager.get_project(id)
4889++ self.manager.modify_project(id, manager, description)
4890++ except exception.NotFound:
4891++ account = self.manager.create_project(id, manager, description)
4892++ return dict(account=_translate_keys(account))
4893++
4894++
4895++class Accounts(extensions.ExtensionDescriptor):
4896++ """Admin-only access to accounts"""
4897++
4898++ name = "Accounts"
4899++ alias = "os-accounts"
4900++ namespace = "http://docs.openstack.org/compute/ext/accounts/api/v1.1"
4901++ updated = "2011-12-23T00:00:00+00:00"
4902++
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches