Merge lp:~midokura/nova/network-service into lp:~ntt-pf-lab/nova/network-service

Proposed by Ryu Ishimoto
Status: Merged
Merged at revision: 773
Proposed branch: lp:~midokura/nova/network-service
Merge into: lp:~ntt-pf-lab/nova/network-service
Diff against target: 64148 lines (+34518/-21481)
129 files modified
.bzrignore (+6/-7)
Authors (+3/-0)
MANIFEST.in (+1/-1)
bin/nova-ajax-console-proxy (+10/-11)
bin/nova-dhcpbridge (+3/-1)
bin/nova-manage (+72/-80)
bin/nova-vncproxy (+101/-0)
doc/source/devref/zone.rst (+127/-0)
doc/source/man/novamanage.rst (+10/-0)
doc/source/runnova/vncconsole.rst (+76/-0)
nova/CA/geninter.sh (+1/-1)
nova/CA/genrootca.sh (+2/-1)
nova/CA/openssl.cnf.tmpl (+5/-1)
nova/adminclient.py (+0/-476)
nova/api/direct.py (+5/-1)
nova/api/ec2/cloud.py (+80/-27)
nova/api/openstack/__init__.py (+23/-12)
nova/api/openstack/accounts.py (+2/-3)
nova/api/openstack/backup_schedules.py (+7/-3)
nova/api/openstack/common.py (+17/-0)
nova/api/openstack/consoles.py (+2/-2)
nova/api/openstack/contrib/__init__.py (+22/-0)
nova/api/openstack/contrib/volumes.py (+336/-0)
nova/api/openstack/extensions.py (+156/-73)
nova/api/openstack/faults.py (+5/-3)
nova/api/openstack/flavors.py (+5/-2)
nova/api/openstack/image_metadata.py (+14/-1)
nova/api/openstack/images.py (+106/-213)
nova/api/openstack/ips.py (+72/-0)
nova/api/openstack/limits.py (+2/-2)
nova/api/openstack/server_metadata.py (+2/-1)
nova/api/openstack/servers.py (+88/-29)
nova/api/openstack/shared_ip_groups.py (+5/-5)
nova/api/openstack/users.py (+1/-2)
nova/api/openstack/versions.py (+8/-2)
nova/api/openstack/views/addresses.py (+8/-2)
nova/api/openstack/views/images.py (+91/-11)
nova/api/openstack/views/servers.py (+14/-14)
nova/api/openstack/zones.py (+2/-4)
nova/compute/api.py (+39/-10)
nova/compute/instance_types.py (+37/-35)
nova/compute/manager.py (+49/-16)
nova/crypto.py (+10/-2)
nova/db/api.py (+5/-0)
nova/db/sqlalchemy/api.py (+25/-1)
nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py (+84/-0)
nova/db/sqlalchemy/migrate_repo/versions/014_diablo.py (+0/-92)
nova/db/sqlalchemy/migrate_repo/versions/015_diablo.py (+93/-0)
nova/db/sqlalchemy/models.py (+7/-1)
nova/image/fake.py (+113/-0)
nova/image/glance.py (+15/-33)
nova/image/local.py (+13/-3)
nova/image/s3.py (+8/-37)
nova/image/service.py (+27/-0)
nova/network/api.py (+15/-0)
nova/network/linux_net.py (+10/-1)
nova/network/manager.py (+1/-0)
nova/network/xenapi_net.py (+85/-0)
nova/rpc.py (+8/-3)
nova/scheduler/chance.py (+3/-1)
nova/scheduler/simple.py (+9/-3)
nova/scheduler/zone.py (+4/-1)
nova/tests/api/openstack/extensions/__init__.py (+15/-0)
nova/tests/api/openstack/test_api.py (+4/-4)
nova/tests/api/openstack/test_faults.py (+106/-23)
nova/tests/api/openstack/test_image_metadata.py (+39/-2)
nova/tests/api/openstack/test_images.py (+463/-37)
nova/tests/api/openstack/test_limits.py (+13/-5)
nova/tests/api/openstack/test_servers.py (+371/-66)
nova/tests/api/openstack/test_shared_ip_groups.py (+27/-3)
nova/tests/api/openstack/test_versions.py (+26/-0)
nova/tests/db/fakes.py (+61/-26)
nova/tests/fake_utils.py (+10/-7)
nova/tests/image/test_glance.py (+54/-9)
nova/tests/integrated/api/client.py (+37/-3)
nova/tests/integrated/integrated_helpers.py (+99/-24)
nova/tests/integrated/test_extensions.py (+44/-0)
nova/tests/integrated/test_login.py (+5/-16)
nova/tests/integrated/test_servers.py (+184/-0)
nova/tests/integrated/test_volumes.py (+295/-0)
nova/tests/integrated/test_xml.py (+56/-0)
nova/tests/test_auth.py (+4/-4)
nova/tests/test_cloud.py (+77/-1)
nova/tests/test_compute.py (+22/-9)
nova/tests/test_console.py (+1/-1)
nova/tests/test_instance_types.py (+5/-1)
nova/tests/test_quota.py (+11/-6)
nova/tests/test_scheduler.py (+1/-1)
nova/tests/test_virt.py (+46/-2)
nova/tests/test_volume.py (+1/-1)
nova/tests/test_xenapi.py (+60/-51)
nova/virt/disk.py (+35/-0)
nova/virt/driver.py (+18/-9)
nova/virt/fake.py (+15/-10)
nova/virt/hyperv.py (+4/-0)
nova/virt/libvirt.xml.template (+24/-10)
nova/virt/libvirt_conn.py (+292/-70)
nova/virt/vmwareapi/vim.py (+29/-25)
nova/virt/vmwareapi_conn.py (+2/-1)
nova/virt/xenapi/fake.py (+107/-65)
nova/virt/xenapi/network_utils.py (+17/-2)
nova/virt/xenapi/vm_utils.py (+29/-8)
nova/virt/xenapi/vmops.py (+142/-94)
nova/virt/xenapi_conn.py (+8/-1)
nova/vnc/__init__.py (+34/-0)
nova/vnc/auth.py (+138/-0)
nova/vnc/proxy.py (+131/-0)
nova/volume/driver.py (+75/-2)
nova/wsgi.py (+42/-5)
plugins/xenserver/xenapi/etc/xapi.d/plugins/agent (+77/-6)
plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py (+19/-16)
po/ast.po (+2487/-1769)
po/cs.po (+2508/-1777)
po/da.po (+2488/-1770)
po/de.po (+2531/-1779)
po/es.po (+3000/-1820)
po/it.po (+2541/-1784)
po/ja.po (+2989/-1791)
po/pt_BR.po (+2677/-1798)
po/ru.po (+2608/-1779)
po/uk.po (+2522/-1775)
po/zh_CN.po (+2585/-1780)
setup.py (+16/-0)
smoketests/test_admin.py (+1/-1)
smoketests/test_sysadmin.py (+3/-2)
tools/euca-get-ajax-console (+6/-0)
tools/eventlet-patch (+24/-0)
tools/install_venv.py (+6/-0)
tools/pip-requires (+1/-0)
To merge this branch: bzr merge lp:~midokura/nova/network-service
Reviewer Review Type Date Requested Status
NTT PF Lab. Pending
Review via email: mp+57303@code.launchpad.net

Description of the change

Merged trunk

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.bzrignore'
--- .bzrignore 2011-02-21 20:35:30 +0000
+++ .bzrignore 2011-04-12 11:01:25 +0000
@@ -5,12 +5,11 @@
5keys5keys
6networks6networks
7nova.sqlite7nova.sqlite
8CA/cacert.pem8CA
9CA/crl.pem
10CA/index.txt*
11CA/openssl.cnf
12CA/serial*
13CA/newcerts/*.pem
14CA/private/cakey.pem
15nova/vcsversion.py9nova/vcsversion.py
16*.DS_Store10*.DS_Store
11.project
12.pydevproject
13clean.sqlite
14run_tests.log
15tests.sqlite
1716
=== modified file 'Authors'
--- Authors 2011-03-25 13:38:57 +0000
+++ Authors 2011-04-12 11:01:25 +0000
@@ -12,6 +12,7 @@
12Chmouel Boudjnah <chmouel@chmouel.com>12Chmouel Boudjnah <chmouel@chmouel.com>
13Chris Behrens <cbehrens@codestud.com>13Chris Behrens <cbehrens@codestud.com>
14Christian Berendt <berendt@b1-systems.de>14Christian Berendt <berendt@b1-systems.de>
15Chuck Short <zulcss@ubuntu.com>
15Cory Wright <corywright@gmail.com>16Cory Wright <corywright@gmail.com>
16Dan Prince <dan.prince@rackspace.com>17Dan Prince <dan.prince@rackspace.com>
17David Pravec <David.Pravec@danix.org>18David Pravec <David.Pravec@danix.org>
@@ -30,7 +31,9 @@
30Jesse Andrews <anotherjesse@gmail.com>31Jesse Andrews <anotherjesse@gmail.com>
31Joe Heck <heckj@mac.com>32Joe Heck <heckj@mac.com>
32Joel Moore <joelbm24@gmail.com>33Joel Moore <joelbm24@gmail.com>
34Johannes Erdfelt <johannes.erdfelt@rackspace.com>
33John Dewey <john@dewey.ws>35John Dewey <john@dewey.ws>
36John Tran <jtran@attinteractive.com>
34Jonathan Bryce <jbryce@jbryce.com>37Jonathan Bryce <jbryce@jbryce.com>
35Jordan Rinke <jordan@openstack.org>38Jordan Rinke <jordan@openstack.org>
36Josh Durgin <joshd@hq.newdream.net>39Josh Durgin <joshd@hq.newdream.net>
3740
=== modified file 'MANIFEST.in'
--- MANIFEST.in 2011-03-14 20:10:11 +0000
+++ MANIFEST.in 2011-04-12 11:01:25 +0000
@@ -1,7 +1,7 @@
1include HACKING LICENSE run_tests.py run_tests.sh1include HACKING LICENSE run_tests.py run_tests.sh
2include README builddeb.sh exercise_rsapi.py2include README builddeb.sh exercise_rsapi.py
3include ChangeLog MANIFEST.in pylintrc Authors3include ChangeLog MANIFEST.in pylintrc Authors
4graft CA4graft nova/CA
5graft doc5graft doc
6graft smoketests6graft smoketests
7graft tools7graft tools
88
=== modified file 'bin/nova-ajax-console-proxy'
--- bin/nova-ajax-console-proxy 2011-03-18 13:56:05 +0000
+++ bin/nova-ajax-console-proxy 2011-04-12 11:01:25 +0000
@@ -108,17 +108,17 @@
108 return "Server Error"108 return "Server Error"
109109
110 def register_listeners(self):110 def register_listeners(self):
111 class Callback:111 class TopicProxy():
112 def __call__(self, data, message):112 @staticmethod
113 if data['method'] == 'authorize_ajax_console':113 def authorize_ajax_console(context, **kwargs):
114 AjaxConsoleProxy.tokens[data['args']['token']] = \114 AjaxConsoleProxy.tokens[kwargs['token']] = \
115 {'args': data['args'], 'last_activity': time.time()}115 {'args': kwargs, 'last_activity': time.time()}
116116
117 conn = rpc.Connection.instance(new=True)117 conn = rpc.Connection.instance(new=True)
118 consumer = rpc.TopicConsumer(118 consumer = rpc.TopicAdapterConsumer(
119 connection=conn,119 connection=conn,
120 topic=FLAGS.ajax_console_proxy_topic)120 proxy=TopicProxy,
121 consumer.register_callback(Callback())121 topic=FLAGS.ajax_console_proxy_topic)
122122
123 def delete_expired_tokens():123 def delete_expired_tokens():
124 now = time.time()124 now = time.time()
@@ -130,8 +130,7 @@
130 for k in to_delete:130 for k in to_delete:
131 del AjaxConsoleProxy.tokens[k]131 del AjaxConsoleProxy.tokens[k]
132132
133 utils.LoopingCall(consumer.fetch, auto_ack=True,133 utils.LoopingCall(consumer.fetch, enable_callbacks=True).start(0.1)
134 enable_callbacks=True).start(0.1)
135 utils.LoopingCall(delete_expired_tokens).start(1)134 utils.LoopingCall(delete_expired_tokens).start(1)
136135
137if __name__ == '__main__':136if __name__ == '__main__':
138137
=== modified file 'bin/nova-dhcpbridge'
--- bin/nova-dhcpbridge 2011-03-14 17:59:41 +0000
+++ bin/nova-dhcpbridge 2011-04-12 11:01:25 +0000
@@ -48,6 +48,7 @@
48flags.DECLARE('network_size', 'nova.network.manager')48flags.DECLARE('network_size', 'nova.network.manager')
49flags.DECLARE('num_networks', 'nova.network.manager')49flags.DECLARE('num_networks', 'nova.network.manager')
50flags.DECLARE('update_dhcp_on_disassociate', 'nova.network.manager')50flags.DECLARE('update_dhcp_on_disassociate', 'nova.network.manager')
51flags.DEFINE_string('dnsmasq_interface', 'br0', 'Default Dnsmasq interface')
5152
52LOG = logging.getLogger('nova.dhcpbridge')53LOG = logging.getLogger('nova.dhcpbridge')
5354
@@ -103,7 +104,8 @@
103 utils.default_flagfile(flagfile)104 utils.default_flagfile(flagfile)
104 argv = FLAGS(sys.argv)105 argv = FLAGS(sys.argv)
105 logging.setup()106 logging.setup()
106 interface = os.environ.get('DNSMASQ_INTERFACE', 'br0')107 # check ENV first so we don't break any older deploys
108 interface = os.environ.get('DNSMASQ_INTERFACE', FLAGS.dnsmasq_interface)
107 if int(os.environ.get('TESTING', '0')):109 if int(os.environ.get('TESTING', '0')):
108 from nova.tests import fake_flags110 from nova.tests import fake_flags
109 action = argv[1]111 action = argv[1]
110112
=== modified file 'bin/nova-manage'
--- bin/nova-manage 2011-04-07 14:25:01 +0000
+++ bin/nova-manage 2011-04-12 11:01:25 +0000
@@ -528,6 +528,49 @@
528class VmCommands(object):528class VmCommands(object):
529 """Class for mangaging VM instances."""529 """Class for mangaging VM instances."""
530530
531 def list(self, host=None):
532 """Show a list of all instances
533
534 :param host: show all instance on specified host.
535 :param instance: show specificed instance.
536 """
537 print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
538 " %-10s %-10s %-10s %-5s" % (
539 _('instance'),
540 _('node'),
541 _('type'),
542 _('state'),
543 _('launched'),
544 _('image'),
545 _('kernel'),
546 _('ramdisk'),
547 _('project'),
548 _('user'),
549 _('zone'),
550 _('index'))
551
552 if host == None:
553 instances = db.instance_get_all(context.get_admin_context())
554 else:
555 instances = db.instance_get_all_by_host(
556 context.get_admin_context(), host)
557
558 for instance in instances:
559 print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
560 " %-10s %-10s %-10s %-5d" % (
561 instance['hostname'],
562 instance['host'],
563 instance['instance_type'],
564 instance['state_description'],
565 instance['launched_at'],
566 instance['image_id'],
567 instance['kernel_id'],
568 instance['ramdisk_id'],
569 instance['project_id'],
570 instance['user_id'],
571 instance['availability_zone'],
572 instance['launch_index'])
573
531 def live_migration(self, ec2_id, dest):574 def live_migration(self, ec2_id, dest):
532 """Migrates a running instance to a new machine.575 """Migrates a running instance to a new machine.
533576
@@ -659,15 +702,6 @@
659 {"method": "update_available_resource"})702 {"method": "update_available_resource"})
660703
661704
662class LogCommands(object):
663 def request(self, request_id, logfile='/var/log/nova.log'):
664 """Show all fields in the log for the given request. Assumes you
665 haven't changed the log format too much.
666 ARGS: request_id [logfile]"""
667 lines = utils.execute("cat %s | grep '\[%s '" % (logfile, request_id))
668 print re.sub('#012', "\n", "\n".join(lines))
669
670
671class DbCommands(object):705class DbCommands(object):
672 """Class for managing the database."""706 """Class for managing the database."""
673707
@@ -683,49 +717,6 @@
683 print migration.db_version()717 print migration.db_version()
684718
685719
686class InstanceCommands(object):
687 """Class for managing instances."""
688
689 def list(self, host=None, instance=None):
690 """Show a list of all instances"""
691 print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
692 " %-10s %-10s %-10s %-5s" % (
693 _('instance'),
694 _('node'),
695 _('type'),
696 _('state'),
697 _('launched'),
698 _('image'),
699 _('kernel'),
700 _('ramdisk'),
701 _('project'),
702 _('user'),
703 _('zone'),
704 _('index'))
705
706 if host == None:
707 instances = db.instance_get_all(context.get_admin_context())
708 else:
709 instances = db.instance_get_all_by_host(
710 context.get_admin_context(), host)
711
712 for instance in instances:
713 print "%-10s %-15s %-10s %-10s %-19s %-12s %-12s %-12s" \
714 " %-10s %-10s %-10s %-5d" % (
715 instance['hostname'],
716 instance['host'],
717 instance['instance_type'],
718 instance['state_description'],
719 instance['launched_at'],
720 instance['image_id'],
721 instance['kernel_id'],
722 instance['ramdisk_id'],
723 instance['project_id'],
724 instance['user_id'],
725 instance['availability_zone'],
726 instance['launch_index'])
727
728
729class VolumeCommands(object):720class VolumeCommands(object):
730 """Methods for dealing with a cloud in an odd state"""721 """Methods for dealing with a cloud in an odd state"""
731722
@@ -836,7 +827,7 @@
836 elif name == "--all":827 elif name == "--all":
837 inst_types = instance_types.get_all_types(True)828 inst_types = instance_types.get_all_types(True)
838 else:829 else:
839 inst_types = instance_types.get_instance_type(name)830 inst_types = instance_types.get_instance_type_by_name(name)
840 except exception.DBError, e:831 except exception.DBError, e:
841 _db_error(e)832 _db_error(e)
842 if isinstance(inst_types.values()[0], dict):833 if isinstance(inst_types.values()[0], dict):
@@ -852,20 +843,17 @@
852 def __init__(self, *args, **kwargs):843 def __init__(self, *args, **kwargs):
853 self.image_service = utils.import_object(FLAGS.image_service)844 self.image_service = utils.import_object(FLAGS.image_service)
854845
855 def _register(self, image_type, disk_format, container_format,846 def _register(self, container_format, disk_format,
856 path, owner, name=None, is_public='T',847 path, owner, name=None, is_public='T',
857 architecture='x86_64', kernel_id=None, ramdisk_id=None):848 architecture='x86_64', kernel_id=None, ramdisk_id=None):
858 meta = {'is_public': True,849 meta = {'is_public': (is_public == 'T'),
859 'name': name,850 'name': name,
851 'container_format': container_format,
860 'disk_format': disk_format,852 'disk_format': disk_format,
861 'container_format': container_format,
862 'properties': {'image_state': 'available',853 'properties': {'image_state': 'available',
863 'owner': owner,854 'project_id': owner,
864 'type': image_type,
865 'architecture': architecture,855 'architecture': architecture,
866 'image_location': 'local',856 'image_location': 'local'}}
867 'is_public': (is_public == 'T')}}
868 print image_type, meta
869 if kernel_id:857 if kernel_id:
870 meta['properties']['kernel_id'] = int(kernel_id)858 meta['properties']['kernel_id'] = int(kernel_id)
871 if ramdisk_id:859 if ramdisk_id:
@@ -890,16 +878,18 @@
890 ramdisk_id = self.ramdisk_register(ramdisk, owner, None,878 ramdisk_id = self.ramdisk_register(ramdisk, owner, None,
891 is_public, architecture)879 is_public, architecture)
892 self.image_register(image, owner, name, is_public,880 self.image_register(image, owner, name, is_public,
893 architecture, kernel_id, ramdisk_id)881 architecture, 'ami', 'ami',
882 kernel_id, ramdisk_id)
894883
895 def image_register(self, path, owner, name=None, is_public='T',884 def image_register(self, path, owner, name=None, is_public='T',
896 architecture='x86_64', kernel_id=None, ramdisk_id=None,885 architecture='x86_64', container_format='bare',
897 disk_format='ami', container_format='ami'):886 disk_format='raw', kernel_id=None, ramdisk_id=None):
898 """Uploads an image into the image_service887 """Uploads an image into the image_service
899 arguments: path owner [name] [is_public='T'] [architecture='x86_64']888 arguments: path owner [name] [is_public='T'] [architecture='x86_64']
889 [container_format='bare'] [disk_format='raw']
900 [kernel_id=None] [ramdisk_id=None]890 [kernel_id=None] [ramdisk_id=None]
901 [disk_format='ami'] [container_format='ami']"""891 """
902 return self._register('machine', disk_format, container_format, path,892 return self._register(container_format, disk_format, path,
903 owner, name, is_public, architecture,893 owner, name, is_public, architecture,
904 kernel_id, ramdisk_id)894 kernel_id, ramdisk_id)
905895
@@ -908,7 +898,7 @@
908 """Uploads a kernel into the image_service898 """Uploads a kernel into the image_service
909 arguments: path owner [name] [is_public='T'] [architecture='x86_64']899 arguments: path owner [name] [is_public='T'] [architecture='x86_64']
910 """900 """
911 return self._register('kernel', 'aki', 'aki', path, owner, name,901 return self._register('aki', 'aki', path, owner, name,
912 is_public, architecture)902 is_public, architecture)
913903
914 def ramdisk_register(self, path, owner, name=None, is_public='T',904 def ramdisk_register(self, path, owner, name=None, is_public='T',
@@ -916,7 +906,7 @@
916 """Uploads a ramdisk into the image_service906 """Uploads a ramdisk into the image_service
917 arguments: path owner [name] [is_public='T'] [architecture='x86_64']907 arguments: path owner [name] [is_public='T'] [architecture='x86_64']
918 """908 """
919 return self._register('ramdisk', 'ari', 'ari', path, owner, name,909 return self._register('ari', 'ari', path, owner, name,
920 is_public, architecture)910 is_public, architecture)
921911
922 def _lookup(self, old_image_id):912 def _lookup(self, old_image_id):
@@ -933,16 +923,17 @@
933 'ramdisk': 'ari'}923 'ramdisk': 'ari'}
934 container_format = mapping[old['type']]924 container_format = mapping[old['type']]
935 disk_format = container_format925 disk_format = container_format
926 if container_format == 'ami' and not old.get('kernelId'):
927 container_format = 'bare'
928 disk_format = 'raw'
936 new = {'disk_format': disk_format,929 new = {'disk_format': disk_format,
937 'container_format': container_format,930 'container_format': container_format,
938 'is_public': True,931 'is_public': old['isPublic'],
939 'name': old['imageId'],932 'name': old['imageId'],
940 'properties': {'image_state': old['imageState'],933 'properties': {'image_state': old['imageState'],
941 'owner': old['imageOwnerId'],934 'project_id': old['imageOwnerId'],
942 'architecture': old['architecture'],935 'architecture': old['architecture'],
943 'type': old['type'],936 'image_location': old['imageLocation']}}
944 'image_location': old['imageLocation'],
945 'is_public': old['isPublic']}}
946 if old.get('kernelId'):937 if old.get('kernelId'):
947 new['properties']['kernel_id'] = self._lookup(old['kernelId'])938 new['properties']['kernel_id'] = self._lookup(old['kernelId'])
948 if old.get('ramdiskId'):939 if old.get('ramdiskId'):
@@ -1006,13 +997,11 @@
1006 ('floating', FloatingIpCommands),997 ('floating', FloatingIpCommands),
1007 ('vm', VmCommands),998 ('vm', VmCommands),
1008 ('service', ServiceCommands),999 ('service', ServiceCommands),
1009 ('log', LogCommands),
1010 ('db', DbCommands),1000 ('db', DbCommands),
1011 ('volume', VolumeCommands),1001 ('volume', VolumeCommands),
1012 ('instance_type', InstanceTypeCommands),1002 ('instance_type', InstanceTypeCommands),
1013 ('image', ImageCommands),1003 ('image', ImageCommands),
1014 ('flavor', InstanceTypeCommands),1004 ('flavor', InstanceTypeCommands)]
1015 ('instance', InstanceCommands)]
10161005
10171006
1018def lazy_match(name, key_value_tuples):1007def lazy_match(name, key_value_tuples):
@@ -1055,8 +1044,8 @@
1055 script_name = argv.pop(0)1044 script_name = argv.pop(0)
1056 if len(argv) < 1:1045 if len(argv) < 1:
1057 print script_name + " category action [<args>]"1046 print script_name + " category action [<args>]"
1058 print "Available categories:"1047 print _("Available categories:")
1059 for k, _ in CATEGORIES:1048 for k, _v in CATEGORIES:
1060 print "\t%s" % k1049 print "\t%s" % k
1061 sys.exit(2)1050 sys.exit(2)
1062 category = argv.pop(0)1051 category = argv.pop(0)
@@ -1067,7 +1056,7 @@
1067 actions = methods_of(command_object)1056 actions = methods_of(command_object)
1068 if len(argv) < 1:1057 if len(argv) < 1:
1069 print script_name + " category action [<args>]"1058 print script_name + " category action [<args>]"
1070 print "Available actions for %s category:" % category1059 print _("Available actions for %s category:") % category
1071 for k, _v in actions:1060 for k, _v in actions:
1072 print "\t%s" % k1061 print "\t%s" % k
1073 sys.exit(2)1062 sys.exit(2)
@@ -1079,9 +1068,12 @@
1079 fn(*argv)1068 fn(*argv)
1080 sys.exit(0)1069 sys.exit(0)
1081 except TypeError:1070 except TypeError:
1082 print "Possible wrong number of arguments supplied"1071 print _("Possible wrong number of arguments supplied")
1083 print "%s %s: %s" % (category, action, fn.__doc__)1072 print "%s %s: %s" % (category, action, fn.__doc__)
1084 raise1073 raise
1074 except Exception:
1075 print _("Command failed, please check log for more info")
1076 raise
10851077
1086if __name__ == '__main__':1078if __name__ == '__main__':
1087 main()1079 main()
10881080
=== added file 'bin/nova-vncproxy'
--- bin/nova-vncproxy 1970-01-01 00:00:00 +0000
+++ bin/nova-vncproxy 2011-04-12 11:01:25 +0000
@@ -0,0 +1,101 @@
1#!/usr/bin/env python
2# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
4# Copyright (c) 2010 Openstack, LLC.
5# All Rights Reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18
19"""VNC Console Proxy Server."""
20
21import eventlet
22import gettext
23import os
24import sys
25
26possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
27 os.pardir,
28 os.pardir))
29if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
30 sys.path.insert(0, possible_topdir)
31
32gettext.install('nova', unicode=1)
33
34from nova import flags
35from nova import log as logging
36from nova import service
37from nova import utils
38from nova import wsgi
39from nova import version
40from nova.vnc import auth
41from nova.vnc import proxy
42
43
44LOG = logging.getLogger('nova.vnc-proxy')
45
46
47FLAGS = flags.FLAGS
48flags.DEFINE_string('vncproxy_wwwroot', '/var/lib/nova/noVNC/',
49 'Full path to noVNC directory')
50flags.DEFINE_boolean('vnc_debug', False,
51 'Enable debugging features, like token bypassing')
52flags.DEFINE_integer('vncproxy_port', 6080,
53 'Port that the VNC proxy should bind to')
54flags.DEFINE_string('vncproxy_host', '0.0.0.0',
55 'Address that the VNC proxy should bind to')
56flags.DEFINE_integer('vnc_token_ttl', 300,
57 'How many seconds before deleting tokens')
58flags.DEFINE_string('vncproxy_manager', 'nova.vnc.auth.VNCProxyAuthManager',
59 'Manager for vncproxy auth')
60
61flags.DEFINE_flag(flags.HelpFlag())
62flags.DEFINE_flag(flags.HelpshortFlag())
63flags.DEFINE_flag(flags.HelpXMLFlag())
64
65
66if __name__ == "__main__":
67 utils.default_flagfile()
68 FLAGS(sys.argv)
69 logging.setup()
70
71 LOG.audit(_("Starting nova-vnc-proxy node (version %s)"),
72 version.version_string_with_vcs())
73
74 if not (os.path.exists(FLAGS.vncproxy_wwwroot) and
75 os.path.exists(FLAGS.vncproxy_wwwroot + '/vnc_auto.html')):
76 LOG.info(_("Missing vncproxy_wwwroot (version %s)"),
77 FLAGS.vncproxy_wwwroot)
78 LOG.info(_("You need a slightly modified version of noVNC "
79 "to work with the nova-vnc-proxy"))
80 LOG.info(_("Check out the most recent nova noVNC code: %s"),
81 "git://github.com/sleepsonthefloor/noVNC.git")
82 LOG.info(_("And drop it in %s"), FLAGS.vncproxy_wwwroot)
83 exit(1)
84
85 app = proxy.WebsocketVNCProxy(FLAGS.vncproxy_wwwroot)
86
87 LOG.audit(_("Allowing access to the following files: %s"),
88 app.get_whitelist())
89
90 with_logging = auth.LoggingMiddleware(app)
91
92 if FLAGS.vnc_debug:
93 with_auth = proxy.DebugMiddleware(with_logging)
94 else:
95 with_auth = auth.VNCNovaAuthMiddleware(with_logging)
96
97 service.serve()
98
99 server = wsgi.Server()
100 server.start(with_auth, FLAGS.vncproxy_port, host=FLAGS.vncproxy_host)
101 server.wait()
0102
=== added file 'doc/source/devref/zone.rst'
--- doc/source/devref/zone.rst 1970-01-01 00:00:00 +0000
+++ doc/source/devref/zone.rst 2011-04-12 11:01:25 +0000
@@ -0,0 +1,127 @@
1..
2 Copyright 2010-2011 OpenStack LLC
3 All Rights Reserved.
4
5 Licensed under the Apache License, Version 2.0 (the "License"); you may
6 not use this file except in compliance with the License. You may obtain
7 a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 License for the specific language governing permissions and limitations
15 under the License.
16
17Zones
18=====
19
20A Nova deployment is called a Zone. At the very least a Zone requires an API node, a Scheduler node, a database and RabbitMQ. Pushed further a Zone may contain many API nodes, many Scheduler, Volume, Network and Compute nodes as well as a cluster of databases and RabbitMQ servers. A Zone allows you to partition your deployments into logical groups for load balancing and instance distribution.
21
22The idea behind Zones is, if a particular deployment is not capable of servicing a particular request, the request may be forwarded to (child) Zones for possible processing. Zones may be nested in a tree fashion.
23
24Zones only know about their immediate children, they do not know about their parent Zones and may in fact have more than one parent. Likewise, a Zone's children may themselves have child Zones.
25
26Zones share nothing. They communicate via the public OpenStack API only. No database, queue, user or project definition is shared between Zones.
27
28
29Capabilities
30------------
31Routing between Zones is based on the Capabilities of that Zone. Capabilities are nothing more than key/value pairs. Values are multi-value, with each value separated with a semicolon (`;`). When expressed as a string they take the form:
32
33::
34
35 key=value;value;value, key=value;value;value
36
37Zones have Capabilities which are general to the Zone and are set via `--zone-capabilities` flag. Zones also have dynamic per-service Capabilities. Services derived from `nova.manager.SchedulerDependentManager` (such as Compute, Volume and Network) can set these capabilities by calling the `update_service_capabilities()` method on their `Manager` base class. These capabilities will be periodically sent to the Scheduler service automatically. The rate at which these updates are sent is controlled by the `--periodic_interval` flag.
38
39Flow within a Zone
40------------------
41The brunt of the work within a Zone is done in the Scheduler Service. The Scheduler is responsible for:
42- collecting capability messages from the Compute, Volume and Network nodes,
43- polling the child Zones for their status and
44- providing data to the Distributed Scheduler for performing load balancing calculations
45
46Inter-service communication within a Zone is done with RabbitMQ. Each class of Service (Compute, Volume and Network) has both a named message exchange (particular to that host) and a general message exchange (particular to that class of service). Messages sent to these exchanges are picked off in round-robin fashion. Zones introduce a new fan-out exchange per service. Messages sent to the fan-out exchange are picked up by all services of a particular class. This fan-out exchange is used by the Scheduler services to receive capability messages from the Compute, Volume and Network nodes.
47
48These capability messages are received by the Scheduler services and stored in the `ZoneManager` object. The SchedulerManager object has a reference to the `ZoneManager` it can use for load balancing.
49
50The `ZoneManager` also polls the child Zones periodically to gather their capabilities to aid in decision making. This is done via the OpenStack API `/v1.0/zones/info` REST call. This also captures the name of each child Zone. The Zone name is set via the `--zone-name` flag (and defaults to "nova").
51
52Zone administrative functions
53-----------------------------
54Zone administrative operations are usually done using python-novaclient_
55
56.. _python-novaclient: https://github.com/rackspace/python-novaclient
57
58In order to use the Zone operations, be sure to enable administrator operations in OpenStack API by setting the `--allow_admin_api=true` flag.
59
60Finally you need to enable Zone Forwarding. This will be used by the Distributed Scheduler initiative currently underway. Set `--enable_zone_routing=true` to enable this feature.
61
62Find out about this Zone
63------------------------
64In any Zone you can find the Zone's name and capabilities with the ``nova zone-info`` command.
65
66::
67
68 alice@novadev:~$ nova zone-info
69 +-----------------+---------------+
70 | Property | Value |
71 +-----------------+---------------+
72 | compute_cpu | 0.7,0.7 |
73 | compute_disk | 123000,123000 |
74 | compute_network | 800,800 |
75 | hypervisor | xenserver |
76 | name | nova |
77 | network_cpu | 0.7,0.7 |
78 | network_disk | 123000,123000 |
79 | network_network | 800,800 |
80 | os | linux |
81 +-----------------+---------------+
82
83This equates to a GET operation on `.../zones/info`. If you have no child Zones defined you'll usually only get back the default `name`, `hypervisor` and `os` capabilities. Otherwise you'll get back a tuple of min, max values for each capabilities of all the hosts of all the services running in the child zone. These take the `<service>_<capability> = <min>,<max>` format.
84
85Adding a child Zone
86-------------------
87Any Zone can be a parent Zone. Children are associated to a Zone. The Zone where this command originates from is known as the Parent Zone. Routing is only ever conducted from a Zone to its children, never the other direction. From a parent zone you can add a child zone with the following command:
88
89::
90
91 nova zone-add <child zone api url> <username> <nova api key>
92
93You can get the `child zone api url`, `nova api key` and `username` from the `novarc` file in the child zone. For example:
94
95::
96
97 export NOVA_API_KEY="3bd1af06-6435-4e23-a827-413b2eb86934"
98 export NOVA_USERNAME="alice"
99 export NOVA_URL="http://192.168.2.120:8774/v1.0/"
100
101
102This equates to a POST operation to `.../zones/` to add a new zone. No connection attempt to the child zone is done when this command. It only puts an entry in the db at this point. After about 30 seconds the `ZoneManager` in the Scheduler services will attempt to talk to the child zone and get its information.
103
104Getting a list of child Zones
105-----------------------------
106
107::
108
109 nova zone-list
110
111 alice@novadev:~$ nova zone-list
112 +----+-------+-----------+--------------------------------------------+---------------------------------+
113 | ID | Name | Is Active | Capabilities | API URL |
114 +----+-------+-----------+--------------------------------------------+---------------------------------+
115 | 2 | zone1 | True | hypervisor=xenserver;kvm, os=linux;windows | http://192.168.2.108:8774/v1.0/ |
116 | 3 | zone2 | True | hypervisor=xenserver;kvm, os=linux;windows | http://192.168.2.115:8774/v1.0/ |
117 +----+-------+-----------+--------------------------------------------+---------------------------------+
118
119This equates to a GET operation to `.../zones`.
120
121Removing a child Zone
122---------------------
123::
124
125 nova zone-delete <N>
126
127This equates to a DELETE call to `.../zones/N`. The Zone with ID=N will be removed. This will only remove the zone entry from the current (parent) Zone, no child Zones are affected. Removing a Child Zone doesn't affect any other part of the hierarchy.
0128
=== modified file 'doc/source/man/novamanage.rst'
--- doc/source/man/novamanage.rst 2011-03-08 20:28:11 +0000
+++ doc/source/man/novamanage.rst 2011-04-12 11:01:25 +0000
@@ -240,6 +240,16 @@
240240
241 Converts all images in directory from the old (Bexar) format to the new format.241 Converts all images in directory from the old (Bexar) format to the new format.
242242
243Nova VM
244~~~~~~~~~~~
245
246``nova-manage vm list [host]``
247 Show a list of all instances. Accepts optional hostname (to show only instances on specific host).
248
249``nova-manage live-migration <ec2_id> <destination host name>``
250 Live migrate instance from current host to destination host. Requires instance id (which comes from euca-describe-instance) and destination host name (which can be found from nova-manage service list).
251
252
243FILES253FILES
244========254========
245255
246256
=== added file 'doc/source/runnova/vncconsole.rst'
--- doc/source/runnova/vncconsole.rst 1970-01-01 00:00:00 +0000
+++ doc/source/runnova/vncconsole.rst 2011-04-12 11:01:25 +0000
@@ -0,0 +1,76 @@
1..
2 Copyright 2010-2011 United States Government as represented by the
3 Administrator of the National Aeronautics and Space Administration.
4 All Rights Reserved.
5
6 Licensed under the Apache License, Version 2.0 (the "License"); you may
7 not use this file except in compliance with the License. You may obtain
8 a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15 License for the specific language governing permissions and limitations
16 under the License.
17
18Getting Started with the VNC Proxy
19==================================
20
21The VNC Proxy is an OpenStack component that allows users of Nova to access
22their instances through a websocket enabled browser (like Google Chrome).
23
24A VNC Connection works like so:
25
26* User connects over an api and gets a url like http://ip:port/?token=xyz
27* User pastes url in browser
28* Browser connects to VNC Proxy though a websocket enabled client like noVNC
29* VNC Proxy authorizes users token, maps the token to a host and port of an
30 instance's VNC server
31* VNC Proxy initiates connection to VNC server, and continues proxying until
32 the session ends
33
34
35Configuring the VNC Proxy
36-------------------------
37nova-vncproxy requires a websocket enabled html client to work properly. At
38this time, the only tested client is a slightly modified fork of noVNC, which
39you can at find http://github.com/openstack/noVNC.git
40
41.. todo:: add instruction for installing from package
42
43noVNC must be in the location specified by --vncproxy_wwwroot, which defaults
44to /var/lib/nova/noVNC. nova-vncproxy will fail to launch until this code
45is properly installed.
46
47By default, nova-vncproxy binds 0.0.0.0:6080. This can be configured with:
48
49* --vncproxy_port=[port]
50* --vncproxy_host=[host]
51
52
53Enabling VNC Consoles in Nova
54-----------------------------
55At the moment, VNC support is supported only when using libvirt. To enable VNC
56Console, configure the following flags:
57
58* --vnc_console_proxy_url=http://[proxy_host]:[proxy_port] - proxy_port
59 defaults to 6080. This url must point to nova-vncproxy
60* --vnc_enabled=[True|False] - defaults to True. If this flag is not set your
61 instances will launch without vnc support.
62
63
64Getting an instance's VNC Console
65---------------------------------
66You can access an instance's VNC Console url in the following methods:
67
68* Using the direct api:
69 eg: 'stack --user=admin --project=admin compute get_vnc_console instance_id=1'
70* Support for Dashboard, and the Openstack API will be forthcoming
71
72
73Accessing VNC Consoles without a web browser
74--------------------------------------------
75At the moment, VNC Consoles are only supported through the web browser, but
76more general VNC support is in the works.
077
=== added directory 'etc/nova'
=== renamed file 'etc/api-paste.ini' => 'etc/nova/api-paste.ini'
=== renamed directory 'CA' => 'nova/CA'
=== modified file 'nova/CA/geninter.sh'
--- CA/geninter.sh 2010-11-06 00:02:36 +0000
+++ nova/CA/geninter.sh 2011-04-12 11:01:25 +0000
@@ -23,7 +23,7 @@
23cd projects/$NAME23cd projects/$NAME
24cp ../../openssl.cnf.tmpl openssl.cnf24cp ../../openssl.cnf.tmpl openssl.cnf
25sed -i -e s/%USERNAME%/$NAME/g openssl.cnf25sed -i -e s/%USERNAME%/$NAME/g openssl.cnf
26mkdir certs crl newcerts private26mkdir -p certs crl newcerts private
27openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes27openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
28echo "10" > serial28echo "10" > serial
29touch index.txt29touch index.txt
3030
=== modified file 'nova/CA/genrootca.sh'
--- CA/genrootca.sh 2010-11-06 00:02:36 +0000
+++ nova/CA/genrootca.sh 2011-04-12 11:01:25 +0000
@@ -20,8 +20,9 @@
20then20then
21 echo "Not installing, it's already done."21 echo "Not installing, it's already done."
22else22else
23 cp openssl.cnf.tmpl openssl.cnf23 cp "$(dirname $0)/openssl.cnf.tmpl" openssl.cnf
24 sed -i -e s/%USERNAME%/ROOT/g openssl.cnf24 sed -i -e s/%USERNAME%/ROOT/g openssl.cnf
25 mkdir -p certs crl newcerts private
25 openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes26 openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes
26 touch index.txt27 touch index.txt
27 echo "10" > serial28 echo "10" > serial
2829
=== modified file 'nova/CA/openssl.cnf.tmpl'
--- CA/openssl.cnf.tmpl 2011-03-16 18:22:29 +0000
+++ nova/CA/openssl.cnf.tmpl 2011-04-12 11:01:25 +0000
@@ -41,9 +41,13 @@
41certopt = default_ca41certopt = default_ca
42policy = policy_match42policy = policy_match
4343
44# NOTE(dprince): stateOrProvinceName must be 'supplied' or 'optional' to
45# work around a stateOrProvince printable string UTF8 mismatch on
46# RHEL 6 and Fedora 14 (using openssl-1.0.0-4.el6.x86_64 or
47# openssl-1.0.0d-1.fc14.x86_64)
44[ policy_match ]48[ policy_match ]
45countryName = match49countryName = match
46stateOrProvinceName = match50stateOrProvinceName = supplied
47organizationName = optional51organizationName = optional
48organizationalUnitName = optional52organizationalUnitName = optional
49commonName = supplied53commonName = supplied
5054
=== removed file 'nova/adminclient.py'
--- nova/adminclient.py 2011-02-24 19:36:15 +0000
+++ nova/adminclient.py 1970-01-01 00:00:00 +0000
@@ -1,476 +0,0 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010 United States Government as represented by the
4# Administrator of the National Aeronautics and Space Administration.
5# All Rights Reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License. You may obtain
9# a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations
17# under the License.
18"""
19Nova User API client library.
20"""
21
22import base64
23import boto
24import boto.exception
25import httplib
26import re
27import string
28
29from boto.ec2.regioninfo import RegionInfo
30
31
32DEFAULT_CLC_URL = 'http://127.0.0.1:8773'
33DEFAULT_REGION = 'nova'
34
35
36class UserInfo(object):
37 """
38 Information about a Nova user, as parsed through SAX.
39
40 **Fields Include**
41
42 * username
43 * accesskey
44 * secretkey
45 * file (optional) containing zip of X509 cert & rc file
46
47 """
48
49 def __init__(self, connection=None, username=None, endpoint=None):
50 self.connection = connection
51 self.username = username
52 self.endpoint = endpoint
53
54 def __repr__(self):
55 return 'UserInfo:%s' % self.username
56
57 def startElement(self, name, attrs, connection):
58 return None
59
60 def endElement(self, name, value, connection):
61 if name == 'username':
62 self.username = str(value)
63 elif name == 'file':
64 self.file = base64.b64decode(str(value))
65 elif name == 'accesskey':
66 self.accesskey = str(value)
67 elif name == 'secretkey':
68 self.secretkey = str(value)
69
70
71class UserRole(object):
72 """
73 Information about a Nova user's role, as parsed through SAX.
74
75 **Fields include**
76
77 * role
78
79 """
80
81 def __init__(self, connection=None):
82 self.connection = connection
83 self.role = None
84
85 def __repr__(self):
86 return 'UserRole:%s' % self.role
87
88 def startElement(self, name, attrs, connection):
89 return None
90
91 def endElement(self, name, value, connection):
92 if name == 'role':
93 self.role = value
94 else:
95 setattr(self, name, str(value))
96
97
98class ProjectInfo(object):
99 """
100 Information about a Nova project, as parsed through SAX.
101
102 **Fields include**
103
104 * projectname
105 * description
106 * projectManagerId
107 * memberIds
108
109 """
110
111 def __init__(self, connection=None):
112 self.connection = connection
113 self.projectname = None
114 self.description = None
115 self.projectManagerId = None
116 self.memberIds = []
117
118 def __repr__(self):
119 return 'ProjectInfo:%s' % self.projectname
120
121 def startElement(self, name, attrs, connection):
122 return None
123
124 def endElement(self, name, value, connection):
125 if name == 'projectname':
126 self.projectname = value
127 elif name == 'description':
128 self.description = value
129 elif name == 'projectManagerId':
130 self.projectManagerId = value
131 elif name == 'memberId':
132 self.memberIds.append(value)
133 else:
134 setattr(self, name, str(value))
135
136
137class ProjectMember(object):
138 """
139 Information about a Nova project member, as parsed through SAX.
140
141 **Fields include**
142
143 * memberId
144
145 """
146
147 def __init__(self, connection=None):
148 self.connection = connection
149 self.memberId = None
150
151 def __repr__(self):
152 return 'ProjectMember:%s' % self.memberId
153
154 def startElement(self, name, attrs, connection):
155 return None
156
157 def endElement(self, name, value, connection):
158 if name == 'member':
159 self.memberId = value
160 else:
161 setattr(self, name, str(value))
162
163
164class HostInfo(object):
165 """
166 Information about a Nova Host, as parsed through SAX.
167
168 **Fields Include**
169
170 * Hostname
171 * Compute service status
172 * Volume service status
173 * Instance count
174 * Volume count
175 """
176
177 def __init__(self, connection=None):
178 self.connection = connection
179 self.hostname = None
180 self.compute = None
181 self.volume = None
182 self.instance_count = 0
183 self.volume_count = 0
184
185 def __repr__(self):
186 return 'Host:%s' % self.hostname
187
188 # this is needed by the sax parser, so ignore the ugly name
189 def startElement(self, name, attrs, connection):
190 return None
191
192 # this is needed by the sax parser, so ignore the ugly name
193 def endElement(self, name, value, connection):
194 fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name))
195 setattr(self, fixed_name, value)
196
197
198class Vpn(object):
199 """
200 Information about a Vpn, as parsed through SAX
201
202 **Fields Include**
203
204 * instance_id
205 * project_id
206 * public_ip
207 * public_port
208 * created_at
209 * internal_ip
210 * state
211 """
212
213 def __init__(self, connection=None):
214 self.connection = connection
215 self.instance_id = None
216 self.project_id = None
217
218 def __repr__(self):
219 return 'Vpn:%s:%s' % (self.project_id, self.instance_id)
220
221 def startElement(self, name, attrs, connection):
222 return None
223
224 def endElement(self, name, value, connection):
225 fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name))
226 setattr(self, fixed_name, value)
227
228
229class InstanceType(object):
230 """
231 Information about a Nova instance type, as parsed through SAX.
232
233 **Fields include**
234
235 * name
236 * vcpus
237 * disk_gb
238 * memory_mb
239 * flavor_id
240
241 """
242
243 def __init__(self, connection=None):
244 self.connection = connection
245 self.name = None
246 self.vcpus = None
247 self.disk_gb = None
248 self.memory_mb = None
249 self.flavor_id = None
250
251 def __repr__(self):
252 return 'InstanceType:%s' % self.name
253
254 def startElement(self, name, attrs, connection):
255 return None
256
257 def endElement(self, name, value, connection):
258 if name == "memoryMb":
259 self.memory_mb = str(value)
260 elif name == "flavorId":
261 self.flavor_id = str(value)
262 elif name == "diskGb":
263 self.disk_gb = str(value)
264 else:
265 setattr(self, name, str(value))
266
267
268class NovaAdminClient(object):
269
270 def __init__(
271 self,
272 clc_url=DEFAULT_CLC_URL,
273 region=DEFAULT_REGION,
274 access_key=None,
275 secret_key=None,
276 **kwargs):
277 parts = self.split_clc_url(clc_url)
278
279 self.clc_url = clc_url
280 self.region = region
281 self.access = access_key
282 self.secret = secret_key
283 self.apiconn = boto.connect_ec2(aws_access_key_id=access_key,
284 aws_secret_access_key=secret_key,
285 is_secure=parts['is_secure'],
286 region=RegionInfo(None,
287 region,
288 parts['ip']),
289 port=parts['port'],
290 path='/services/Admin',
291 **kwargs)
292 self.apiconn.APIVersion = 'nova'
293
294 def connection_for(self, username, project, clc_url=None, region=None,
295 **kwargs):
296 """Returns a boto ec2 connection for the given username."""
297 if not clc_url:
298 clc_url = self.clc_url
299 if not region:
300 region = self.region
301 parts = self.split_clc_url(clc_url)
302 user = self.get_user(username)
303 access_key = '%s:%s' % (user.accesskey, project)
304 return boto.connect_ec2(aws_access_key_id=access_key,
305 aws_secret_access_key=user.secretkey,
306 is_secure=parts['is_secure'],
307 region=RegionInfo(None,
308 self.region,
309 parts['ip']),
310 port=parts['port'],
311 path='/services/Cloud',
312 **kwargs)
313
314 def split_clc_url(self, clc_url):
315 """Splits a cloud controller endpoint url."""
316 parts = httplib.urlsplit(clc_url)
317 is_secure = parts.scheme == 'https'
318 ip, port = parts.netloc.split(':')
319 return {'ip': ip, 'port': int(port), 'is_secure': is_secure}
320
321 def get_users(self):
322 """Grabs the list of all users."""
323 return self.apiconn.get_list('DescribeUsers', {}, [('item', UserInfo)])
324
325 def get_user(self, name):
326 """Grab a single user by name."""
327 try:
328 return self.apiconn.get_object('DescribeUser',
329 {'Name': name},
330 UserInfo)
331 except boto.exception.BotoServerError, e:
332 if e.status == 400 and e.error_code == 'NotFound':
333 return None
334 raise
335
336 def has_user(self, username):
337 """Determine if user exists."""
338 return self.get_user(username) != None
339
340 def create_user(self, username):
341 """Creates a new user, returning the userinfo object with
342 access/secret."""
343 return self.apiconn.get_object('RegisterUser', {'Name': username},
344 UserInfo)
345
346 def delete_user(self, username):
347 """Deletes a user."""
348 return self.apiconn.get_object('DeregisterUser', {'Name': username},
349 UserInfo)
350
351 def get_roles(self, project_roles=True):
352 """Returns a list of available roles."""
353 return self.apiconn.get_list('DescribeRoles',
354 {'ProjectRoles': project_roles},
355 [('item', UserRole)])
356
357 def get_user_roles(self, user, project=None):
358 """Returns a list of roles for the given user.
359
360 Omitting project will return any global roles that the user has.
361 Specifying project will return only project specific roles.
362
363 """
364 params = {'User': user}
365 if project:
366 params['Project'] = project
367 return self.apiconn.get_list('DescribeUserRoles',
368 params,
369 [('item', UserRole)])
370
371 def add_user_role(self, user, role, project=None):
372 """Add a role to a user either globally or for a specific project."""
373 return self.modify_user_role(user, role, project=project,
374 operation='add')
375
376 def remove_user_role(self, user, role, project=None):
377 """Remove a role from a user either globally or for a specific
378 project."""
379 return self.modify_user_role(user, role, project=project,
380 operation='remove')
381
382 def modify_user_role(self, user, role, project=None, operation='add',
383 **kwargs):
384 """Add or remove a role for a user and project."""
385 params = {'User': user,
386 'Role': role,
387 'Project': project,
388 'Operation': operation}
389 return self.apiconn.get_status('ModifyUserRole', params)
390
391 def get_projects(self, user=None):
392 """Returns a list of all projects."""
393 if user:
394 params = {'User': user}
395 else:
396 params = {}
397 return self.apiconn.get_list('DescribeProjects',
398 params,
399 [('item', ProjectInfo)])
400
401 def get_project(self, name):
402 """Returns a single project with the specified name."""
403 project = self.apiconn.get_object('DescribeProject',
404 {'Name': name},
405 ProjectInfo)
406
407 if project.projectname != None:
408 return project
409
410 def create_project(self, projectname, manager_user, description=None,
411 member_users=None):
412 """Creates a new project."""
413 params = {'Name': projectname,
414 'ManagerUser': manager_user,
415 'Description': description,
416 'MemberUsers': member_users}
417 return self.apiconn.get_object('RegisterProject', params, ProjectInfo)
418
419 def modify_project(self, projectname, manager_user=None, description=None):
420 """Modifies an existing project."""
421 params = {'Name': projectname,
422 'ManagerUser': manager_user,
423 'Description': description}
424 return self.apiconn.get_status('ModifyProject', params)
425
426 def delete_project(self, projectname):
427 """Permanently deletes the specified project."""
428 return self.apiconn.get_object('DeregisterProject',
429 {'Name': projectname},
430 ProjectInfo)
431
432 def get_project_members(self, name):
433 """Returns a list of members of a project."""
434 return self.apiconn.get_list('DescribeProjectMembers',
435 {'Name': name},
436 [('item', ProjectMember)])
437
438 def add_project_member(self, user, project):
439 """Adds a user to a project."""
440 return self.modify_project_member(user, project, operation='add')
441
442 def remove_project_member(self, user, project):
443 """Removes a user from a project."""
444 return self.modify_project_member(user, project, operation='remove')
445
446 def modify_project_member(self, user, project, operation='add'):
447 """Adds or removes a user from a project."""
448 params = {'User': user,
449 'Project': project,
450 'Operation': operation}
451 return self.apiconn.get_status('ModifyProjectMember', params)
452
453 def get_zip(self, user, project):
454 """Returns the content of a zip file containing novarc and access
455 credentials."""
456 params = {'Name': user, 'Project': project}
457 zip = self.apiconn.get_object('GenerateX509ForUser', params, UserInfo)
458 return zip.file
459
460 def start_vpn(self, project):
461 """
462 Starts the vpn for a user
463 """
464 return self.apiconn.get_object('StartVpn', {'Project': project}, Vpn)
465
466 def get_vpns(self):
467 """Return a list of vpn with project name"""
468 return self.apiconn.get_list('DescribeVpns', {}, [('item', Vpn)])
469
470 def get_hosts(self):
471 return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)])
472
473 def get_instance_types(self):
474 """Grabs the list of all users."""
475 return self.apiconn.get_list('DescribeInstanceTypes', {},
476 [('item', InstanceType)])
4770
=== modified file 'nova/api/direct.py'
--- nova/api/direct.py 2011-03-24 20:20:15 +0000
+++ nova/api/direct.py 2011-04-12 11:01:25 +0000
@@ -206,10 +206,14 @@
206 # NOTE(vish): make sure we have no unicode keys for py2.6.206 # NOTE(vish): make sure we have no unicode keys for py2.6.
207 params = dict([(str(k), v) for (k, v) in params.iteritems()])207 params = dict([(str(k), v) for (k, v) in params.iteritems()])
208 result = method(context, **params)208 result = method(context, **params)
209
209 if result is None or type(result) is str or type(result) is unicode:210 if result is None or type(result) is str or type(result) is unicode:
210 return result211 return result
212
211 try:213 try:
212 return self._serialize(result, req.best_match_content_type())214 content_type = req.best_match_content_type()
215 default_xmlns = self.get_default_xmlns(req)
216 return self._serialize(result, content_type, default_xmlns)
213 except:217 except:
214 raise exception.Error("returned non-serializable type: %s"218 raise exception.Error("returned non-serializable type: %s"
215 % result)219 % result)
216220
=== modified file 'nova/api/ec2/cloud.py'
--- nova/api/ec2/cloud.py 2011-04-07 08:40:22 +0000
+++ nova/api/ec2/cloud.py 2011-04-12 11:01:25 +0000
@@ -103,10 +103,18 @@
103 # Gen root CA, if we don't have one103 # Gen root CA, if we don't have one
104 root_ca_path = os.path.join(FLAGS.ca_path, FLAGS.ca_file)104 root_ca_path = os.path.join(FLAGS.ca_path, FLAGS.ca_file)
105 if not os.path.exists(root_ca_path):105 if not os.path.exists(root_ca_path):
106 genrootca_sh_path = os.path.join(os.path.dirname(__file__),
107 os.path.pardir,
108 os.path.pardir,
109 'CA',
110 'genrootca.sh')
111
106 start = os.getcwd()112 start = os.getcwd()
113 if not os.path.exists(FLAGS.ca_path):
114 os.makedirs(FLAGS.ca_path)
107 os.chdir(FLAGS.ca_path)115 os.chdir(FLAGS.ca_path)
108 # TODO(vish): Do this with M2Crypto instead116 # TODO(vish): Do this with M2Crypto instead
109 utils.runthis(_("Generating root CA: %s"), "sh", "genrootca.sh")117 utils.runthis(_("Generating root CA: %s"), "sh", genrootca_sh_path)
110 os.chdir(start)118 os.chdir(start)
111119
112 def _get_mpi_data(self, context, project_id):120 def _get_mpi_data(self, context, project_id):
@@ -134,6 +142,11 @@
134 instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address)142 instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address)
135 if instance_ref is None:143 if instance_ref is None:
136 return None144 return None
145
146 # This ensures that all attributes of the instance
147 # are populated.
148 instance_ref = db.instance_get(ctxt, instance_ref['id'])
149
137 mpi = self._get_mpi_data(ctxt, instance_ref['project_id'])150 mpi = self._get_mpi_data(ctxt, instance_ref['project_id'])
138 if instance_ref['key_name']:151 if instance_ref['key_name']:
139 keys = {'0': {'_name': instance_ref['key_name'],152 keys = {'0': {'_name': instance_ref['key_name'],
@@ -156,7 +169,7 @@
156 floating_ip = net['IPs'][0]['floating_ips'][0]169 floating_ip = net['IPs'][0]['floating_ips'][0]
157170
158 ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])171 ec2_id = ec2utils.id_to_ec2_id(instance_ref['id'])
159 image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'machine')172 image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'ami')
160 data = {173 data = {
161 'user-data': base64.b64decode(instance_ref['user_data']),174 'user-data': base64.b64decode(instance_ref['user_data']),
162 'meta-data': {175 'meta-data': {
@@ -186,7 +199,7 @@
186 for image_type in ['kernel', 'ramdisk']:199 for image_type in ['kernel', 'ramdisk']:
187 if '%s_id' % image_type in instance_ref:200 if '%s_id' % image_type in instance_ref:
188 ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type],201 ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type],
189 image_type)202 self._image_type(image_type))
190 data['meta-data']['%s-id' % image_type] = ec2_id203 data['meta-data']['%s-id' % image_type] = ec2_id
191204
192 if False: # TODO(vish): store ancestor ids205 if False: # TODO(vish): store ancestor ids
@@ -546,6 +559,13 @@
546 return self.compute_api.get_ajax_console(context,559 return self.compute_api.get_ajax_console(context,
547 instance_id=instance_id)560 instance_id=instance_id)
548561
562 def get_vnc_console(self, context, instance_id, **kwargs):
563 """Returns vnc browser url. Used by OS dashboard."""
564 ec2_id = instance_id
565 instance_id = ec2utils.ec2_id_to_id(ec2_id)
566 return self.compute_api.get_vnc_console(context,
567 instance_id=instance_id)
568
549 def describe_volumes(self, context, volume_id=None, **kwargs):569 def describe_volumes(self, context, volume_id=None, **kwargs):
550 if volume_id:570 if volume_id:
551 volumes = []571 volumes = []
@@ -734,7 +754,10 @@
734 instance['project_id'],754 instance['project_id'],
735 instance['host'])755 instance['host'])
736 i['productCodesSet'] = self._convert_to_set([], 'product_codes')756 i['productCodesSet'] = self._convert_to_set([], 'product_codes')
737 i['instanceType'] = instance['instance_type']757 if instance['instance_type']:
758 i['instanceType'] = instance['instance_type'].get('name')
759 else:
760 i['instanceType'] = None
738 i['launchTime'] = instance['created_at']761 i['launchTime'] = instance['created_at']
739 i['amiLaunchIndex'] = instance['launch_index']762 i['amiLaunchIndex'] = instance['launch_index']
740 i['displayName'] = instance['display_name']763 i['displayName'] = instance['display_name']
@@ -763,6 +786,7 @@
763786
764 def format_addresses(self, context):787 def format_addresses(self, context):
765 addresses = []788 addresses = []
789
766 net_factory = net_service.get_service_factory(context,790 net_factory = net_service.get_service_factory(context,
767 context.project_id)791 context.project_id)
768 net_api = net_factory.get_api_service()792 net_api = net_factory.get_api_service()
@@ -770,6 +794,9 @@
770 ip_info_list = net_api.get_addresses(context, project_id)794 ip_info_list = net_api.get_addresses(context, project_id)
771795
772 for ip_info in ip_info_list:796 for ip_info in ip_info_list:
797 if ip_info['project_id'] is None:
798 continue
799
773 ec2_id = None800 ec2_id = None
774 if ip_info['vnic_id']:801 if ip_info['vnic_id']:
775 instance = db.instance_get_by_virtual_nic(context,802 instance = db.instance_get_by_virtual_nic(context,
@@ -788,6 +815,7 @@
788815
789 def allocate_address(self, context, **kwargs):816 def allocate_address(self, context, **kwargs):
790 LOG.audit(_("Allocate address"), context=context)817 LOG.audit(_("Allocate address"), context=context)
818
791 net_factory = net_service.get_service_factory(context,819 net_factory = net_service.get_service_factory(context,
792 context.project_id)820 context.project_id)
793 net_api_service = net_factory.get_api_service()821 net_api_service = net_factory.get_api_service()
@@ -802,7 +830,7 @@
802 context.project_id)830 context.project_id)
803 raise ex831 raise ex
804832
805 return {'addressSet': [{'publicIp': public_ip}]}833 return {'publicIp': public_ip}
806834
807 def release_address(self, context, public_ip, **kwargs):835 def release_address(self, context, public_ip, **kwargs):
808 LOG.audit(_("Release address %s"), public_ip, context=context)836 LOG.audit(_("Release address %s"), public_ip, context=context)
@@ -841,7 +869,7 @@
841 ramdisk = self._get_image(context, kwargs['ramdisk_id'])869 ramdisk = self._get_image(context, kwargs['ramdisk_id'])
842 kwargs['ramdisk_id'] = ramdisk['id']870 kwargs['ramdisk_id'] = ramdisk['id']
843 instances = self.compute_api.create(context,871 instances = self.compute_api.create(context,
844 instance_type=instance_types.get_by_type(872 instance_type=instance_types.get_instance_type_by_name(
845 kwargs.get('instance_type', None)),873 kwargs.get('instance_type', None)),
846 image_id=self._get_image(context, kwargs['image_id'])['id'],874 image_id=self._get_image(context, kwargs['image_id'])['id'],
847 min_count=int(kwargs.get('min_count', max_count)),875 min_count=int(kwargs.get('min_count', max_count)),
@@ -898,13 +926,27 @@
898 self.compute_api.update(context, instance_id=instance_id, **kwargs)926 self.compute_api.update(context, instance_id=instance_id, **kwargs)
899 return True927 return True
900928
901 _type_prefix_map = {'machine': 'ami',929 @staticmethod
902 'kernel': 'aki',930 def _image_type(image_type):
903 'ramdisk': 'ari'}931 """Converts to a three letter image type.
904932
905 def _image_ec2_id(self, image_id, image_type='machine'):933 aki, kernel => aki
906 prefix = self._type_prefix_map[image_type]934 ari, ramdisk => ari
907 template = prefix + '-%08x'935 anything else => ami
936
937 """
938 if image_type == 'kernel':
939 return 'aki'
940 if image_type == 'ramdisk':
941 return 'ari'
942 if image_type not in ['aki', 'ari']:
943 return 'ami'
944 return image_type
945
946 @staticmethod
947 def _image_ec2_id(image_id, image_type='ami'):
948 """Returns image ec2_id using id and three letter type."""
949 template = image_type + '-%08x'
908 return ec2utils.id_to_ec2_id(int(image_id), template=template)950 return ec2utils.id_to_ec2_id(int(image_id), template=template)
909951
910 def _get_image(self, context, ec2_id):952 def _get_image(self, context, ec2_id):
@@ -917,24 +959,34 @@
917 def _format_image(self, image):959 def _format_image(self, image):
918 """Convert from format defined by BaseImageService to S3 format."""960 """Convert from format defined by BaseImageService to S3 format."""
919 i = {}961 i = {}
920 image_type = image['properties'].get('type')962 image_type = self._image_type(image.get('container_format'))
921 ec2_id = self._image_ec2_id(image.get('id'), image_type)963 ec2_id = self._image_ec2_id(image.get('id'), image_type)
922 name = image.get('name')964 name = image.get('name')
923 if name:965 i['imageId'] = ec2_id
924 i['imageId'] = "%s (%s)" % (ec2_id, name)
925 else:
926 i['imageId'] = ec2_id
927 kernel_id = image['properties'].get('kernel_id')966 kernel_id = image['properties'].get('kernel_id')
928 if kernel_id:967 if kernel_id:
929 i['kernelId'] = self._image_ec2_id(kernel_id, 'kernel')968 i['kernelId'] = self._image_ec2_id(kernel_id, 'aki')
930 ramdisk_id = image['properties'].get('ramdisk_id')969 ramdisk_id = image['properties'].get('ramdisk_id')
931 if ramdisk_id:970 if ramdisk_id:
932 i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ramdisk')971 i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ari')
933 i['imageOwnerId'] = image['properties'].get('owner_id')972 i['imageOwnerId'] = image['properties'].get('owner_id')
934 i['imageLocation'] = image['properties'].get('image_location')973 if name:
935 i['imageState'] = image['properties'].get('image_state')974 i['imageLocation'] = "%s (%s)" % (image['properties'].
936 i['type'] = image_type975 get('image_location'), name)
937 i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True'976 else:
977 i['imageLocation'] = image['properties'].get('image_location')
978 # NOTE(vish): fallback status if image_state isn't set
979 state = image.get('status')
980 if state == 'active':
981 state = 'available'
982 i['imageState'] = image['properties'].get('image_state', state)
983 i['displayName'] = name
984 i['description'] = image.get('description')
985 display_mapping = {'aki': 'kernel',
986 'ari': 'ramdisk',
987 'ami': 'machine'}
988 i['imageType'] = display_mapping.get(image_type)
989 i['isPublic'] = image.get('is_public') == True
938 i['architecture'] = image['properties'].get('architecture')990 i['architecture'] = image['properties'].get('architecture')
939 return i991 return i
940992
@@ -966,8 +1018,9 @@
966 image_location = kwargs['name']1018 image_location = kwargs['name']
967 metadata = {'properties': {'image_location': image_location}}1019 metadata = {'properties': {'image_location': image_location}}
968 image = self.image_service.create(context, metadata)1020 image = self.image_service.create(context, metadata)
1021 image_type = self._image_type(image.get('container_format'))
969 image_id = self._image_ec2_id(image['id'],1022 image_id = self._image_ec2_id(image['id'],
970 image['properties']['type'])1023 image_type)
971 msg = _("Registered image %(image_location)s with"1024 msg = _("Registered image %(image_location)s with"
972 " id %(image_id)s") % locals()1025 " id %(image_id)s") % locals()
973 LOG.audit(msg, context=context)1026 LOG.audit(msg, context=context)
@@ -982,7 +1035,7 @@
982 except exception.NotFound:1035 except exception.NotFound:
983 raise exception.NotFound(_('Image %s not found') % image_id)1036 raise exception.NotFound(_('Image %s not found') % image_id)
984 result = {'imageId': image_id, 'launchPermission': []}1037 result = {'imageId': image_id, 'launchPermission': []}
985 if image['properties']['is_public']:1038 if image['is_public']:
986 result['launchPermission'].append({'group': 'all'})1039 result['launchPermission'].append({'group': 'all'})
987 return result1040 return result
9881041
@@ -1007,7 +1060,7 @@
1007 internal_id = image['id']1060 internal_id = image['id']
1008 del(image['id'])1061 del(image['id'])
10091062
1010 image['properties']['is_public'] = (operation_type == 'add')1063 image['is_public'] = (operation_type == 'add')
1011 return self.image_service.update(context, internal_id, image)1064 return self.image_service.update(context, internal_id, image)
10121065
1013 def update_image(self, context, image_id, **kwargs):1066 def update_image(self, context, image_id, **kwargs):
10141067
=== modified file 'nova/api/openstack/__init__.py'
--- nova/api/openstack/__init__.py 2011-04-07 01:08:35 +0000
+++ nova/api/openstack/__init__.py 2011-04-12 11:01:25 +0000
@@ -36,6 +36,7 @@
36from nova.api.openstack import flavors36from nova.api.openstack import flavors
37from nova.api.openstack import images37from nova.api.openstack import images
38from nova.api.openstack import image_metadata38from nova.api.openstack import image_metadata
39from nova.api.openstack import ips
39from nova.api.openstack import limits40from nova.api.openstack import limits
40from nova.api.openstack import servers41from nova.api.openstack import servers
41from nova.api.openstack import server_metadata42from nova.api.openstack import server_metadata
@@ -108,23 +109,11 @@
108 controller=accounts.Controller(),109 controller=accounts.Controller(),
109 collection={'detail': 'GET'})110 collection={'detail': 'GET'})
110111
111 mapper.resource("backup_schedule", "backup_schedule",
112 controller=backup_schedules.Controller(),
113 parent_resource=dict(member_name='server',
114 collection_name='servers'))
115
116 mapper.resource("console", "consoles",112 mapper.resource("console", "consoles",
117 controller=consoles.Controller(),113 controller=consoles.Controller(),
118 parent_resource=dict(member_name='server',114 parent_resource=dict(member_name='server',
119 collection_name='servers'))115 collection_name='servers'))
120116
121 mapper.resource("image", "images", controller=images.Controller(),
122 collection={'detail': 'GET'})
123
124 mapper.resource("shared_ip_group", "shared_ip_groups",
125 collection={'detail': 'GET'},
126 controller=shared_ip_groups.Controller())
127
128 _limits = limits.LimitsController()117 _limits = limits.LimitsController()
129 mapper.resource("limit", "limits", controller=_limits)118 mapper.resource("limit", "limits", controller=_limits)
130119
@@ -144,10 +133,28 @@
144 collection={'detail': 'GET'},133 collection={'detail': 'GET'},
145 member=self.server_members)134 member=self.server_members)
146135
136 mapper.resource("image", "images",
137 controller=images.ControllerV10(),
138 collection={'detail': 'GET'})
139
147 mapper.resource("flavor", "flavors",140 mapper.resource("flavor", "flavors",
148 controller=flavors.ControllerV10(),141 controller=flavors.ControllerV10(),
149 collection={'detail': 'GET'})142 collection={'detail': 'GET'})
150143
144 mapper.resource("shared_ip_group", "shared_ip_groups",
145 collection={'detail': 'GET'},
146 controller=shared_ip_groups.Controller())
147
148 mapper.resource("backup_schedule", "backup_schedule",
149 controller=backup_schedules.Controller(),
150 parent_resource=dict(member_name='server',
151 collection_name='servers'))
152
153 mapper.resource("ip", "ips", controller=ips.Controller(),
154 collection=dict(public='GET', private='GET'),
155 parent_resource=dict(member_name='server',
156 collection_name='servers'))
157
151158
152class APIRouterV11(APIRouter):159class APIRouterV11(APIRouter):
153 """Define routes specific to OpenStack API V1.1."""160 """Define routes specific to OpenStack API V1.1."""
@@ -159,6 +166,10 @@
159 collection={'detail': 'GET'},166 collection={'detail': 'GET'},
160 member=self.server_members)167 member=self.server_members)
161168
169 mapper.resource("image", "images",
170 controller=images.ControllerV11(),
171 collection={'detail': 'GET'})
172
162 mapper.resource("image_meta", "meta",173 mapper.resource("image_meta", "meta",
163 controller=image_metadata.Controller(),174 controller=image_metadata.Controller(),
164 parent_resource=dict(member_name='image',175 parent_resource=dict(member_name='image',
165176
=== modified file 'nova/api/openstack/accounts.py'
--- nova/api/openstack/accounts.py 2011-03-18 14:34:08 +0000
+++ nova/api/openstack/accounts.py 2011-04-12 11:01:25 +0000
@@ -13,15 +13,14 @@
13# License for the specific language governing permissions and limitations13# License for the specific language governing permissions and limitations
14# under the License.14# under the License.
1515
16import common
17import webob.exc16import webob.exc
1817
19from nova import exception18from nova import exception
20from nova import flags19from nova import flags
21from nova import log as logging20from nova import log as logging
22from nova import wsgi
2321
24from nova.auth import manager22from nova.auth import manager
23from nova.api.openstack import common
25from nova.api.openstack import faults24from nova.api.openstack import faults
2625
27FLAGS = flags.FLAGS26FLAGS = flags.FLAGS
@@ -35,7 +34,7 @@
35 manager=account.project_manager_id)34 manager=account.project_manager_id)
3635
3736
38class Controller(wsgi.Controller):37class Controller(common.OpenstackController):
3938
40 _serialization_metadata = {39 _serialization_metadata = {
41 'application/xml': {40 'application/xml': {
4241
=== modified file 'nova/api/openstack/backup_schedules.py'
--- nova/api/openstack/backup_schedules.py 2011-03-09 18:10:45 +0000
+++ nova/api/openstack/backup_schedules.py 2011-04-12 11:01:25 +0000
@@ -19,7 +19,7 @@
1919
20from webob import exc20from webob import exc
2121
22from nova import wsgi22from nova.api.openstack import common
23from nova.api.openstack import faults23from nova.api.openstack import faults
24import nova.image.service24import nova.image.service
2525
@@ -29,7 +29,7 @@
29 return dict(backupSchedule=inst)29 return dict(backupSchedule=inst)
3030
3131
32class Controller(wsgi.Controller):32class Controller(common.OpenstackController):
33 """ The backup schedule API controller for the Openstack API """33 """ The backup schedule API controller for the Openstack API """
3434
35 _serialization_metadata = {35 _serialization_metadata = {
@@ -42,7 +42,11 @@
4242
43 def index(self, req, server_id):43 def index(self, req, server_id):
44 """ Returns the list of backup schedules for a given instance """44 """ Returns the list of backup schedules for a given instance """
45 return _translate_keys({})45 return faults.Fault(exc.HTTPNotImplemented())
46
47 def show(self, req, server_id, id):
48 """ Returns a single backup schedule for a given instance """
49 return faults.Fault(exc.HTTPNotImplemented())
4650
47 def create(self, req, server_id):51 def create(self, req, server_id):
48 """ No actual update method required, since the existing API allows52 """ No actual update method required, since the existing API allows
4953
=== modified file 'nova/api/openstack/common.py'
--- nova/api/openstack/common.py 2011-03-24 23:34:52 +0000
+++ nova/api/openstack/common.py 2011-04-12 11:01:25 +0000
@@ -21,10 +21,20 @@
2121
22from nova import exception22from nova import exception
23from nova import flags23from nova import flags
24from nova import log as logging
25from nova import wsgi
26
27
28LOG = logging.getLogger('common')
29
2430
25FLAGS = flags.FLAGS31FLAGS = flags.FLAGS
2632
2733
34XML_NS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0'
35XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1'
36
37
28def limited(items, request, max_limit=FLAGS.osapi_max_limit):38def limited(items, request, max_limit=FLAGS.osapi_max_limit):
29 """39 """
30 Return a slice of items according to requested offset and limit.40 Return a slice of items according to requested offset and limit.
@@ -121,4 +131,11 @@
121 try:131 try:
122 return int(urlparse(href).path.split('/')[-1])132 return int(urlparse(href).path.split('/')[-1])
123 except:133 except:
134 LOG.debug(_("Error extracting id from href: %s") % href)
124 raise webob.exc.HTTPBadRequest(_('could not parse id from href'))135 raise webob.exc.HTTPBadRequest(_('could not parse id from href'))
136
137
138class OpenstackController(wsgi.Controller):
139 def get_default_xmlns(self, req):
140 # Use V10 by default
141 return XML_NS_V10
125142
=== modified file 'nova/api/openstack/consoles.py'
--- nova/api/openstack/consoles.py 2011-03-11 19:49:32 +0000
+++ nova/api/openstack/consoles.py 2011-04-12 11:01:25 +0000
@@ -19,7 +19,7 @@
1919
20from nova import console20from nova import console
21from nova import exception21from nova import exception
22from nova import wsgi22from nova.api.openstack import common
23from nova.api.openstack import faults23from nova.api.openstack import faults
2424
2525
@@ -43,7 +43,7 @@
43 return dict(console=info)43 return dict(console=info)
4444
4545
46class Controller(wsgi.Controller):46class Controller(common.OpenstackController):
47 """The Consoles Controller for the Openstack API"""47 """The Consoles Controller for the Openstack API"""
4848
49 _serialization_metadata = {49 _serialization_metadata = {
5050
=== added directory 'nova/api/openstack/contrib'
=== added file 'nova/api/openstack/contrib/__init__.py'
--- nova/api/openstack/contrib/__init__.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/contrib/__init__.py 2011-04-12 11:01:25 +0000
@@ -0,0 +1,22 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2011 Justin Santa Barbara
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.import datetime
17
18"""Contrib contains extensions that are shipped with nova.
19
20It can't be called 'extensions' because that causes namespacing problems.
21
22"""
023
=== added file 'nova/api/openstack/contrib/volumes.py'
--- nova/api/openstack/contrib/volumes.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/contrib/volumes.py 2011-04-12 11:01:25 +0000
@@ -0,0 +1,336 @@
1# Copyright 2011 Justin Santa Barbara
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16"""The volumes extension."""
17
18from webob import exc
19
20from nova import compute
21from nova import exception
22from nova import flags
23from nova import log as logging
24from nova import volume
25from nova import wsgi
26from nova.api.openstack import common
27from nova.api.openstack import extensions
28from nova.api.openstack import faults
29
30
31LOG = logging.getLogger("nova.api.volumes")
32
33
34FLAGS = flags.FLAGS
35
36
37def _translate_volume_detail_view(context, vol):
38 """Maps keys for volumes details view."""
39
40 d = _translate_volume_summary_view(context, vol)
41
42 # No additional data / lookups at the moment
43
44 return d
45
46
47def _translate_volume_summary_view(context, vol):
48 """Maps keys for volumes summary view."""
49 d = {}
50
51 d['id'] = vol['id']
52 d['status'] = vol['status']
53 d['size'] = vol['size']
54 d['availabilityZone'] = vol['availability_zone']
55 d['createdAt'] = vol['created_at']
56
57 if vol['attach_status'] == 'attached':
58 d['attachments'] = [_translate_attachment_detail_view(context, vol)]
59 else:
60 d['attachments'] = [{}]
61
62 d['displayName'] = vol['display_name']
63 d['displayDescription'] = vol['display_description']
64 return d
65
66
67class VolumeController(wsgi.Controller):
68 """The Volumes API controller for the OpenStack API."""
69
70 _serialization_metadata = {
71 'application/xml': {
72 "attributes": {
73 "volume": [
74 "id",
75 "status",
76 "size",
77 "availabilityZone",
78 "createdAt",
79 "displayName",
80 "displayDescription",
81 ]}}}
82
83 def __init__(self):
84 self.volume_api = volume.API()
85 super(VolumeController, self).__init__()
86
87 def show(self, req, id):
88 """Return data about the given volume."""
89 context = req.environ['nova.context']
90
91 try:
92 vol = self.volume_api.get(context, id)
93 except exception.NotFound:
94 return faults.Fault(exc.HTTPNotFound())
95
96 return {'volume': _translate_volume_detail_view(context, vol)}
97
98 def delete(self, req, id):
99 """Delete a volume."""
100 context = req.environ['nova.context']
101
102 LOG.audit(_("Delete volume with id: %s"), id, context=context)
103
104 try:
105 self.volume_api.delete(context, volume_id=id)
106 except exception.NotFound:
107 return faults.Fault(exc.HTTPNotFound())
108 return exc.HTTPAccepted()
109
110 def index(self, req):
111 """Returns a summary list of volumes."""
112 return self._items(req, entity_maker=_translate_volume_summary_view)
113
114 def detail(self, req):
115 """Returns a detailed list of volumes."""
116 return self._items(req, entity_maker=_translate_volume_detail_view)
117
118 def _items(self, req, entity_maker):
119 """Returns a list of volumes, transformed through entity_maker."""
120 context = req.environ['nova.context']
121
122 volumes = self.volume_api.get_all(context)
123 limited_list = common.limited(volumes, req)
124 res = [entity_maker(context, vol) for vol in limited_list]
125 return {'volumes': res}
126
127 def create(self, req):
128 """Creates a new volume."""
129 context = req.environ['nova.context']
130
131 env = self._deserialize(req.body, req.get_content_type())
132 if not env:
133 return faults.Fault(exc.HTTPUnprocessableEntity())
134
135 vol = env['volume']
136 size = vol['size']
137 LOG.audit(_("Create volume of %s GB"), size, context=context)
138 new_volume = self.volume_api.create(context, size,
139 vol.get('display_name'),
140 vol.get('display_description'))
141
142 # Work around problem that instance is lazy-loaded...
143 new_volume['instance'] = None
144
145 retval = _translate_volume_detail_view(context, new_volume)
146
147 return {'volume': retval}
148
149
150def _translate_attachment_detail_view(_context, vol):
151 """Maps keys for attachment details view."""
152
153 d = _translate_attachment_summary_view(_context, vol)
154
155 # No additional data / lookups at the moment
156
157 return d
158
159
160def _translate_attachment_summary_view(_context, vol):
161 """Maps keys for attachment summary view."""
162 d = {}
163
164 volume_id = vol['id']
165
166 # NOTE(justinsb): We use the volume id as the id of the attachment object
167 d['id'] = volume_id
168
169 d['volumeId'] = volume_id
170 if vol.get('instance_id'):
171 d['serverId'] = vol['instance_id']
172 if vol.get('mountpoint'):
173 d['device'] = vol['mountpoint']
174
175 return d
176
177
178class VolumeAttachmentController(wsgi.Controller):
179 """The volume attachment API controller for the Openstack API.
180
181 A child resource of the server. Note that we use the volume id
182 as the ID of the attachment (though this is not guaranteed externally)
183
184 """
185
186 _serialization_metadata = {
187 'application/xml': {
188 'attributes': {
189 'volumeAttachment': ['id',
190 'serverId',
191 'volumeId',
192 'device']}}}
193
194 def __init__(self):
195 self.compute_api = compute.API()
196 self.volume_api = volume.API()
197 super(VolumeAttachmentController, self).__init__()
198
199 def index(self, req, server_id):
200 """Returns the list of volume attachments for a given instance."""
201 return self._items(req, server_id,
202 entity_maker=_translate_attachment_summary_view)
203
204 def show(self, req, server_id, id):
205 """Return data about the given volume attachment."""
206 context = req.environ['nova.context']
207
208 volume_id = id
209 try:
210 vol = self.volume_api.get(context, volume_id)
211 except exception.NotFound:
212 LOG.debug("volume_id not found")
213 return faults.Fault(exc.HTTPNotFound())
214
215 if str(vol['instance_id']) != server_id:
216 LOG.debug("instance_id != server_id")
217 return faults.Fault(exc.HTTPNotFound())
218
219 return {'volumeAttachment': _translate_attachment_detail_view(context,
220 vol)}
221
222 def create(self, req, server_id):
223 """Attach a volume to an instance."""
224 context = req.environ['nova.context']
225
226 env = self._deserialize(req.body, req.get_content_type())
227 if not env:
228 return faults.Fault(exc.HTTPUnprocessableEntity())
229
230 instance_id = server_id
231 volume_id = env['volumeAttachment']['volumeId']
232 device = env['volumeAttachment']['device']
233
234 msg = _("Attach volume %(volume_id)s to instance %(server_id)s"
235 " at %(device)s") % locals()
236 LOG.audit(msg, context=context)
237
238 try:
239 self.compute_api.attach_volume(context,
240 instance_id=instance_id,
241 volume_id=volume_id,
242 device=device)
243 except exception.NotFound:
244 return faults.Fault(exc.HTTPNotFound())
245
246 # The attach is async
247 attachment = {}
248 attachment['id'] = volume_id
249 attachment['volumeId'] = volume_id
250
251 # NOTE(justinsb): And now, we have a problem...
252 # The attach is async, so there's a window in which we don't see
253 # the attachment (until the attachment completes). We could also
254 # get problems with concurrent requests. I think we need an
255 # attachment state, and to write to the DB here, but that's a bigger
256 # change.
257 # For now, we'll probably have to rely on libraries being smart
258
259 # TODO(justinsb): How do I return "accepted" here?
260 return {'volumeAttachment': attachment}
261
262 def update(self, _req, _server_id, _id):
263 """Update a volume attachment. We don't currently support this."""
264 return faults.Fault(exc.HTTPBadRequest())
265
266 def delete(self, req, server_id, id):
267 """Detach a volume from an instance."""
268 context = req.environ['nova.context']
269
270 volume_id = id
271 LOG.audit(_("Detach volume %s"), volume_id, context=context)
272
273 try:
274 vol = self.volume_api.get(context, volume_id)
275 except exception.NotFound:
276 return faults.Fault(exc.HTTPNotFound())
277
278 if str(vol['instance_id']) != server_id:
279 LOG.debug("instance_id != server_id")
280 return faults.Fault(exc.HTTPNotFound())
281
282 self.compute_api.detach_volume(context,
283 volume_id=volume_id)
284
285 return exc.HTTPAccepted()
286
287 def _items(self, req, server_id, entity_maker):
288 """Returns a list of attachments, transformed through entity_maker."""
289 context = req.environ['nova.context']
290
291 try:
292 instance = self.compute_api.get(context, server_id)
293 except exception.NotFound:
294 return faults.Fault(exc.HTTPNotFound())
295
296 volumes = instance['volumes']
297 limited_list = common.limited(volumes, req)
298 res = [entity_maker(context, vol) for vol in limited_list]
299 return {'volumeAttachments': res}
300
301
302class Volumes(extensions.ExtensionDescriptor):
303 def get_name(self):
304 return "Volumes"
305
306 def get_alias(self):
307 return "VOLUMES"
308
309 def get_description(self):
310 return "Volumes support"
311
312 def get_namespace(self):
313 return "http://docs.openstack.org/ext/volumes/api/v1.1"
314
315 def get_updated(self):
316 return "2011-03-25T00:00:00+00:00"
317
318 def get_resources(self):
319 resources = []
320
321 # NOTE(justinsb): No way to provide singular name ('volume')
322 # Does this matter?
323 res = extensions.ResourceExtension('volumes',
324 VolumeController(),
325 collection_actions={'detail': 'GET'}
326 )
327 resources.append(res)
328
329 res = extensions.ResourceExtension('volume_attachments',
330 VolumeAttachmentController(),
331 parent=dict(
332 member_name='server',
333 collection_name='servers'))
334 resources.append(res)
335
336 return resources
0337
=== modified file 'nova/api/openstack/extensions.py'
--- nova/api/openstack/extensions.py 2011-03-21 18:00:39 +0000
+++ nova/api/openstack/extensions.py 2011-04-12 11:01:25 +0000
@@ -1,6 +1,7 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=41# vim: tabstop=4 shiftwidth=4 softtabstop=4
22
3# Copyright 2011 OpenStack LLC.3# Copyright 2011 OpenStack LLC.
4# Copyright 2011 Justin Santa Barbara
4# All Rights Reserved.5# All Rights Reserved.
5#6#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may7# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -16,15 +17,18 @@
16# under the License.17# under the License.
1718
18import imp19import imp
20import inspect
19import os21import os
20import sys22import sys
21import routes23import routes
22import webob.dec24import webob.dec
23import webob.exc25import webob.exc
2426
27from nova import exception
25from nova import flags28from nova import flags
26from nova import log as logging29from nova import log as logging
27from nova import wsgi30from nova import wsgi
31from nova.api.openstack import common
28from nova.api.openstack import faults32from nova.api.openstack import faults
2933
3034
@@ -34,7 +38,85 @@
34FLAGS = flags.FLAGS38FLAGS = flags.FLAGS
3539
3640
37class ActionExtensionController(wsgi.Controller):41class ExtensionDescriptor(object):
42 """Base class that defines the contract for extensions.
43
44 Note that you don't have to derive from this class to have a valid
45 extension; it is purely a convenience.
46
47 """
48
49 def get_name(self):
50 """The name of the extension.
51
52 e.g. 'Fox In Socks'
53
54 """
55 raise NotImplementedError()
56
57 def get_alias(self):
58 """The alias for the extension.
59
60 e.g. 'FOXNSOX'
61
62 """
63 raise NotImplementedError()
64
65 def get_description(self):
66 """Friendly description for the extension.
67
68 e.g. 'The Fox In Socks Extension'
69
70 """
71 raise NotImplementedError()
72
73 def get_namespace(self):
74 """The XML namespace for the extension.
75
76 e.g. 'http://www.fox.in.socks/api/ext/pie/v1.0'
77
78 """
79 raise NotImplementedError()
80
81 def get_updated(self):
82 """The timestamp when the extension was last updated.
83
84 e.g. '2011-01-22T13:25:27-06:00'
85
86 """
87 # NOTE(justinsb): Not sure of the purpose of this is, vs the XML NS
88 raise NotImplementedError()
89
90 def get_resources(self):
91 """List of extensions.ResourceExtension extension objects.
92
93 Resources define new nouns, and are accessible through URLs.
94
95 """
96 resources = []
97 return resources
98
99 def get_actions(self):
100 """List of extensions.ActionExtension extension objects.
101
102 Actions are verbs callable from the API.
103
104 """
105 actions = []
106 return actions
107
108 def get_response_extensions(self):
109 """List of extensions.ResponseExtension extension objects.
110
111 Response extensions are used to insert information into existing
112 response data.
113
114 """
115 response_exts = []
116 return response_exts
117
118
119class ActionExtensionController(common.OpenstackController):
38120
39 def __init__(self, application):121 def __init__(self, application):
40122
@@ -55,7 +137,7 @@
55 return res137 return res
56138
57139
58class ResponseExtensionController(wsgi.Controller):140class ResponseExtensionController(common.OpenstackController):
59141
60 def __init__(self, application):142 def __init__(self, application):
61 self.application = application143 self.application = application
@@ -74,7 +156,8 @@
74 body = res.body156 body = res.body
75 headers = res.headers157 headers = res.headers
76 except AttributeError:158 except AttributeError:
77 body = self._serialize(res, content_type)159 default_xmlns = None
160 body = self._serialize(res, content_type, default_xmlns)
78 headers = {"Content-Type": content_type}161 headers = {"Content-Type": content_type}
79 res = webob.Response()162 res = webob.Response()
80 res.body = body163 res.body = body
@@ -82,7 +165,7 @@
82 return res165 return res
83166
84167
85class ExtensionController(wsgi.Controller):168class ExtensionController(common.OpenstackController):
86169
87 def __init__(self, extension_manager):170 def __init__(self, extension_manager):
88 self.extension_manager = extension_manager171 self.extension_manager = extension_manager
@@ -94,45 +177,38 @@
94 ext_data['description'] = ext.get_description()177 ext_data['description'] = ext.get_description()
95 ext_data['namespace'] = ext.get_namespace()178 ext_data['namespace'] = ext.get_namespace()
96 ext_data['updated'] = ext.get_updated()179 ext_data['updated'] = ext.get_updated()
97 ext_data['links'] = [] # TODO: implement extension links180 ext_data['links'] = [] # TODO(dprince): implement extension links
98 return ext_data181 return ext_data
99182
100 def index(self, req):183 def index(self, req):
101 extensions = []184 extensions = []
102 for alias, ext in self.extension_manager.extensions.iteritems():185 for _alias, ext in self.extension_manager.extensions.iteritems():
103 extensions.append(self._translate(ext))186 extensions.append(self._translate(ext))
104 return dict(extensions=extensions)187 return dict(extensions=extensions)
105188
106 def show(self, req, id):189 def show(self, req, id):
107 # NOTE: the extensions alias is used as the 'id' for show190 # NOTE(dprince): the extensions alias is used as the 'id' for show
108 ext = self.extension_manager.extensions[id]191 ext = self.extension_manager.extensions[id]
109 return self._translate(ext)192 return self._translate(ext)
110193
111 def delete(self, req, id):194 def delete(self, req, id):
112 raise faults.Fault(exc.HTTPNotFound())195 raise faults.Fault(webob.exc.HTTPNotFound())
113196
114 def create(self, req):197 def create(self, req):
115 raise faults.Fault(exc.HTTPNotFound())198 raise faults.Fault(webob.exc.HTTPNotFound())
116
117 def delete(self, req, id):
118 raise faults.Fault(exc.HTTPNotFound())
119199
120200
121class ExtensionMiddleware(wsgi.Middleware):201class ExtensionMiddleware(wsgi.Middleware):
122 """202 """Extensions middleware for WSGI."""
123 Extensions middleware that intercepts configured routes for extensions.
124 """
125 @classmethod203 @classmethod
126 def factory(cls, global_config, **local_config):204 def factory(cls, global_config, **local_config):
127 """ paste factory """205 """Paste factory."""
128 def _factory(app):206 def _factory(app):
129 return cls(app, **local_config)207 return cls(app, **local_config)
130 return _factory208 return _factory
131209
132 def _action_ext_controllers(self, application, ext_mgr, mapper):210 def _action_ext_controllers(self, application, ext_mgr, mapper):
133 """211 """Return a dict of ActionExtensionController-s by collection."""
134 Return a dict of ActionExtensionController objects by collection
135 """
136 action_controllers = {}212 action_controllers = {}
137 for action in ext_mgr.get_actions():213 for action in ext_mgr.get_actions():
138 if not action.collection in action_controllers.keys():214 if not action.collection in action_controllers.keys():
@@ -151,9 +227,7 @@
151 return action_controllers227 return action_controllers
152228
153 def _response_ext_controllers(self, application, ext_mgr, mapper):229 def _response_ext_controllers(self, application, ext_mgr, mapper):
154 """230 """Returns a dict of ResponseExtensionController-s by collection."""
155 Return a dict of ResponseExtensionController objects by collection
156 """
157 response_ext_controllers = {}231 response_ext_controllers = {}
158 for resp_ext in ext_mgr.get_response_extensions():232 for resp_ext in ext_mgr.get_response_extensions():
159 if not resp_ext.key in response_ext_controllers.keys():233 if not resp_ext.key in response_ext_controllers.keys():
@@ -212,18 +286,18 @@
212286
213 @webob.dec.wsgify(RequestClass=wsgi.Request)287 @webob.dec.wsgify(RequestClass=wsgi.Request)
214 def __call__(self, req):288 def __call__(self, req):
215 """289 """Route the incoming request with router."""
216 Route the incoming request with router.
217 """
218 req.environ['extended.app'] = self.application290 req.environ['extended.app'] = self.application
219 return self._router291 return self._router
220292
221 @staticmethod293 @staticmethod
222 @webob.dec.wsgify(RequestClass=wsgi.Request)294 @webob.dec.wsgify(RequestClass=wsgi.Request)
223 def _dispatch(req):295 def _dispatch(req):
224 """296 """Dispatch the request.
297
225 Returns the routed WSGI app's response or defers to the extended298 Returns the routed WSGI app's response or defers to the extended
226 application.299 application.
300
227 """301 """
228 match = req.environ['wsgiorg.routing_args'][1]302 match = req.environ['wsgiorg.routing_args'][1]
229 if not match:303 if not match:
@@ -233,10 +307,11 @@
233307
234308
235class ExtensionManager(object):309class ExtensionManager(object):
236 """310 """Load extensions from the configured extension path.
237 Load extensions from the configured extension path.311
238 See nova/tests/api/openstack/extensions/foxinsocks.py for an example312 See nova/tests/api/openstack/extensions/foxinsocks/extension.py for an
239 extension implementation.313 example extension implementation.
314
240 """315 """
241316
242 def __init__(self, path):317 def __init__(self, path):
@@ -244,12 +319,10 @@
244319
245 self.path = path320 self.path = path
246 self.extensions = {}321 self.extensions = {}
247 self._load_extensions()322 self._load_all_extensions()
248323
249 def get_resources(self):324 def get_resources(self):
250 """325 """Returns a list of ResourceExtension objects."""
251 returns a list of ResourceExtension objects
252 """
253 resources = []326 resources = []
254 resources.append(ResourceExtension('extensions',327 resources.append(ResourceExtension('extensions',
255 ExtensionController(self)))328 ExtensionController(self)))
@@ -257,40 +330,37 @@
257 try:330 try:
258 resources.extend(ext.get_resources())331 resources.extend(ext.get_resources())
259 except AttributeError:332 except AttributeError:
260 # NOTE: Extension aren't required to have resource extensions333 # NOTE(dprince): Extension aren't required to have resource
334 # extensions
261 pass335 pass
262 return resources336 return resources
263337
264 def get_actions(self):338 def get_actions(self):
265 """339 """Returns a list of ActionExtension objects."""
266 returns a list of ActionExtension objects
267 """
268 actions = []340 actions = []
269 for alias, ext in self.extensions.iteritems():341 for alias, ext in self.extensions.iteritems():
270 try:342 try:
271 actions.extend(ext.get_actions())343 actions.extend(ext.get_actions())
272 except AttributeError:344 except AttributeError:
273 # NOTE: Extension aren't required to have action extensions345 # NOTE(dprince): Extension aren't required to have action
346 # extensions
274 pass347 pass
275 return actions348 return actions
276349
277 def get_response_extensions(self):350 def get_response_extensions(self):
278 """351 """Returns a list of ResponseExtension objects."""
279 returns a list of ResponseExtension objects
280 """
281 response_exts = []352 response_exts = []
282 for alias, ext in self.extensions.iteritems():353 for alias, ext in self.extensions.iteritems():
283 try:354 try:
284 response_exts.extend(ext.get_response_extensions())355 response_exts.extend(ext.get_response_extensions())
285 except AttributeError:356 except AttributeError:
286 # NOTE: Extension aren't required to have response extensions357 # NOTE(dprince): Extension aren't required to have response
358 # extensions
287 pass359 pass
288 return response_exts360 return response_exts
289361
290 def _check_extension(self, extension):362 def _check_extension(self, extension):
291 """363 """Checks for required methods in extension objects."""
292 Checks for required methods in extension objects.
293 """
294 try:364 try:
295 LOG.debug(_('Ext name: %s'), extension.get_name())365 LOG.debug(_('Ext name: %s'), extension.get_name())
296 LOG.debug(_('Ext alias: %s'), extension.get_alias())366 LOG.debug(_('Ext alias: %s'), extension.get_alias())
@@ -300,40 +370,59 @@
300 except AttributeError as ex:370 except AttributeError as ex:
301 LOG.exception(_("Exception loading extension: %s"), unicode(ex))371 LOG.exception(_("Exception loading extension: %s"), unicode(ex))
302372
303 def _load_extensions(self):373 def _load_all_extensions(self):
304 """374 """Load extensions from the configured path.
375
305 Load extensions from the configured path. The extension name is376 Load extensions from the configured path. The extension name is
306 constructed from the module_name. If your extension module was named377 constructed from the module_name. If your extension module was named
307 widgets.py the extension class within that module should be378 widgets.py the extension class within that module should be
308 'Widgets'.379 'Widgets'.
309380
381 In addition, extensions are loaded from the 'contrib' directory.
382
310 See nova/tests/api/openstack/extensions/foxinsocks.py for an example383 See nova/tests/api/openstack/extensions/foxinsocks.py for an example
311 extension implementation.384 extension implementation.
385
312 """386 """
313 if not os.path.exists(self.path):387 if os.path.exists(self.path):
314 return388 self._load_all_extensions_from_path(self.path)
315389
316 for f in os.listdir(self.path):390 contrib_path = os.path.join(os.path.dirname(__file__), "contrib")
391 if os.path.exists(contrib_path):
392 self._load_all_extensions_from_path(contrib_path)
393
394 def _load_all_extensions_from_path(self, path):
395 for f in os.listdir(path):
317 LOG.audit(_('Loading extension file: %s'), f)396 LOG.audit(_('Loading extension file: %s'), f)
318 mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])397 mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
319 ext_path = os.path.join(self.path, f)398 ext_path = os.path.join(path, f)
320 if file_ext.lower() == '.py':399 if file_ext.lower() == '.py' and not mod_name.startswith('_'):
321 mod = imp.load_source(mod_name, ext_path)400 mod = imp.load_source(mod_name, ext_path)
322 ext_name = mod_name[0].upper() + mod_name[1:]401 ext_name = mod_name[0].upper() + mod_name[1:]
323 try:402 new_ext_class = getattr(mod, ext_name, None)
324 new_ext = getattr(mod, ext_name)()403 if not new_ext_class:
325 self._check_extension(new_ext)404 LOG.warn(_('Did not find expected name '
326 self.extensions[new_ext.get_alias()] = new_ext405 '"%(ext_name)s" in %(file)s'),
327 except AttributeError as ex:406 {'ext_name': ext_name,
328 LOG.exception(_("Exception loading extension: %s"),407 'file': ext_path})
329 unicode(ex))408 continue
409 new_ext = new_ext_class()
410 self._check_extension(new_ext)
411 self._add_extension(new_ext)
412
413 def _add_extension(self, ext):
414 alias = ext.get_alias()
415 LOG.audit(_('Loaded extension: %s'), alias)
416
417 self._check_extension(ext)
418
419 if alias in self.extensions:
420 raise exception.Error("Found duplicate extension: %s" % alias)
421 self.extensions[alias] = ext
330422
331423
332class ResponseExtension(object):424class ResponseExtension(object):
333 """425 """Add data to responses from core nova OpenStack API controllers."""
334 ResponseExtension objects can be used to add data to responses from
335 core nova OpenStack API controllers.
336 """
337426
338 def __init__(self, method, url_route, handler):427 def __init__(self, method, url_route, handler):
339 self.url_route = url_route428 self.url_route = url_route
@@ -343,10 +432,7 @@
343432
344433
345class ActionExtension(object):434class ActionExtension(object):
346 """435 """Add custom actions to core nova OpenStack API controllers."""
347 ActionExtension objects can be used to add custom actions to core nova
348 nova OpenStack API controllers.
349 """
350436
351 def __init__(self, collection, action_name, handler):437 def __init__(self, collection, action_name, handler):
352 self.collection = collection438 self.collection = collection
@@ -355,10 +441,7 @@
355441
356442
357class ResourceExtension(object):443class ResourceExtension(object):
358 """444 """Add top level resources to the OpenStack API in nova."""
359 ResourceExtension objects can be used to add top level resources
360 to the OpenStack API in nova.
361 """
362445
363 def __init__(self, collection, controller, parent=None,446 def __init__(self, collection, controller, parent=None,
364 collection_actions={}, member_actions={}):447 collection_actions={}, member_actions={}):
365448
=== modified file 'nova/api/openstack/faults.py'
--- nova/api/openstack/faults.py 2011-03-17 20:26:52 +0000
+++ nova/api/openstack/faults.py 2011-04-12 11:01:25 +0000
@@ -20,10 +20,10 @@
20import webob.exc20import webob.exc
2121
22from nova import wsgi22from nova import wsgi
23from nova.api.openstack import common
2324
2425
25class Fault(webob.exc.HTTPException):26class Fault(webob.exc.HTTPException):
26
27 """An RS API fault response."""27 """An RS API fault response."""
2828
29 _fault_names = {29 _fault_names = {
@@ -47,7 +47,7 @@
47 """Generate a WSGI response based on the exception passed to ctor."""47 """Generate a WSGI response based on the exception passed to ctor."""
48 # Replace the body with fault details.48 # Replace the body with fault details.
49 code = self.wrapped_exc.status_int49 code = self.wrapped_exc.status_int
50 fault_name = self._fault_names.get(code, "computeFault")50 fault_name = self._fault_names.get(code, "cloudServersFault")
51 fault_data = {51 fault_data = {
52 fault_name: {52 fault_name: {
53 'code': code,53 'code': code,
@@ -57,9 +57,11 @@
57 fault_data[fault_name]['retryAfter'] = retry57 fault_data[fault_name]['retryAfter'] = retry
58 # 'code' is an attribute on the fault tag itself58 # 'code' is an attribute on the fault tag itself
59 metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}59 metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}
60 serializer = wsgi.Serializer(metadata)60 default_xmlns = common.XML_NS_V10
61 serializer = wsgi.Serializer(metadata, default_xmlns)
61 content_type = req.best_match_content_type()62 content_type = req.best_match_content_type()
62 self.wrapped_exc.body = serializer.serialize(fault_data, content_type)63 self.wrapped_exc.body = serializer.serialize(fault_data, content_type)
64 self.wrapped_exc.content_type = content_type
63 return self.wrapped_exc65 return self.wrapped_exc
6466
6567
6668
=== modified file 'nova/api/openstack/flavors.py'
--- nova/api/openstack/flavors.py 2011-03-24 16:46:39 +0000
+++ nova/api/openstack/flavors.py 2011-04-12 11:01:25 +0000
@@ -19,11 +19,11 @@
1919
20from nova import db20from nova import db
21from nova import exception21from nova import exception
22from nova import wsgi22from nova.api.openstack import common
23from nova.api.openstack import views23from nova.api.openstack import views
2424
2525
26class Controller(wsgi.Controller):26class Controller(common.OpenstackController):
27 """Flavor controller for the OpenStack API."""27 """Flavor controller for the OpenStack API."""
2828
29 _serialization_metadata = {29 _serialization_metadata = {
@@ -76,3 +76,6 @@
76 def _get_view_builder(self, req):76 def _get_view_builder(self, req):
77 base_url = req.application_url77 base_url = req.application_url
78 return views.flavors.ViewBuilderV11(base_url)78 return views.flavors.ViewBuilderV11(base_url)
79
80 def get_default_xmlns(self, req):
81 return common.XML_NS_V11
7982
=== modified file 'nova/api/openstack/image_metadata.py'
--- nova/api/openstack/image_metadata.py 2011-03-25 14:07:42 +0000
+++ nova/api/openstack/image_metadata.py 2011-04-12 11:01:25 +0000
@@ -18,15 +18,17 @@
18from webob import exc18from webob import exc
1919
20from nova import flags20from nova import flags
21from nova import quota
21from nova import utils22from nova import utils
22from nova import wsgi23from nova import wsgi
24from nova.api.openstack import common
23from nova.api.openstack import faults25from nova.api.openstack import faults
2426
2527
26FLAGS = flags.FLAGS28FLAGS = flags.FLAGS
2729
2830
29class Controller(wsgi.Controller):31class Controller(common.OpenstackController):
30 """The image metadata API controller for the Openstack API"""32 """The image metadata API controller for the Openstack API"""
3133
32 def __init__(self):34 def __init__(self):
@@ -39,6 +41,15 @@
39 metadata = image.get('properties', {})41 metadata = image.get('properties', {})
40 return metadata42 return metadata
4143
44 def _check_quota_limit(self, context, metadata):
45 if metadata is None:
46 return
47 num_metadata = len(metadata)
48 quota_metadata = quota.allowed_metadata_items(context, num_metadata)
49 if quota_metadata < num_metadata:
50 expl = _("Image metadata limit exceeded")
51 raise exc.HTTPBadRequest(explanation=expl)
52
42 def index(self, req, image_id):53 def index(self, req, image_id):
43 """Returns the list of metadata for a given instance"""54 """Returns the list of metadata for a given instance"""
44 context = req.environ['nova.context']55 context = req.environ['nova.context']
@@ -61,6 +72,7 @@
61 if 'metadata' in body:72 if 'metadata' in body:
62 for key, value in body['metadata'].iteritems():73 for key, value in body['metadata'].iteritems():
63 metadata[key] = value74 metadata[key] = value
75 self._check_quota_limit(context, metadata)
64 img['properties'] = metadata76 img['properties'] = metadata
65 self.image_service.update(context, image_id, img, None)77 self.image_service.update(context, image_id, img, None)
66 return dict(metadata=metadata)78 return dict(metadata=metadata)
@@ -77,6 +89,7 @@
77 img = self.image_service.show(context, image_id)89 img = self.image_service.show(context, image_id)
78 metadata = self._get_metadata(context, image_id, img)90 metadata = self._get_metadata(context, image_id, img)
79 metadata[id] = body[id]91 metadata[id] = body[id]
92 self._check_quota_limit(context, metadata)
80 img['properties'] = metadata93 img['properties'] = metadata
81 self.image_service.update(context, image_id, img, None)94 self.image_service.update(context, image_id, img, None)
8295
8396
=== modified file 'nova/api/openstack/images.py'
--- nova/api/openstack/images.py 2011-03-24 21:13:55 +0000
+++ nova/api/openstack/images.py 2011-04-12 11:01:25 +0000
@@ -1,6 +1,4 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=41# Copyright 2011 OpenStack LLC.
2
3# Copyright 2010 OpenStack LLC.
4# All Rights Reserved.2# All Rights Reserved.
5#3#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may4# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -15,248 +13,143 @@
15# License for the specific language governing permissions and limitations13# License for the specific language governing permissions and limitations
16# under the License.14# under the License.
1715
18import datetime16import webob.exc
19
20from webob import exc
2117
22from nova import compute18from nova import compute
23from nova import exception19from nova import exception
24from nova import flags20from nova import flags
25from nova import log21from nova import log
26from nova import utils22from nova import utils
27from nova import wsgi
28import nova.api.openstack
29from nova.api.openstack import common23from nova.api.openstack import common
30from nova.api.openstack import faults24from nova.api.openstack import faults
31import nova.image.service25from nova.api.openstack.views import images as images_view
3226
3327
34LOG = log.getLogger('nova.api.openstack.images')28LOG = log.getLogger('nova.api.openstack.images')
35
36FLAGS = flags.FLAGS29FLAGS = flags.FLAGS
3730
3831
39def _translate_keys(item):32class Controller(common.OpenstackController):
40 """33 """Base `wsgi.Controller` for retrieving/displaying images."""
41 Maps key names to Rackspace-like attributes for return
42 also pares down attributes to those we want
43 item is a dict
44
45 Note: should be removed when the set of keys expected by the api
46 and the set of keys returned by the image service are equivalent
47
48 """
49 # TODO(tr3buchet): this map is specific to s3 object store,
50 # replace with a list of keys for _filter_keys later
51 mapped_keys = {'status': 'imageState',
52 'id': 'imageId',
53 'name': 'imageLocation'}
54
55 mapped_item = {}
56 # TODO(tr3buchet):
57 # this chunk of code works with s3 and the local image service/glance
58 # when we switch to glance/local image service it can be replaced with
59 # a call to _filter_keys, and mapped_keys can be changed to a list
60 try:
61 for k, v in mapped_keys.iteritems():
62 # map s3 fields
63 mapped_item[k] = item[v]
64 except KeyError:
65 # return only the fields api expects
66 mapped_item = _filter_keys(item, mapped_keys.keys())
67
68 return mapped_item
69
70
71def _translate_status(item):
72 """
73 Translates status of image to match current Rackspace api bindings
74 item is a dict
75
76 Note: should be removed when the set of statuses expected by the api
77 and the set of statuses returned by the image service are equivalent
78
79 """
80 status_mapping = {
81 'pending': 'queued',
82 'decrypting': 'preparing',
83 'untarring': 'saving',
84 'available': 'active'}
85 try:
86 item['status'] = status_mapping[item['status']]
87 except KeyError:
88 # TODO(sirp): Performing translation of status (if necessary) here for
89 # now. Perhaps this should really be done in EC2 API and
90 # S3ImageService
91 pass
92
93
94def _filter_keys(item, keys):
95 """
96 Filters all model attributes except for keys
97 item is a dict
98
99 """
100 return dict((k, v) for k, v in item.iteritems() if k in keys)
101
102
103def _convert_image_id_to_hash(image):
104 if 'imageId' in image:
105 # Convert EC2-style ID (i-blah) to Rackspace-style (int)
106 image_id = abs(hash(image['imageId']))
107 image['imageId'] = image_id
108 image['id'] = image_id
109
110
111def _translate_s3_like_images(image_metadata):
112 """Work-around for leaky S3ImageService abstraction"""
113 api_metadata = image_metadata.copy()
114 _convert_image_id_to_hash(api_metadata)
115 api_metadata = _translate_keys(api_metadata)
116 _translate_status(api_metadata)
117 return api_metadata
118
119
120def _translate_from_image_service_to_api(image_metadata):
121 """Translate from ImageService to OpenStack API style attribute names
122
123 This involves 4 steps:
124
125 1. Filter out attributes that the OpenStack API doesn't need
126
127 2. Translate from base image attributes from names used by
128 BaseImageService to names used by OpenStack API
129
130 3. Add in any image properties
131
132 4. Format values according to API spec (for example dates must
133 look like "2010-08-10T12:00:00Z")
134 """
135 service_metadata = image_metadata.copy()
136 properties = service_metadata.pop('properties', {})
137
138 # 1. Filter out unecessary attributes
139 api_keys = ['id', 'name', 'updated_at', 'created_at', 'status']
140 api_metadata = utils.subset_dict(service_metadata, api_keys)
141
142 # 2. Translate base image attributes
143 api_map = {'updated_at': 'updated', 'created_at': 'created'}
144 api_metadata = utils.map_dict_keys(api_metadata, api_map)
145
146 # 3. Add in any image properties
147 # 3a. serverId is used for backups and snapshots
148 try:
149 api_metadata['serverId'] = int(properties['instance_id'])
150 except KeyError:
151 pass # skip if it's not present
152 except ValueError:
153 pass # skip if it's not an integer
154
155 # 3b. Progress special case
156 # TODO(sirp): ImageService doesn't have a notion of progress yet, so for
157 # now just fake it
158 if service_metadata['status'] == 'saving':
159 api_metadata['progress'] = 0
160
161 # 4. Format values
162 # 4a. Format Image Status (API requires uppercase)
163 api_metadata['status'] = _format_status_for_api(api_metadata['status'])
164
165 # 4b. Format timestamps
166 for attr in ('created', 'updated'):
167 if attr in api_metadata:
168 api_metadata[attr] = _format_datetime_for_api(
169 api_metadata[attr])
170
171 return api_metadata
172
173
174def _format_status_for_api(status):
175 """Return status in a format compliant with OpenStack API"""
176 mapping = {'queued': 'QUEUED',
177 'preparing': 'PREPARING',
178 'saving': 'SAVING',
179 'active': 'ACTIVE',
180 'killed': 'FAILED'}
181 return mapping[status]
182
183
184def _format_datetime_for_api(datetime_):
185 """Stringify datetime objects in a format compliant with OpenStack API"""
186 API_DATETIME_FMT = '%Y-%m-%dT%H:%M:%SZ'
187 return datetime_.strftime(API_DATETIME_FMT)
188
189
190def _safe_translate(image_metadata):
191 """Translate attributes for OpenStack API, temporary workaround for
192 S3ImageService attribute leakage.
193 """
194 # FIXME(sirp): The S3ImageService appears to be leaking implementation
195 # details, including its internal attribute names, and internal
196 # `status` values. Working around it for now.
197 s3_like_image = ('imageId' in image_metadata)
198 if s3_like_image:
199 translate = _translate_s3_like_images
200 else:
201 translate = _translate_from_image_service_to_api
202 return translate(image_metadata)
203
204
205class Controller(wsgi.Controller):
20634
207 _serialization_metadata = {35 _serialization_metadata = {
208 'application/xml': {36 'application/xml': {
209 "attributes": {37 "attributes": {
210 "image": ["id", "name", "updated", "created", "status",38 "image": ["id", "name", "updated", "created", "status",
211 "serverId", "progress"]}}}39 "serverId", "progress"],
21240 "link": ["rel", "type", "href"],
213 def __init__(self):41 },
214 self._service = utils.import_object(FLAGS.image_service)42 },
43 }
44
45 def __init__(self, image_service=None, compute_service=None):
46 """Initialize new `ImageController`.
47
48 :param compute_service: `nova.compute.api:API`
49 :param image_service: `nova.image.service:BaseImageService`
50 """
51 _default_service = utils.import_object(flags.FLAGS.image_service)
52
53 self._compute_service = compute_service or compute.API()
54 self._image_service = image_service or _default_service
21555
216 def index(self, req):56 def index(self, req):
217 """Return all public images in brief"""57 """Return an index listing of images available to the request.
58
59 :param req: `wsgi.Request` object
60 """
218 context = req.environ['nova.context']61 context = req.environ['nova.context']
219 image_metas = self._service.index(context)62 images = self._image_service.index(context)
220 image_metas = common.limited(image_metas, req)63 images = common.limited(images, req)
221 return dict(images=image_metas)64 builder = self.get_builder(req).build
65 return dict(images=[builder(image, detail=False) for image in images])
22266
223 def detail(self, req):67 def detail(self, req):
224 """Return all public images in detail"""68 """Return a detailed index listing of images available to the request.
69
70 :param req: `wsgi.Request` object.
71 """
225 context = req.environ['nova.context']72 context = req.environ['nova.context']
226 image_metas = self._service.detail(context)73 images = self._image_service.detail(context)
227 image_metas = common.limited(image_metas, req)74 images = common.limited(images, req)
228 api_image_metas = [_safe_translate(image_meta)75 builder = self.get_builder(req).build
229 for image_meta in image_metas]76 return dict(images=[builder(image, detail=True) for image in images])
230 return dict(images=api_image_metas)
23177
232 def show(self, req, id):78 def show(self, req, id):
233 """Return data about the given image id"""79 """Return detailed information about a specific image.
80
81 :param req: `wsgi.Request` object
82 :param id: Image identifier (integer)
83 """
234 context = req.environ['nova.context']84 context = req.environ['nova.context']
235 try:85
236 image_id = common.get_image_id_from_image_hash(86 try:
237 self._service, context, id)87 image_id = int(id)
88 except ValueError:
89 explanation = _("Image not found.")
90 raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation))
91
92 try:
93 image = self._image_service.show(context, image_id)
238 except exception.NotFound:94 except exception.NotFound:
239 raise faults.Fault(exc.HTTPNotFound())95 explanation = _("Image '%d' not found.") % (image_id)
96 raise faults.Fault(webob.exc.HTTPNotFound(explanation=explanation))
24097
241 image_meta = self._service.show(context, image_id)98 return dict(image=self.get_builder(req).build(image, detail=True))
242 api_image_meta = _safe_translate(image_meta)
243 return dict(image=api_image_meta)
24499
245 def delete(self, req, id):100 def delete(self, req, id):
246 # Only public images are supported for now.101 """Delete an image, if allowed.
247 raise faults.Fault(exc.HTTPNotFound())102
103 :param req: `wsgi.Request` object
104 :param id: Image identifier (integer)
105 """
106 image_id = id
107 context = req.environ['nova.context']
108 self._image_service.delete(context, image_id)
109 return webob.exc.HTTPNoContent()
248110
249 def create(self, req):111 def create(self, req):
112 """Snapshot a server instance and save the image.
113
114 :param req: `wsgi.Request` object
115 """
250 context = req.environ['nova.context']116 context = req.environ['nova.context']
251 env = self._deserialize(req.body, req.get_content_type())117 content_type = req.get_content_type()
252 instance_id = env["image"]["serverId"]118 image = self._deserialize(req.body, content_type)
253 name = env["image"]["name"]119
254 image_meta = compute.API().snapshot(120 if not image:
255 context, instance_id, name)121 raise webob.exc.HTTPBadRequest()
256 api_image_meta = _safe_translate(image_meta)122
257 return dict(image=api_image_meta)123 try:
258124 server_id = image["image"]["serverId"]
259 def update(self, req, id):125 image_name = image["image"]["name"]
260 # Users may not modify public images, and that's all that126 except KeyError:
261 # we support for now.127 raise webob.exc.HTTPBadRequest()
262 raise faults.Fault(exc.HTTPNotFound())128
129 image = self._compute_service.snapshot(context, server_id, image_name)
130 return self.get_builder(req).build(image, detail=True)
131
132 def get_builder(self, request):
133 """Indicates that you must use a Controller subclass."""
134 raise NotImplementedError
135
136
137class ControllerV10(Controller):
138 """Version 1.0 specific controller logic."""
139
140 def get_builder(self, request):
141 """Property to get the ViewBuilder class we need to use."""
142 base_url = request.application_url
143 return images_view.ViewBuilderV10(base_url)
144
145
146class ControllerV11(Controller):
147 """Version 1.1 specific controller logic."""
148
149 def get_builder(self, request):
150 """Property to get the ViewBuilder class we need to use."""
151 base_url = request.application_url
152 return images_view.ViewBuilderV11(base_url)
153
154 def get_default_xmlns(self, req):
155 return common.XML_NS_V11
263156
=== added file 'nova/api/openstack/ips.py'
--- nova/api/openstack/ips.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/ips.py 2011-04-12 11:01:25 +0000
@@ -0,0 +1,72 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2011 OpenStack LLC.
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18import time
19
20from webob import exc
21
22import nova
23import nova.api.openstack.views.addresses
24from nova.api.openstack import common
25from nova.api.openstack import faults
26
27
28class Controller(common.OpenstackController):
29 """The servers addresses API controller for the Openstack API."""
30
31 _serialization_metadata = {
32 'application/xml': {
33 'list_collections': {
34 'public': {'item_name': 'ip', 'item_key': 'addr'},
35 'private': {'item_name': 'ip', 'item_key': 'addr'},
36 },
37 },
38 }
39
40 def __init__(self):
41 self.compute_api = nova.compute.API()
42 self.builder = nova.api.openstack.views.addresses.ViewBuilderV10()
43
44 def index(self, req, server_id):
45 try:
46 instance = self.compute_api.get(req.environ['nova.context'], id)
47 except nova.exception.NotFound:
48 return faults.Fault(exc.HTTPNotFound())
49 return {'addresses': self.builder.build(instance)}
50
51 def public(self, req, server_id):
52 try:
53 instance = self.compute_api.get(req.environ['nova.context'], id)
54 except nova.exception.NotFound:
55 return faults.Fault(exc.HTTPNotFound())
56 return {'public': self.builder.build_public_parts(instance)}
57
58 def private(self, req, server_id):
59 try:
60 instance = self.compute_api.get(req.environ['nova.context'], id)
61 except nova.exception.NotFound:
62 return faults.Fault(exc.HTTPNotFound())
63 return {'private': self.builder.build_private_parts(instance)}
64
65 def show(self, req, server_id, id):
66 return faults.Fault(exc.HTTPNotImplemented())
67
68 def create(self, req, server_id):
69 return faults.Fault(exc.HTTPNotImplemented())
70
71 def delete(self, req, server_id, id):
72 return faults.Fault(exc.HTTPNotImplemented())
073
=== modified file 'nova/api/openstack/limits.py'
--- nova/api/openstack/limits.py 2011-03-17 20:26:52 +0000
+++ nova/api/openstack/limits.py 2011-04-12 11:01:25 +0000
@@ -31,8 +31,8 @@
31from webob.dec import wsgify31from webob.dec import wsgify
3232
33from nova import wsgi33from nova import wsgi
34from nova.api.openstack import common
34from nova.api.openstack import faults35from nova.api.openstack import faults
35from nova.wsgi import Controller
36from nova.wsgi import Middleware36from nova.wsgi import Middleware
3737
3838
@@ -43,7 +43,7 @@
43PER_DAY = 60 * 60 * 2443PER_DAY = 60 * 60 * 24
4444
4545
46class LimitsController(Controller):46class LimitsController(common.OpenstackController):
47 """47 """
48 Controller for accessing limits in the OpenStack API.48 Controller for accessing limits in the OpenStack API.
49 """49 """
5050
=== modified file 'nova/api/openstack/server_metadata.py'
--- nova/api/openstack/server_metadata.py 2011-03-22 14:01:18 +0000
+++ nova/api/openstack/server_metadata.py 2011-04-12 11:01:25 +0000
@@ -19,10 +19,11 @@
1919
20from nova import compute20from nova import compute
21from nova import wsgi21from nova import wsgi
22from nova.api.openstack import common
22from nova.api.openstack import faults23from nova.api.openstack import faults
2324
2425
25class Controller(wsgi.Controller):26class Controller(common.OpenstackController):
26 """ The server metadata API controller for the Openstack API """27 """ The server metadata API controller for the Openstack API """
2728
28 def __init__(self):29 def __init__(self):
2930
=== modified file 'nova/api/openstack/servers.py'
--- nova/api/openstack/servers.py 2011-03-28 08:27:17 +0000
+++ nova/api/openstack/servers.py 2011-04-12 11:01:25 +0000
@@ -44,7 +44,7 @@
44FLAGS = flags.FLAGS44FLAGS = flags.FLAGS
4545
4646
47class Controller(wsgi.Controller):47class Controller(common.OpenstackController):
48 """ The Server API controller for the OpenStack API """48 """ The Server API controller for the OpenStack API """
4949
50 _serialization_metadata = {50 _serialization_metadata = {
@@ -55,6 +55,13 @@
55 "imageRef", "vnics"],55 "imageRef", "vnics"],
56 "link": ["rel", "type", "href"],56 "link": ["rel", "type", "href"],
57 },57 },
58 "dict_collections": {
59 "metadata": {"item_name": "meta", "item_key": "key"},
60 },
61 "list_collections": {
62 "public": {"item_name": "ip", "item_key": "addr"},
63 "private": {"item_name": "ip", "item_key": "addr"},
64 },
58 },65 },
59 }66 }
6067
@@ -63,15 +70,6 @@
63 self._image_service = utils.import_object(FLAGS.image_service)70 self._image_service = utils.import_object(FLAGS.image_service)
64 super(Controller, self).__init__()71 super(Controller, self).__init__()
6572
66 def ips(self, req, id):
67 try:
68 instance = self.compute_api.get(req.environ['nova.context'], id)
69 except exception.NotFound:
70 return faults.Fault(exc.HTTPNotFound())
71
72 builder = self._get_addresses_view_builder(req)
73 return builder.build(instance)
74
75 def index(self, req):73 def index(self, req):
76 """ Returns a list of server names and ids for a given user """74 """ Returns a list of server names and ids for a given user """
77 return self._items(req, is_detail=False)75 return self._items(req, is_detail=False)
@@ -151,16 +149,27 @@
151149
152 vnics = [env['server']['vnics']] if 'vnics' in env['server'] else None150 vnics = [env['server']['vnics']] if 'vnics' in env['server'] else None
153 flavor_id = self._flavor_id_from_req_data(env)151 flavor_id = self._flavor_id_from_req_data(env)
152
153 if not 'name' in env['server']:
154 msg = _("Server name is not defined")
155 return exc.HTTPBadRequest(msg)
156
157 name = env['server']['name']
158 self._validate_server_name(name)
159 name = name.strip()
160
154 try:161 try:
162 inst_type = \
163 instance_types.get_instance_type_by_flavor_id(flavor_id)
155 (inst,) = self.compute_api.create(164 (inst,) = self.compute_api.create(
156 context,165 context,
157 instance_types.get_by_flavor_id(flavor_id),166 inst_type,
158 image_id,167 image_id,
159 vnic_list=vnics,168 vnic_list=vnics,
160 kernel_id=kernel_id,169 kernel_id=kernel_id,
161 ramdisk_id=ramdisk_id,170 ramdisk_id=ramdisk_id,
162 display_name=env['server']['name'],171 display_name=name,
163 display_description=env['server']['name'],172 display_description=name,
164 key_name=key_name,173 key_name=key_name,
165 key_data=key_data,174 key_data=key_data,
166 metadata=metadata,175 metadata=metadata,
@@ -168,13 +177,12 @@
168 except quota.QuotaError as error:177 except quota.QuotaError as error:
169 self._handle_quota_error(error)178 self._handle_quota_error(error)
170179
171 inst['instance_type'] = flavor_id180 inst['instance_type'] = inst_type
172 inst['image_id'] = requested_image_id181 inst['image_id'] = requested_image_id
173182
174 builder = self._get_view_builder(req)183 builder = self._get_view_builder(req)
175 server = builder.build(inst, is_detail=True)184 server = builder.build(inst, is_detail=True)
176 password = "%s%s" % (server['server']['name'][:4],185 password = utils.generate_password(16)
177 utils.generate_password(12))
178 server['server']['adminPass'] = password186 server['server']['adminPass'] = password
179 self.compute_api.set_admin_password(context, server['server']['id'],187 self.compute_api.set_admin_password(context, server['server']['id'],
180 password)188 password)
@@ -248,31 +256,45 @@
248256
249 ctxt = req.environ['nova.context']257 ctxt = req.environ['nova.context']
250 update_dict = {}258 update_dict = {}
251 if 'adminPass' in inst_dict['server']:259
252 update_dict['admin_pass'] = inst_dict['server']['adminPass']
253 try:
254 self.compute_api.set_admin_password(ctxt, id)
255 except exception.TimeoutException:
256 return exc.HTTPRequestTimeout()
257 if 'name' in inst_dict['server']:260 if 'name' in inst_dict['server']:
258 update_dict['display_name'] = inst_dict['server']['name']261 name = inst_dict['server']['name']
262 self._validate_server_name(name)
263 update_dict['display_name'] = name.strip()
264
265 self._parse_update(ctxt, id, inst_dict, update_dict)
266
259 try:267 try:
260 self.compute_api.update(ctxt, id, **update_dict)268 self.compute_api.update(ctxt, id, **update_dict)
261 except exception.NotFound:269 except exception.NotFound:
262 return faults.Fault(exc.HTTPNotFound())270 return faults.Fault(exc.HTTPNotFound())
271
263 return exc.HTTPNoContent()272 return exc.HTTPNoContent()
264273
274 def _validate_server_name(self, value):
275 if not isinstance(value, basestring):
276 msg = _("Server name is not a string or unicode")
277 raise exc.HTTPBadRequest(msg)
278
279 if value.strip() == '':
280 msg = _("Server name is an empty string")
281 raise exc.HTTPBadRequest(msg)
282
283 def _parse_update(self, context, id, inst_dict, update_dict):
284 pass
285
265 @scheduler_api.redirect_handler286 @scheduler_api.redirect_handler
266 def action(self, req, id):287 def action(self, req, id):
267 """Multi-purpose method used to reboot, rebuild, or288 """Multi-purpose method used to reboot, rebuild, or
268 resize a server"""289 resize a server"""
269290
270 actions = {291 actions = {
271 'reboot': self._action_reboot,292 'changePassword': self._action_change_password,
272 'resize': self._action_resize,293 'reboot': self._action_reboot,
294 'resize': self._action_resize,
273 'confirmResize': self._action_confirm_resize,295 'confirmResize': self._action_confirm_resize,
274 'revertResize': self._action_revert_resize,296 'revertResize': self._action_revert_resize,
275 'rebuild': self._action_rebuild,297 'rebuild': self._action_rebuild,
276 }298 }
277299
278 input_dict = self._deserialize(req.body, req.get_content_type())300 input_dict = self._deserialize(req.body, req.get_content_type())
@@ -281,6 +303,9 @@
281 return actions[key](input_dict, req, id)303 return actions[key](input_dict, req, id)
282 return faults.Fault(exc.HTTPNotImplemented())304 return faults.Fault(exc.HTTPNotImplemented())
283305
306 def _action_change_password(self, input_dict, req, id):
307 return exc.HTTPNotImplemented()
308
284 def _action_confirm_resize(self, input_dict, req, id):309 def _action_confirm_resize(self, input_dict, req, id):
285 try:310 try:
286 self.compute_api.confirm_resize(req.environ['nova.context'], id)311 self.compute_api.confirm_resize(req.environ['nova.context'], id)
@@ -479,7 +504,7 @@
479504
480 @scheduler_api.redirect_handler505 @scheduler_api.redirect_handler
481 def get_ajax_console(self, req, id):506 def get_ajax_console(self, req, id):
482 """ Returns a url to an instance's ajaxterm console. """507 """Returns a url to an instance's ajaxterm console."""
483 try:508 try:
484 self.compute_api.get_ajax_console(req.environ['nova.context'],509 self.compute_api.get_ajax_console(req.environ['nova.context'],
485 int(id))510 int(id))
@@ -488,6 +513,16 @@
488 return exc.HTTPAccepted()513 return exc.HTTPAccepted()
489514
490 @scheduler_api.redirect_handler515 @scheduler_api.redirect_handler
516 def get_vnc_console(self, req, id):
517 """Returns a url to an instance's ajaxterm console."""
518 try:
519 self.compute_api.get_vnc_console(req.environ['nova.context'],
520 int(id))
521 except exception.NotFound:
522 return faults.Fault(exc.HTTPNotFound())
523 return exc.HTTPAccepted()
524
525 @scheduler_api.redirect_handler
491 def diagnostics(self, req, id):526 def diagnostics(self, req, id):
492 """Permit Admins to retrieve server diagnostics."""527 """Permit Admins to retrieve server diagnostics."""
493 ctxt = req.environ["nova.context"]528 ctxt = req.environ["nova.context"]
@@ -532,7 +567,7 @@
532 _("Cannot build from image %(image_id)s, status not active") %567 _("Cannot build from image %(image_id)s, status not active") %
533 locals())568 locals())
534569
535 if image_meta['properties']['disk_format'] != 'ami':570 if image_meta.get('container_format') != 'ami':
536 return None, None571 return None, None
537572
538 try:573 try:
@@ -568,6 +603,14 @@
568 def _limit_items(self, items, req):603 def _limit_items(self, items, req):
569 return common.limited(items, req)604 return common.limited(items, req)
570605
606 def _parse_update(self, context, server_id, inst_dict, update_dict):
607 if 'adminPass' in inst_dict['server']:
608 update_dict['admin_pass'] = inst_dict['server']['adminPass']
609 try:
610 self.compute_api.set_admin_password(context, server_id)
611 except exception.TimeoutException:
612 return exc.HTTPRequestTimeout()
613
571614
572class ControllerV11(Controller):615class ControllerV11(Controller):
573 def _image_id_from_req_data(self, data):616 def _image_id_from_req_data(self, data):
@@ -591,9 +634,25 @@
591 def _get_addresses_view_builder(self, req):634 def _get_addresses_view_builder(self, req):
592 return nova.api.openstack.views.addresses.ViewBuilderV11(req)635 return nova.api.openstack.views.addresses.ViewBuilderV11(req)
593636
637 def _action_change_password(self, input_dict, req, id):
638 context = req.environ['nova.context']
639 if (not 'changePassword' in input_dict
640 or not 'adminPass' in input_dict['changePassword']):
641 msg = _("No adminPass was specified")
642 return exc.HTTPBadRequest(msg)
643 password = input_dict['changePassword']['adminPass']
644 if not isinstance(password, basestring) or password == '':
645 msg = _("Invalid adminPass")
646 return exc.HTTPBadRequest(msg)
647 self.compute_api.set_admin_password(context, id, password)
648 return exc.HTTPAccepted()
649
594 def _limit_items(self, items, req):650 def _limit_items(self, items, req):
595 return common.limited_by_marker(items, req)651 return common.limited_by_marker(items, req)
596652
653 def get_default_xmlns(self, req):
654 return common.XML_NS_V11
655
597656
598class ServerCreateRequestXMLDeserializer(object):657class ServerCreateRequestXMLDeserializer(object):
599 """658 """
600659
=== modified file 'nova/api/openstack/shared_ip_groups.py'
--- nova/api/openstack/shared_ip_groups.py 2011-03-09 18:10:45 +0000
+++ nova/api/openstack/shared_ip_groups.py 2011-04-12 11:01:25 +0000
@@ -17,7 +17,7 @@
1717
18from webob import exc18from webob import exc
1919
20from nova import wsgi20from nova.api.openstack import common
21from nova.api.openstack import faults21from nova.api.openstack import faults
2222
2323
@@ -32,7 +32,7 @@
32 return dict(sharedIpGroups=inst)32 return dict(sharedIpGroups=inst)
3333
3434
35class Controller(wsgi.Controller):35class Controller(common.OpenstackController):
36 """ The Shared IP Groups Controller for the Openstack API """36 """ The Shared IP Groups Controller for the Openstack API """
3737
38 _serialization_metadata = {38 _serialization_metadata = {
@@ -42,11 +42,11 @@
4242
43 def index(self, req):43 def index(self, req):
44 """ Returns a list of Shared IP Groups for the user """44 """ Returns a list of Shared IP Groups for the user """
45 return dict(sharedIpGroups=[])45 raise faults.Fault(exc.HTTPNotImplemented())
4646
47 def show(self, req, id):47 def show(self, req, id):
48 """ Shows in-depth information on a specific Shared IP Group """48 """ Shows in-depth information on a specific Shared IP Group """
49 return _translate_keys({})49 raise faults.Fault(exc.HTTPNotImplemented())
5050
51 def update(self, req, id):51 def update(self, req, id):
52 """ You can't update a Shared IP Group """52 """ You can't update a Shared IP Group """
@@ -58,7 +58,7 @@
5858
59 def detail(self, req):59 def detail(self, req):
60 """ Returns a complete list of Shared IP Groups """60 """ Returns a complete list of Shared IP Groups """
61 return _translate_detail_keys({})61 raise faults.Fault(exc.HTTPNotImplemented())
6262
63 def create(self, req):63 def create(self, req):
64 """ Creates a new Shared IP group """64 """ Creates a new Shared IP group """
6565
=== modified file 'nova/api/openstack/users.py'
--- nova/api/openstack/users.py 2011-03-16 19:15:57 +0000
+++ nova/api/openstack/users.py 2011-04-12 11:01:25 +0000
@@ -18,7 +18,6 @@
18from nova import exception18from nova import exception
19from nova import flags19from nova import flags
20from nova import log as logging20from nova import log as logging
21from nova import wsgi
22from nova.api.openstack import common21from nova.api.openstack import common
23from nova.api.openstack import faults22from nova.api.openstack import faults
24from nova.auth import manager23from nova.auth import manager
@@ -35,7 +34,7 @@
35 admin=user.admin)34 admin=user.admin)
3635
3736
38class Controller(wsgi.Controller):37class Controller(common.OpenstackController):
3938
40 _serialization_metadata = {39 _serialization_metadata = {
41 'application/xml': {40 'application/xml': {
4241
=== modified file 'nova/api/openstack/versions.py'
--- nova/api/openstack/versions.py 2011-03-22 18:09:23 +0000
+++ nova/api/openstack/versions.py 2011-04-12 11:01:25 +0000
@@ -15,8 +15,8 @@
15# License for the specific language governing permissions and limitations15# License for the specific language governing permissions and limitations
16# under the License.16# under the License.
1717
18import webob
18import webob.dec19import webob.dec
19import webob.exc
2020
21from nova import wsgi21from nova import wsgi
22import nova.api.openstack.views.versions22import nova.api.openstack.views.versions
@@ -51,4 +51,10 @@
51 }51 }
5252
53 content_type = req.best_match_content_type()53 content_type = req.best_match_content_type()
54 return wsgi.Serializer(metadata).serialize(response, content_type)54 body = wsgi.Serializer(metadata).serialize(response, content_type)
55
56 response = webob.Response()
57 response.content_type = content_type
58 response.body = body
59
60 return response
5561
=== modified file 'nova/api/openstack/views/addresses.py'
--- nova/api/openstack/views/addresses.py 2011-03-17 07:21:09 +0000
+++ nova/api/openstack/views/addresses.py 2011-04-12 11:01:25 +0000
@@ -28,10 +28,16 @@
2828
29class ViewBuilderV10(ViewBuilder):29class ViewBuilderV10(ViewBuilder):
30 def build(self, inst):30 def build(self, inst):
31 private_ips = utils.get_from_path(inst, 'fixed_ip/address')31 private_ips = self.build_private_parts(inst)
32 public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')32 public_ips = self.build_public_parts(inst)
33 return dict(public=public_ips, private=private_ips)33 return dict(public=public_ips, private=private_ips)
3434
35 def build_public_parts(self, inst):
36 return utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
37
38 def build_private_parts(self, inst):
39 return utils.get_from_path(inst, 'fixed_ip/address')
40
3541
36class ViewBuilderV11(ViewBuilder):42class ViewBuilderV11(ViewBuilder):
37 def build(self, inst):43 def build(self, inst):
3844
=== modified file 'nova/api/openstack/views/images.py'
--- nova/api/openstack/views/images.py 2011-03-17 07:41:01 +0000
+++ nova/api/openstack/views/images.py 2011-04-12 11:01:25 +0000
@@ -15,20 +15,100 @@
15# License for the specific language governing permissions and limitations15# License for the specific language governing permissions and limitations
16# under the License.16# under the License.
1717
18from nova.api.openstack import common18import os.path
1919
2020
21class ViewBuilder(object):21class ViewBuilder(object):
22 def __init__(self):22 """Base class for generating responses to OpenStack API image requests."""
23 pass23
24
25 def build(self, image_obj):
26 raise NotImplementedError()
27
28
29class ViewBuilderV11(ViewBuilder):
30 def __init__(self, base_url):24 def __init__(self, base_url):
31 self.base_url = base_url25 """Initialize new `ViewBuilder`."""
26 self._url = base_url
27
28 def _format_dates(self, image):
29 """Update all date fields to ensure standardized formatting."""
30 for attr in ['created_at', 'updated_at', 'deleted_at']:
31 if image.get(attr) is not None:
32 image[attr] = image[attr].strftime('%Y-%m-%dT%H:%M:%SZ')
33
34 def _format_status(self, image):
35 """Update the status field to standardize format."""
36 status_mapping = {
37 'pending': 'QUEUED',
38 'decrypting': 'PREPARING',
39 'untarring': 'SAVING',
40 'available': 'ACTIVE',
41 'killed': 'FAILED',
42 }
43
44 try:
45 image['status'] = status_mapping[image['status']].upper()
46 except KeyError:
47 image['status'] = image['status'].upper()
3248
33 def generate_href(self, image_id):49 def generate_href(self, image_id):
34 return "%s/images/%s" % (self.base_url, image_id)50 """Return an href string pointing to this object."""
51 return os.path.join(self._url, "images", str(image_id))
52
53 def build(self, image_obj, detail=False):
54 """Return a standardized image structure for display by the API."""
55 properties = image_obj.get("properties", {})
56
57 self._format_dates(image_obj)
58
59 if "status" in image_obj:
60 self._format_status(image_obj)
61
62 image = {
63 "id": image_obj.get("id"),
64 "name": image_obj.get("name"),
65 }
66
67 if "instance_id" in properties:
68 try:
69 image["serverId"] = int(properties["instance_id"])
70 except ValueError:
71 pass
72
73 if detail:
74 image.update({
75 "created": image_obj.get("created_at"),
76 "updated": image_obj.get("updated_at"),
77 "status": image_obj.get("status"),
78 })
79
80 if image["status"] == "SAVING":
81 image["progress"] = 0
82
83 return image
84
85
86class ViewBuilderV10(ViewBuilder):
87 """OpenStack API v1.0 Image Builder"""
88 pass
89
90
91class ViewBuilderV11(ViewBuilder):
92 """OpenStack API v1.1 Image Builder"""
93
94 def build(self, image_obj, detail=False):
95 """Return a standardized image structure for display by the API."""
96 image = ViewBuilder.build(self, image_obj, detail)
97 href = self.generate_href(image_obj["id"])
98
99 image["links"] = [{
100 "rel": "self",
101 "href": href,
102 },
103 {
104 "rel": "bookmark",
105 "type": "application/json",
106 "href": href,
107 },
108 {
109 "rel": "bookmark",
110 "type": "application/xml",
111 "href": href,
112 }]
113
114 return image
35115
=== modified file 'nova/api/openstack/views/servers.py'
--- nova/api/openstack/views/servers.py 2011-03-24 14:15:50 +0000
+++ nova/api/openstack/views/servers.py 2011-04-12 11:01:25 +0000
@@ -57,16 +57,16 @@
57 def _build_detail(self, inst):57 def _build_detail(self, inst):
58 """Returns a detailed model of a server."""58 """Returns a detailed model of a server."""
59 power_mapping = {59 power_mapping = {
60 None: 'build',60 None: 'BUILD',
61 power_state.NOSTATE: 'build',61 power_state.NOSTATE: 'BUILD',
62 power_state.RUNNING: 'active',62 power_state.RUNNING: 'ACTIVE',
63 power_state.BLOCKED: 'active',63 power_state.BLOCKED: 'ACTIVE',
64 power_state.SUSPENDED: 'suspended',64 power_state.SUSPENDED: 'SUSPENDED',
65 power_state.PAUSED: 'paused',65 power_state.PAUSED: 'PAUSED',
66 power_state.SHUTDOWN: 'active',66 power_state.SHUTDOWN: 'ACTIVE',
67 power_state.SHUTOFF: 'active',67 power_state.SHUTOFF: 'ACTIVE',
68 power_state.CRASHED: 'error',68 power_state.CRASHED: 'ERROR',
69 power_state.FAILED: 'error'}69 power_state.FAILED: 'ERROR'}
7070
71 inst_dict = {71 inst_dict = {
72 'id': int(inst['id']),72 'id': int(inst['id']),
@@ -77,12 +77,12 @@
77 ctxt = nova.context.get_admin_context()77 ctxt = nova.context.get_admin_context()
78 compute_api = nova.compute.API()78 compute_api = nova.compute.API()
79 if compute_api.has_finished_migration(ctxt, inst['id']):79 if compute_api.has_finished_migration(ctxt, inst['id']):
80 inst_dict['status'] = 'resize-confirm'80 inst_dict['status'] = 'RESIZE-CONFIRM'
8181
82 # Return the metadata as a dictionary82 # Return the metadata as a dictionary
83 metadata = {}83 metadata = {}
84 for item in inst.get('metadata', []):84 for item in inst.get('metadata', []):
85 metadata[item['key']] = item['value']85 metadata[item['key']] = str(item['value'])
86 inst_dict['metadata'] = metadata86 inst_dict['metadata'] = metadata
8787
88 inst_dict['hostId'] = ''88 inst_dict['hostId'] = ''
@@ -115,7 +115,7 @@
115115
116 def _build_flavor(self, response, inst):116 def _build_flavor(self, response, inst):
117 if 'instance_type' in dict(inst):117 if 'instance_type' in dict(inst):
118 response['flavorId'] = inst['instance_type']118 response['flavorId'] = inst['instance_type']['flavorid']
119119
120120
121class ViewBuilderV11(ViewBuilder):121class ViewBuilderV11(ViewBuilder):
@@ -134,7 +134,7 @@
134134
135 def _build_flavor(self, response, inst):135 def _build_flavor(self, response, inst):
136 if "instance_type" in dict(inst):136 if "instance_type" in dict(inst):
137 flavor_id = inst["instance_type"]137 flavor_id = inst["instance_type"]['flavorid']
138 flavor_ref = self.flavor_builder.generate_href(flavor_id)138 flavor_ref = self.flavor_builder.generate_href(flavor_id)
139 response["flavorRef"] = flavor_ref139 response["flavorRef"] = flavor_ref
140140
141141
=== modified file 'nova/api/openstack/zones.py'
--- nova/api/openstack/zones.py 2011-03-24 19:04:24 +0000
+++ nova/api/openstack/zones.py 2011-04-12 11:01:25 +0000
@@ -13,12 +13,10 @@
13# License for the specific language governing permissions and limitations13# License for the specific language governing permissions and limitations
14# under the License.14# under the License.
1515
16import common
17
18from nova import db16from nova import db
19from nova import flags17from nova import flags
20from nova import log as logging18from nova import log as logging
21from nova import wsgi19from nova.api.openstack import common
22from nova.scheduler import api20from nova.scheduler import api
2321
2422
@@ -43,7 +41,7 @@
43 'deleted', 'deleted_at', 'updated_at'))41 'deleted', 'deleted_at', 'updated_at'))
4442
4543
46class Controller(wsgi.Controller):44class Controller(common.OpenstackController):
4745
48 _serialization_metadata = {46 _serialization_metadata = {
49 'application/xml': {47 'application/xml': {
5048
=== modified file 'nova/compute/api.py'
--- nova/compute/api.py 2011-04-07 11:20:06 +0000
+++ nova/compute/api.py 2011-04-12 11:01:25 +0000
@@ -38,8 +38,12 @@
38from nova.db import base38from nova.db import base
39from nova.network import service as net_service39from nova.network import service as net_service
4040
41
42LOG = logging.getLogger('nova.compute.api')
43
44
41FLAGS = flags.FLAGS45FLAGS = flags.FLAGS
42LOG = logging.getLogger('nova.compute.api')46flags.DECLARE('vncproxy_topic', 'nova.vnc')
4347
44def generate_default_hostname(instance_id):48def generate_default_hostname(instance_id):
45 """Default function to generate a hostname given an instance reference."""49 """Default function to generate a hostname given an instance reference."""
@@ -107,8 +111,11 @@
107 """Create the number of instances requested if quota and111 """Create the number of instances requested if quota and
108 other arguments check out ok."""112 other arguments check out ok."""
109113
110 type_data = instance_types.get_instance_type(instance_type)114 if not instance_type:
111 num_instances = quota.allowed_instances(context, max_count, type_data)115 instance_type = instance_types.get_default_instance_type()
116
117 num_instances = quota.allowed_instances(context, max_count,
118 instance_type)
112 if num_instances < min_count:119 if num_instances < min_count:
113 pid = context.project_id120 pid = context.project_id
114 LOG.warn(_("Quota exceeeded for %(pid)s,"121 LOG.warn(_("Quota exceeeded for %(pid)s,"
@@ -194,10 +201,10 @@
194 'user_id': context.user_id,201 'user_id': context.user_id,
195 'project_id': context.project_id,202 'project_id': context.project_id,
196 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),203 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
197 'instance_type': instance_type,204 'instance_type_id': instance_type['id'],
198 'memory_mb': type_data['memory_mb'],205 'memory_mb': instance_type['memory_mb'],
199 'vcpus': type_data['vcpus'],206 'vcpus': instance_type['vcpus'],
200 'local_gb': type_data['local_gb'],207 'local_gb': instance_type['local_gb'],
201 'display_name': display_name,208 'display_name': display_name,
202 'display_description': display_description,209 'display_description': display_description,
203 'user_data': user_data or '',210 'user_data': user_data or '',
@@ -373,11 +380,15 @@
373 instance_id)380 instance_id)
374 raise381 raise
375382
376 if (instance['state_description'] == 'terminating'):383 if instance['state_description'] == 'terminating':
377 LOG.warning(_("Instance %s is already being terminated"),384 LOG.warning(_("Instance %s is already being terminated"),
378 instance_id)385 instance_id)
379 return386 return
380387
388 if instance['state_description'] == 'migrating':
389 LOG.warning(_("Instance %s is being migrated"), instance_id)
390 return
391
381 self.update(context,392 self.update(context,
382 instance['id'],393 instance['id'],
383 state_description='terminating',394 state_description='terminating',
@@ -531,8 +542,7 @@
531 def resize(self, context, instance_id, flavor_id):542 def resize(self, context, instance_id, flavor_id):
532 """Resize a running instance."""543 """Resize a running instance."""
533 instance = self.db.instance_get(context, instance_id)544 instance = self.db.instance_get(context, instance_id)
534 current_instance_type = self.db.instance_type_get_by_name(545 current_instance_type = instance['instance_type']
535 context, instance['instance_type'])
536546
537 new_instance_type = self.db.instance_type_get_by_flavor_id(547 new_instance_type = self.db.instance_type_get_by_flavor_id(
538 context, flavor_id)548 context, flavor_id)
@@ -622,6 +632,25 @@
622 return {'url': '%s/?token=%s' % (FLAGS.ajax_console_proxy_url,632 return {'url': '%s/?token=%s' % (FLAGS.ajax_console_proxy_url,
623 output['token'])}633 output['token'])}
624634
635 def get_vnc_console(self, context, instance_id):
636 """Get a url to a VNC Console."""
637 instance = self.get(context, instance_id)
638 output = self._call_compute_message('get_vnc_console',
639 context,
640 instance_id)
641 rpc.call(context, '%s' % FLAGS.vncproxy_topic,
642 {'method': 'authorize_vnc_console',
643 'args': {'token': output['token'],
644 'host': output['host'],
645 'port': output['port']}})
646
647 # hostignore and portignore are compatability params for noVNC
648 return {'url': '%s/vnc_auto.html?token=%s&host=%s&port=%s' % (
649 FLAGS.vncproxy_url,
650 output['token'],
651 'hostignore',
652 'portignore')}
653
625 def get_console_output(self, context, instance_id):654 def get_console_output(self, context, instance_id):
626 """Get console output for an an instance"""655 """Get console output for an an instance"""
627 return self._call_compute_message('get_console_output',656 return self._call_compute_message('get_console_output',
628657
=== modified file 'nova/compute/instance_types.py'
--- nova/compute/instance_types.py 2011-03-03 01:38:42 +0000
+++ nova/compute/instance_types.py 2011-04-12 11:01:25 +0000
@@ -59,8 +59,8 @@
59 rxtx_quota=rxtx_quota,59 rxtx_quota=rxtx_quota,
60 rxtx_cap=rxtx_cap))60 rxtx_cap=rxtx_cap))
61 except exception.DBError, e:61 except exception.DBError, e:
62 LOG.exception(_('DB error: %s' % e))62 LOG.exception(_('DB error: %s') % e)
63 raise exception.ApiError(_("Cannot create instance type: %s" % name))63 raise exception.ApiError(_("Cannot create instance type: %s") % name)
6464
6565
66def destroy(name):66def destroy(name):
@@ -72,8 +72,8 @@
72 try:72 try:
73 db.instance_type_destroy(context.get_admin_context(), name)73 db.instance_type_destroy(context.get_admin_context(), name)
74 except exception.NotFound:74 except exception.NotFound:
75 LOG.exception(_('Instance type %s not found for deletion' % name))75 LOG.exception(_('Instance type %s not found for deletion') % name)
76 raise exception.ApiError(_("Unknown instance type: %s" % name))76 raise exception.ApiError(_("Unknown instance type: %s") % name)
7777
7878
79def purge(name):79def purge(name):
@@ -85,8 +85,8 @@
85 try:85 try:
86 db.instance_type_purge(context.get_admin_context(), name)86 db.instance_type_purge(context.get_admin_context(), name)
87 except exception.NotFound:87 except exception.NotFound:
88 LOG.exception(_('Instance type %s not found for purge' % name))88 LOG.exception(_('Instance type %s not found for purge') % name)
89 raise exception.ApiError(_("Unknown instance type: %s" % name))89 raise exception.ApiError(_("Unknown instance type: %s") % name)
9090
9191
92def get_all_types(inactive=0):92def get_all_types(inactive=0):
@@ -101,41 +101,43 @@
101 return get_all_types(context.get_admin_context())101 return get_all_types(context.get_admin_context())
102102
103103
104def get_instance_type(name):104def get_default_instance_type():
105 name = FLAGS.default_instance_type
106 try:
107 return get_instance_type_by_name(name)
108 except exception.DBError:
109 raise exception.ApiError(_("Unknown instance type: %s") % name)
110
111
112def get_instance_type(id):
113 """Retrieves single instance type by id"""
114 if id is None:
115 return get_default_instance_type()
116 try:
117 ctxt = context.get_admin_context()
118 return db.instance_type_get_by_id(ctxt, id)
119 except exception.DBError:
120 raise exception.ApiError(_("Unknown instance type: %s") % name)
121
122
123def get_instance_type_by_name(name):
105 """Retrieves single instance type by name"""124 """Retrieves single instance type by name"""
106 if name is None:125 if name is None:
107 return FLAGS.default_instance_type126 return get_default_instance_type()
108 try:127 try:
109 ctxt = context.get_admin_context()128 ctxt = context.get_admin_context()
110 inst_type = db.instance_type_get_by_name(ctxt, name)129 return db.instance_type_get_by_name(ctxt, name)
111 return inst_type
112 except exception.DBError:130 except exception.DBError:
113 raise exception.ApiError(_("Unknown instance type: %s" % name))131 raise exception.ApiError(_("Unknown instance type: %s") % name)
114132
115133
116def get_by_type(instance_type):134def get_instance_type_by_flavor_id(flavor_id):
117 """retrieve instance type name"""135 """retrieve instance type by flavor_id"""
118 if instance_type is None:
119 return FLAGS.default_instance_type
120
121 try:
122 ctxt = context.get_admin_context()
123 inst_type = db.instance_type_get_by_name(ctxt, instance_type)
124 return inst_type['name']
125 except exception.DBError, e:
126 LOG.exception(_('DB error: %s' % e))
127 raise exception.ApiError(_("Unknown instance type: %s" %\
128 instance_type))
129
130
131def get_by_flavor_id(flavor_id):
132 """retrieve instance type's name by flavor_id"""
133 if flavor_id is None:136 if flavor_id is None:
134 return FLAGS.default_instance_type137 return get_default_instance_type()
135 try:138 try:
136 ctxt = context.get_admin_context()139 ctxt = context.get_admin_context()
137 flavor = db.instance_type_get_by_flavor_id(ctxt, flavor_id)140 return db.instance_type_get_by_flavor_id(ctxt, flavor_id)
138 return flavor['name']
139 except exception.DBError, e:141 except exception.DBError, e:
140 LOG.exception(_('DB error: %s' % e))142 LOG.exception(_('DB error: %s') % e)
141 raise exception.ApiError(_("Unknown flavor: %s" % flavor_id))143 raise exception.ApiError(_("Unknown flavor: %s") % flavor_id)
142144
=== modified file 'nova/compute/manager.py'
--- nova/compute/manager.py 2011-04-07 01:08:35 +0000
+++ nova/compute/manager.py 2011-04-12 11:01:25 +0000
@@ -140,12 +140,6 @@
140 """140 """
141 self.driver.init_host(host=self.host)141 self.driver.init_host(host=self.host)
142142
143 def periodic_tasks(self, context=None):
144 """Tasks to be run at a periodic interval."""
145 super(ComputeManager, self).periodic_tasks(context)
146 if FLAGS.rescue_timeout > 0:
147 self.driver.poll_rescued_instances(FLAGS.rescue_timeout)
148
149 def _update_state(self, context, instance_id):143 def _update_state(self, context, instance_id):
150 """Update the state of an instance from the driver info."""144 """Update the state of an instance from the driver info."""
151 # FIXME(ja): include other fields from state?145 # FIXME(ja): include other fields from state?
@@ -708,6 +702,15 @@
708702
709 return self.driver.get_ajax_console(instance_ref)703 return self.driver.get_ajax_console(instance_ref)
710704
705 @exception.wrap_exception
706 def get_vnc_console(self, context, instance_id):
707 """Return connection information for an vnc console."""
708 context = context.elevated()
709 LOG.debug(_("instance %s: getting vnc console"), instance_id)
710 instance_ref = self.db.instance_get(context, instance_id)
711
712 return self.driver.get_vnc_console(instance_ref)
713
711 @checks_instance_lock714 @checks_instance_lock
712 def attach_volume(self, context, instance_id, volume_id, mountpoint):715 def attach_volume(self, context, instance_id, volume_id, mountpoint):
713 """Attach a volume to an instance."""716 """Attach a volume to an instance."""
@@ -1014,11 +1017,20 @@
1014 error_list = []1017 error_list = []
10151018
1016 try:1019 try:
1020 if FLAGS.rescue_timeout > 0:
1021 self.driver.poll_rescued_instances(FLAGS.rescue_timeout)
1022 except Exception as ex:
1023 LOG.warning(_("Error during poll_rescued_instances: %s"),
1024 unicode(ex))
1025 error_list.append(ex)
1026
1027 try:
1017 self._poll_instance_states(context)1028 self._poll_instance_states(context)
1018 except Exception as ex:1029 except Exception as ex:
1019 LOG.warning(_("Error during instance poll: %s"),1030 LOG.warning(_("Error during instance poll: %s"),
1020 unicode(ex))1031 unicode(ex))
1021 error_list.append(ex)1032 error_list.append(ex)
1033
1022 return error_list1034 return error_list
10231035
1024 def _poll_instance_states(self, context):1036 def _poll_instance_states(self, context):
@@ -1032,16 +1044,41 @@
10321044
1033 for db_instance in db_instances:1045 for db_instance in db_instances:
1034 name = db_instance['name']1046 name = db_instance['name']
1047 db_state = db_instance['state']
1035 vm_instance = vm_instances.get(name)1048 vm_instance = vm_instances.get(name)
1049
1036 if vm_instance is None:1050 if vm_instance is None:
1037 LOG.info(_("Found instance '%(name)s' in DB but no VM. "1051 # NOTE(justinsb): We have to be very careful here, because a
1038 "Setting state to shutoff.") % locals())1052 # concurrent operation could be in progress (e.g. a spawn)
1039 vm_state = power_state.SHUTOFF1053 if db_state == power_state.NOSTATE:
1054 # Assume that NOSTATE => spawning
1055 # TODO(justinsb): This does mean that if we crash during a
1056 # spawn, the machine will never leave the spawning state,
1057 # but this is just the way nova is; this function isn't
1058 # trying to correct that problem.
1059 # We could have a separate task to correct this error.
1060 # TODO(justinsb): What happens during a live migration?
1061 LOG.info(_("Found instance '%(name)s' in DB but no VM. "
1062 "State=%(db_state)s, so assuming spawn is in "
1063 "progress.") % locals())
1064 vm_state = db_state
1065 else:
1066 LOG.info(_("Found instance '%(name)s' in DB but no VM. "
1067 "State=%(db_state)s, so setting state to "
1068 "shutoff.") % locals())
1069 vm_state = power_state.SHUTOFF
1040 else:1070 else:
1041 vm_state = vm_instance.state1071 vm_state = vm_instance.state
1042 vms_not_found_in_db.remove(name)1072 vms_not_found_in_db.remove(name)
10431073
1044 db_state = db_instance['state']1074 if db_instance['state_description'] == 'migrating':
1075 # A situation which db record exists, but no instance"
1076 # sometimes occurs while live-migration at src compute,
1077 # this case should be ignored.
1078 LOG.debug(_("Ignoring %(name)s, as it's currently being "
1079 "migrated.") % locals())
1080 continue
1081
1045 if vm_state != db_state:1082 if vm_state != db_state:
1046 LOG.info(_("DB/VM state mismatch. Changing state from "1083 LOG.info(_("DB/VM state mismatch. Changing state from "
1047 "'%(db_state)s' to '%(vm_state)s'") % locals())1084 "'%(db_state)s' to '%(vm_state)s'") % locals())
@@ -1049,12 +1086,8 @@
1049 db_instance['id'],1086 db_instance['id'],
1050 vm_state)1087 vm_state)
10511088
1052 if vm_state == power_state.SHUTOFF:1089 # NOTE(justinsb): We no longer auto-remove SHUTOFF instances
1053 # TODO(soren): This is what the compute manager does when you1090 # It's quite hard to get them back when we do.
1054 # terminate an instance. At some point I figure we'll have a
1055 # "terminated" state and some sort of cleanup job that runs
1056 # occasionally, cleaning them out.
1057 self.db.instance_destroy(context, db_instance['id'])
10581091
1059 # Are there VMs not in the DB?1092 # Are there VMs not in the DB?
1060 for vm_not_found_in_db in vms_not_found_in_db:1093 for vm_not_found_in_db in vms_not_found_in_db:
10611094
=== modified file 'nova/crypto.py'
--- nova/crypto.py 2011-03-23 04:31:50 +0000
+++ nova/crypto.py 2011-04-12 11:01:25 +0000
@@ -215,9 +215,12 @@
215215
216def _ensure_project_folder(project_id):216def _ensure_project_folder(project_id):
217 if not os.path.exists(ca_path(project_id)):217 if not os.path.exists(ca_path(project_id)):
218 geninter_sh_path = os.path.join(os.path.dirname(__file__),
219 'CA',
220 'geninter.sh')
218 start = os.getcwd()221 start = os.getcwd()
219 os.chdir(ca_folder())222 os.chdir(ca_folder())
220 utils.execute('sh', 'geninter.sh', project_id,223 utils.execute('sh', geninter_sh_path, project_id,
221 _project_cert_subject(project_id))224 _project_cert_subject(project_id))
222 os.chdir(start)225 os.chdir(start)
223226
@@ -227,13 +230,16 @@
227 csr_fn = os.path.join(project_folder, "server.csr")230 csr_fn = os.path.join(project_folder, "server.csr")
228 crt_fn = os.path.join(project_folder, "server.crt")231 crt_fn = os.path.join(project_folder, "server.crt")
229232
233 genvpn_sh_path = os.path.join(os.path.dirname(__file__),
234 'CA',
235 'genvpn.sh')
230 if os.path.exists(crt_fn):236 if os.path.exists(crt_fn):
231 return237 return
232 _ensure_project_folder(project_id)238 _ensure_project_folder(project_id)
233 start = os.getcwd()239 start = os.getcwd()
234 os.chdir(ca_folder())240 os.chdir(ca_folder())
235 # TODO(vish): the shell scripts could all be done in python241 # TODO(vish): the shell scripts could all be done in python
236 utils.execute('sh', 'genvpn.sh',242 utils.execute('sh', genvpn_sh_path,
237 project_id, _vpn_cert_subject(project_id))243 project_id, _vpn_cert_subject(project_id))
238 with open(csr_fn, "r") as csrfile:244 with open(csr_fn, "r") as csrfile:
239 csr_text = csrfile.read()245 csr_text = csrfile.read()
@@ -263,6 +269,8 @@
263 LOG.debug(_("Flags path: %s"), ca_folder)269 LOG.debug(_("Flags path: %s"), ca_folder)
264 start = os.getcwd()270 start = os.getcwd()
265 # Change working dir to CA271 # Change working dir to CA
272 if not os.path.exists(ca_folder):
273 os.makedirs(ca_folder)
266 os.chdir(ca_folder)274 os.chdir(ca_folder)
267 utils.execute('openssl', 'ca', '-batch', '-out', outbound, '-config',275 utils.execute('openssl', 'ca', '-batch', '-out', outbound, '-config',
268 './openssl.cnf', '-infiles', inbound)276 './openssl.cnf', '-infiles', inbound)
269277
=== modified file 'nova/db/api.py'
--- nova/db/api.py 2011-03-28 08:27:17 +0000
+++ nova/db/api.py 2011-04-12 11:01:25 +0000
@@ -1153,6 +1153,11 @@
1153 return IMPL.instance_type_get_all(context, inactive)1153 return IMPL.instance_type_get_all(context, inactive)
11541154
11551155
1156def instance_type_get_by_id(context, id):
1157 """Get instance type by id"""
1158 return IMPL.instance_type_get_by_id(context, id)
1159
1160
1156def instance_type_get_by_name(context, name):1161def instance_type_get_by_name(context, name):
1157 """Get instance type by name"""1162 """Get instance type by name"""
1158 return IMPL.instance_type_get_by_name(context, name)1163 return IMPL.instance_type_get_by_name(context, name)
11591164
=== modified file 'nova/db/sqlalchemy/api.py'
--- nova/db/sqlalchemy/api.py 2011-03-28 08:27:17 +0000
+++ nova/db/sqlalchemy/api.py 2011-04-12 11:01:25 +0000
@@ -660,7 +660,9 @@
660 filter(models.FixedIp.instance_id != None).\660 filter(models.FixedIp.instance_id != None).\
661 filter_by(allocated=0).\661 filter_by(allocated=0).\
662 update({'instance_id': None,662 update({'instance_id': None,
663 'leased': 0})663 'leased': 0,
664 'updated_at': datetime.datetime.utcnow()},
665 synchronize_session='fetch')
664 return result666 return result
665667
666668
@@ -829,6 +831,7 @@
829 options(joinedload('volumes')).\831 options(joinedload('volumes')).\
830 options(joinedload_all('fixed_ip.network')).\832 options(joinedload_all('fixed_ip.network')).\
831 options(joinedload('metadata')).\833 options(joinedload('metadata')).\
834 options(joinedload('instance_type')).\
832 filter_by(id=instance_id).\835 filter_by(id=instance_id).\
833 filter_by(deleted=can_read_deleted(context)).\836 filter_by(deleted=can_read_deleted(context)).\
834 first()837 first()
@@ -838,6 +841,7 @@
838 options(joinedload_all('security_groups.rules')).\841 options(joinedload_all('security_groups.rules')).\
839 options(joinedload('volumes')).\842 options(joinedload('volumes')).\
840 options(joinedload('metadata')).\843 options(joinedload('metadata')).\
844 options(joinedload('instance_type')).\
841 filter_by(project_id=context.project_id).\845 filter_by(project_id=context.project_id).\
842 filter_by(id=instance_id).\846 filter_by(id=instance_id).\
843 filter_by(deleted=False).\847 filter_by(deleted=False).\
@@ -857,6 +861,7 @@
857 options(joinedload_all('fixed_ip.floating_ips')).\861 options(joinedload_all('fixed_ip.floating_ips')).\
858 options(joinedload('security_groups')).\862 options(joinedload('security_groups')).\
859 options(joinedload_all('fixed_ip.network')).\863 options(joinedload_all('fixed_ip.network')).\
864 options(joinedload('instance_type')).\
860 filter_by(deleted=can_read_deleted(context)).\865 filter_by(deleted=can_read_deleted(context)).\
861 all()866 all()
862867
@@ -868,6 +873,7 @@
868 options(joinedload_all('fixed_ip.floating_ips')).\873 options(joinedload_all('fixed_ip.floating_ips')).\
869 options(joinedload('security_groups')).\874 options(joinedload('security_groups')).\
870 options(joinedload_all('fixed_ip.network')).\875 options(joinedload_all('fixed_ip.network')).\
876 options(joinedload('instance_type')).\
871 filter_by(deleted=can_read_deleted(context)).\877 filter_by(deleted=can_read_deleted(context)).\
872 filter_by(user_id=user_id).\878 filter_by(user_id=user_id).\
873 all()879 all()
@@ -880,6 +886,7 @@
880 options(joinedload_all('fixed_ip.floating_ips')).\886 options(joinedload_all('fixed_ip.floating_ips')).\
881 options(joinedload('security_groups')).\887 options(joinedload('security_groups')).\
882 options(joinedload_all('fixed_ip.network')).\888 options(joinedload_all('fixed_ip.network')).\
889 options(joinedload('instance_type')).\
883 filter_by(host=host).\890 filter_by(host=host).\
884 filter_by(deleted=can_read_deleted(context)).\891 filter_by(deleted=can_read_deleted(context)).\
885 all()892 all()
@@ -894,6 +901,7 @@
894 options(joinedload_all('fixed_ip.floating_ips')).\901 options(joinedload_all('fixed_ip.floating_ips')).\
895 options(joinedload('security_groups')).\902 options(joinedload('security_groups')).\
896 options(joinedload_all('fixed_ip.network')).\903 options(joinedload_all('fixed_ip.network')).\
904 options(joinedload('instance_type')).\
897 filter_by(project_id=project_id).\905 filter_by(project_id=project_id).\
898 filter_by(deleted=can_read_deleted(context)).\906 filter_by(deleted=can_read_deleted(context)).\
899 all()907 all()
@@ -908,6 +916,7 @@
908 options(joinedload_all('fixed_ip.floating_ips')).\916 options(joinedload_all('fixed_ip.floating_ips')).\
909 options(joinedload('security_groups')).\917 options(joinedload('security_groups')).\
910 options(joinedload_all('fixed_ip.network')).\918 options(joinedload_all('fixed_ip.network')).\
919 options(joinedload('instance_type')).\
911 filter_by(reservation_id=reservation_id).\920 filter_by(reservation_id=reservation_id).\
912 filter_by(deleted=can_read_deleted(context)).\921 filter_by(deleted=can_read_deleted(context)).\
913 all()922 all()
@@ -916,6 +925,7 @@
916 options(joinedload_all('fixed_ip.floating_ips')).\925 options(joinedload_all('fixed_ip.floating_ips')).\
917 options(joinedload('security_groups')).\926 options(joinedload('security_groups')).\
918 options(joinedload_all('fixed_ip.network')).\927 options(joinedload_all('fixed_ip.network')).\
928 options(joinedload('instance_type')).\
919 filter_by(project_id=context.project_id).\929 filter_by(project_id=context.project_id).\
920 filter_by(reservation_id=reservation_id).\930 filter_by(reservation_id=reservation_id).\
921 filter_by(deleted=False).\931 filter_by(deleted=False).\
@@ -928,6 +938,7 @@
928 return session.query(models.Instance).\938 return session.query(models.Instance).\
929 options(joinedload_all('fixed_ip.floating_ips')).\939 options(joinedload_all('fixed_ip.floating_ips')).\
930 options(joinedload('security_groups')).\940 options(joinedload('security_groups')).\
941 options(joinedload('instance_type')).\
931 filter_by(project_id=project_id).\942 filter_by(project_id=project_id).\
932 filter_by(image_id=FLAGS.vpn_image_id).\943 filter_by(image_id=FLAGS.vpn_image_id).\
933 filter_by(deleted=can_read_deleted(context)).\944 filter_by(deleted=can_read_deleted(context)).\
@@ -2428,6 +2439,19 @@
24282439
24292440
2430@require_context2441@require_context
2442def instance_type_get_by_id(context, id):
2443 """Returns a dict describing specific instance_type"""
2444 session = get_session()
2445 inst_type = session.query(models.InstanceTypes).\
2446 filter_by(id=id).\
2447 first()
2448 if not inst_type:
2449 raise exception.NotFound(_("No instance type with id %s") % id)
2450 else:
2451 return dict(inst_type)
2452
2453
2454@require_context
2431def instance_type_get_by_name(context, name):2455def instance_type_get_by_name(context, name):
2432 """Returns a dict describing specific instance_type"""2456 """Returns a dict describing specific instance_type"""
2433 session = get_session()2457 session = get_session()
24342458
=== added file 'nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py'
--- nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py 1970-01-01 00:00:00 +0000
+++ nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py 2011-04-12 11:01:25 +0000
@@ -0,0 +1,84 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010 OpenStack LLC.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17from sqlalchemy import *
18from sqlalchemy.sql import text
19from migrate import *
20
21#from nova import log as logging
22
23
24meta = MetaData()
25
26
27c_instance_type = Column('instance_type',
28 String(length=255, convert_unicode=False,
29 assert_unicode=None, unicode_error=None,
30 _warn_on_bytestring=False),
31 nullable=True)
32
33c_instance_type_id = Column('instance_type_id',
34 String(length=255, convert_unicode=False,
35 assert_unicode=None, unicode_error=None,
36 _warn_on_bytestring=False),
37 nullable=True)
38
39instance_types = Table('instance_types', meta,
40 Column('id', Integer(), primary_key=True, nullable=False),
41 Column('name',
42 String(length=255, convert_unicode=False, assert_unicode=None,
43 unicode_error=None, _warn_on_bytestring=False),
44 unique=True))
45
46
47def upgrade(migrate_engine):
48 # Upgrade operations go here. Don't create your own engine;
49 # bind migrate_engine to your metadata
50 meta.bind = migrate_engine
51
52 instances = Table('instances', meta, autoload=True,
53 autoload_with=migrate_engine)
54
55 instances.create_column(c_instance_type_id)
56
57 recs = migrate_engine.execute(instance_types.select())
58 for row in recs:
59 type_id = row[0]
60 type_name = row[1]
61 migrate_engine.execute(instances.update()\
62 .where(instances.c.instance_type == type_name)\
63 .values(instance_type_id=type_id))
64
65 instances.c.instance_type.drop()
66
67
68def downgrade(migrate_engine):
69 meta.bind = migrate_engine
70
71 instances = Table('instances', meta, autoload=True,
72 autoload_with=migrate_engine)
73
74 instances.create_column(c_instance_type)
75
76 recs = migrate_engine.execute(instance_types.select())
77 for row in recs:
78 type_id = row[0]
79 type_name = row[1]
80 migrate_engine.execute(instances.update()\
81 .where(instances.c.instance_type_id == type_id)\
82 .values(instance_type=type_name))
83
84 instances.c.instance_type_id.drop()
085
=== removed file 'nova/db/sqlalchemy/migrate_repo/versions/014_diablo.py'
--- nova/db/sqlalchemy/migrate_repo/versions/014_diablo.py 2011-03-28 08:27:17 +0000
+++ nova/db/sqlalchemy/migrate_repo/versions/014_diablo.py 1970-01-01 00:00:00 +0000
@@ -1,92 +0,0 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright (c) 2011 NTT.
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18from sqlalchemy import *
19from migrate import *
20
21from nova import log as logging
22
23meta = MetaData()
24
25# Just for the ForeignKey and column creation to succeed, these are not the
26# actual definitions of instances or services.
27instances = Table('instances', meta,
28 Column('id', Integer(), primary_key=True, nullable=False),
29 )
30
31#
32# New Tables
33#
34instance_virtual_nic_association = Table(
35 'instance_virtual_nic_association', meta,
36 Column('created_at', DateTime(timezone=False)),
37 Column('updated_at', DateTime(timezone=False)),
38 Column('deleted_at', DateTime(timezone=False)),
39 Column('deleted', Boolean(create_constraint=True, name=None)),
40 Column('id', Integer(), primary_key=True, nullable=False),
41 Column('virtual_nic_id',
42 String(length=255, convert_unicode=False, assert_unicode=None,
43 unicode_error=None, _warn_on_bytestring=False),
44 nullable=False),
45 Column('instance_id', Integer(), ForeignKey('instances.id'),
46 nullable=False),
47 )
48
49project_network_service_association = Table(
50 'project_network_service_association', meta,
51 Column('created_at', DateTime(timezone=False)),
52 Column('updated_at', DateTime(timezone=False)),
53 Column('deleted_at', DateTime(timezone=False)),
54 Column('deleted', Boolean(create_constraint=True, name=None)),
55 Column('id', Integer(), primary_key=True, nullable=False),
56 Column('project_id',
57 String(length=255, convert_unicode=False, assert_unicode=None,
58 unicode_error=None, _warn_on_bytestring=False),
59 nullable=False),
60 Column('network_service',
61 String(length=255, convert_unicode=False, assert_unicode=None,
62 unicode_error=None, _warn_on_bytestring=False),
63 nullable=False),
64 UniqueConstraint('project_id')
65 )
66
67def upgrade(migrate_engine):
68 # Upgrade operations go here. Don't create your own engine;
69 # bind migrate_engine to your metadata
70 meta.bind = migrate_engine
71
72 for table in (instance_virtual_nic_association,
73 project_network_service_association):
74 try:
75 table.create()
76 except Exception:
77 logging.info(repr(table))
78 logging.exception('Exception while creating table')
79 raise
80
81def downgrade(migrate_engine):
82 # Operations to reverse the above upgrade go here.
83 meta.bind = migrate_engine
84
85 for table in (project_network_service_association,
86 instance_virtual_nic_association):
87 try:
88 table.drop()
89 except Exception:
90 logging.info(repr(table))
91 logging.exception('Exception while dropping table')
92 raise
930
=== added file 'nova/db/sqlalchemy/migrate_repo/versions/015_diablo.py'
--- nova/db/sqlalchemy/migrate_repo/versions/015_diablo.py 1970-01-01 00:00:00 +0000
+++ nova/db/sqlalchemy/migrate_repo/versions/015_diablo.py 2011-04-12 11:01:25 +0000
@@ -0,0 +1,93 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright (c) 2011 NTT.
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18from sqlalchemy import *
19from migrate import *
20
21from nova import log as logging
22
23meta = MetaData()
24
25# Just for the ForeignKey and column creation to succeed, these are not the
26# actual definitions of instances or services.
27instances = Table('instances', meta,
28 Column('id', Integer(), primary_key=True, nullable=False),
29 )
30
31#
32# New Tables
33#
34instance_virtual_nic_association = Table(
35 'instance_virtual_nic_association', meta,
36 Column('created_at', DateTime(timezone=False)),
37 Column('updated_at', DateTime(timezone=False)),
38 Column('deleted_at', DateTime(timezone=False)),
39 Column('deleted', Boolean(create_constraint=True, name=None)),
40 Column('id', Integer(), primary_key=True, nullable=False),
41 Column('virtual_nic_id',
42 String(length=255, convert_unicode=False, assert_unicode=None,
43 unicode_error=None, _warn_on_bytestring=False),
44 nullable=False),
45 Column('instance_id', Integer(), ForeignKey('instances.id'),
46 nullable=False),
47 )
48
49project_network_service_association = Table(
50 'project_network_service_association', meta,
51 Column('created_at', DateTime(timezone=False)),
52 Column('updated_at', DateTime(timezone=False)),
53 Column('deleted_at', DateTime(timezone=False)),
54 Column('deleted', Boolean(create_constraint=True, name=None)),
55 Column('id', Integer(), primary_key=True, nullable=False),
56 Column('project_id',
57 String(length=255, convert_unicode=False, assert_unicode=None,
58 unicode_error=None, _warn_on_bytestring=False),
59 nullable=False),
60 Column('network_service',
61 String(length=255, convert_unicode=False, assert_unicode=None,
62 unicode_error=None, _warn_on_bytestring=False),
63 nullable=False),
64 UniqueConstraint('project_id')
65 )
66
67def upgrade(migrate_engine):
68 # Upgrade operations go here. Don't create your own engine;
69 # bind migrate_engine to your metadata
70 meta.bind = migrate_engine
71 return
72
73 for table in (instance_virtual_nic_association,
74 project_network_service_association):
75 try:
76 table.create()
77 except Exception:
78 logging.info(repr(table))
79 logging.exception('Exception while creating table')
80 raise
81
82def downgrade(migrate_engine):
83 # Operations to reverse the above upgrade go here.
84 meta.bind = migrate_engine
85
86 for table in (project_network_service_association,
87 instance_virtual_nic_association):
88 try:
89 table.drop()
90 except Exception:
91 logging.info(repr(table))
92 logging.exception('Exception while dropping table')
93 raise
094
=== modified file 'nova/db/sqlalchemy/models.py'
--- nova/db/sqlalchemy/models.py 2011-03-28 08:27:17 +0000
+++ nova/db/sqlalchemy/models.py 2011-04-12 11:01:25 +0000
@@ -209,7 +209,7 @@
209 hostname = Column(String(255))209 hostname = Column(String(255))
210 host = Column(String(255)) # , ForeignKey('hosts.id'))210 host = Column(String(255)) # , ForeignKey('hosts.id'))
211211
212 instance_type = Column(String(255))212 instance_type_id = Column(String(255))
213213
214 user_data = Column(Text)214 user_data = Column(Text)
215215
@@ -268,6 +268,12 @@
268 rxtx_quota = Column(Integer, nullable=False, default=0)268 rxtx_quota = Column(Integer, nullable=False, default=0)
269 rxtx_cap = Column(Integer, nullable=False, default=0)269 rxtx_cap = Column(Integer, nullable=False, default=0)
270270
271 instances = relationship(Instance,
272 backref=backref('instance_type', uselist=False),
273 foreign_keys=id,
274 primaryjoin='and_(Instance.instance_type_id == '
275 'InstanceTypes.id)')
276
271277
272class Volume(BASE, NovaBase):278class Volume(BASE, NovaBase):
273 """Represents a block storage device that can be attached to a vm."""279 """Represents a block storage device that can be attached to a vm."""
274280
=== added file 'nova/image/fake.py'
--- nova/image/fake.py 1970-01-01 00:00:00 +0000
+++ nova/image/fake.py 2011-04-12 11:01:25 +0000
@@ -0,0 +1,113 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2011 Justin Santa Barbara
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17"""Implementation of an fake image service"""
18
19import copy
20import datetime
21
22from nova import exception
23from nova import flags
24from nova import log as logging
25from nova.image import service
26
27
28LOG = logging.getLogger('nova.image.fake')
29
30
31FLAGS = flags.FLAGS
32
33
34class FakeImageService(service.BaseImageService):
35 """Mock (fake) image service for unit testing."""
36
37 def __init__(self):
38 self.images = {}
39 # NOTE(justinsb): The OpenStack API can't upload an image?
40 # So, make sure we've got one..
41 timestamp = datetime.datetime(2011, 01, 01, 01, 02, 03)
42 image = {'id': '123456',
43 'name': 'fakeimage123456',
44 'created_at': timestamp,
45 'updated_at': timestamp,
46 'status': 'active',
47 'container_format': 'ami',
48 'disk_format': 'raw',
49 'properties': {'kernel_id': FLAGS.null_kernel,
50 'ramdisk_id': FLAGS.null_kernel}
51 }
52 self.create(None, image)
53 super(FakeImageService, self).__init__()
54
55 def index(self, context):
56 """Returns list of images."""
57 return copy.deepcopy(self.images.values())
58
59 def detail(self, context):
60 """Return list of detailed image information."""
61 return copy.deepcopy(self.images.values())
62
63 def show(self, context, image_id):
64 """Get data about specified image.
65
66 Returns a dict containing image data for the given opaque image id.
67
68 """
69 image_id = int(image_id)
70 image = self.images.get(image_id)
71 if image:
72 return copy.deepcopy(image)
73 LOG.warn("Unable to find image id %s. Have images: %s",
74 image_id, self.images)
75 raise exception.NotFound
76
77 def create(self, context, data):
78 """Store the image data and return the new image id.
79
80 :raises Duplicate if the image already exist.
81
82 """
83 image_id = int(data['id'])
84 if self.images.get(image_id):
85 raise exception.Duplicate()
86
87 self.images[image_id] = copy.deepcopy(data)
88
89 def update(self, context, image_id, data):
90 """Replace the contents of the given image with the new data.
91
92 :raises NotFound if the image does not exist.
93
94 """
95 image_id = int(image_id)
96 if not self.images.get(image_id):
97 raise exception.NotFound
98 self.images[image_id] = copy.deepcopy(data)
99
100 def delete(self, context, image_id):
101 """Delete the given image.
102
103 :raises NotFound if the image does not exist.
104
105 """
106 image_id = int(image_id)
107 removed = self.images.pop(image_id, None)
108 if not removed:
109 raise exception.NotFound
110
111 def delete_all(self):
112 """Clears out all images."""
113 self.images.clear()
0114
=== modified file 'nova/image/glance.py'
--- nova/image/glance.py 2011-03-24 21:50:27 +0000
+++ nova/image/glance.py 2011-04-12 11:01:25 +0000
@@ -151,6 +151,8 @@
151151
152 :raises NotFound if the image does not exist.152 :raises NotFound if the image does not exist.
153 """153 """
154 # NOTE(vish): show is to check if image is available
155 self.show(context, image_id)
154 try:156 try:
155 image_meta = self.client.update_image(image_id, image_meta, data)157 image_meta = self.client.update_image(image_id, image_meta, data)
156 except glance_exception.NotFound:158 except glance_exception.NotFound:
@@ -165,6 +167,8 @@
165167
166 :raises NotFound if the image does not exist.168 :raises NotFound if the image does not exist.
167 """169 """
170 # NOTE(vish): show is to check if image is available
171 self.show(context, image_id)
168 try:172 try:
169 result = self.client.delete_image(image_id)173 result = self.client.delete_image(image_id)
170 except glance_exception.NotFound:174 except glance_exception.NotFound:
@@ -186,33 +190,6 @@
186 image_meta = _convert_timestamps_to_datetimes(image_meta)190 image_meta = _convert_timestamps_to_datetimes(image_meta)
187 return image_meta191 return image_meta
188192
189 @staticmethod
190 def _is_image_available(context, image_meta):
191 """
192 Images are always available if they are public or if the user is an
193 admin.
194
195 Otherwise, we filter by project_id (if present) and then fall-back to
196 images owned by user.
197 """
198 # FIXME(sirp): We should be filtering by user_id on the Glance side
199 # for security; however, we can't do that until we get authn/authz
200 # sorted out. Until then, filtering in Nova.
201 if image_meta['is_public'] or context.is_admin:
202 return True
203
204 properties = image_meta['properties']
205
206 if context.project_id and ('project_id' in properties):
207 return str(properties['project_id']) == str(project_id)
208
209 try:
210 user_id = properties['user_id']
211 except KeyError:
212 return False
213
214 return str(user_id) == str(context.user_id)
215
216193
217# utility functions194# utility functions
218def _convert_timestamps_to_datetimes(image_meta):195def _convert_timestamps_to_datetimes(image_meta):
@@ -220,7 +197,7 @@
220 Returns image with known timestamp fields converted to datetime objects197 Returns image with known timestamp fields converted to datetime objects
221 """198 """
222 for attr in ['created_at', 'updated_at', 'deleted_at']:199 for attr in ['created_at', 'updated_at', 'deleted_at']:
223 if image_meta.get(attr) is not None:200 if image_meta.get(attr):
224 image_meta[attr] = _parse_glance_iso8601_timestamp(201 image_meta[attr] = _parse_glance_iso8601_timestamp(
225 image_meta[attr])202 image_meta[attr])
226 return image_meta203 return image_meta
@@ -230,8 +207,13 @@
230 """207 """
231 Parse a subset of iso8601 timestamps into datetime objects208 Parse a subset of iso8601 timestamps into datetime objects
232 """209 """
233 GLANCE_FMT = "%Y-%m-%dT%H:%M:%S"210 iso_formats = ["%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S"]
234 ISO_FMT = "%Y-%m-%dT%H:%M:%S.%f"211
235 # FIXME(sirp): Glance is not returning in ISO format, we should fix Glance212 for iso_format in iso_formats:
236 # to do so, and then switch to parsing it here213 try:
237 return datetime.datetime.strptime(timestamp, GLANCE_FMT)214 return datetime.datetime.strptime(timestamp, iso_format)
215 except ValueError:
216 pass
217
218 raise ValueError(_("%(timestamp)s does not follow any of the "
219 "signatures: %(ISO_FORMATS)s") % locals())
238220
=== modified file 'nova/image/local.py'
--- nova/image/local.py 2011-03-23 05:50:53 +0000
+++ nova/image/local.py 2011-04-12 11:01:25 +0000
@@ -84,7 +84,10 @@
84 def show(self, context, image_id):84 def show(self, context, image_id):
85 try:85 try:
86 with open(self._path_to(image_id)) as metadata_file:86 with open(self._path_to(image_id)) as metadata_file:
87 return json.load(metadata_file)87 image_meta = json.load(metadata_file)
88 if not self._is_image_available(context, image_meta):
89 raise exception.NotFound
90 return image_meta
88 except (IOError, ValueError):91 except (IOError, ValueError):
89 raise exception.NotFound92 raise exception.NotFound
9093
@@ -119,10 +122,15 @@
119 image_path = self._path_to(image_id, None)122 image_path = self._path_to(image_id, None)
120 if not os.path.exists(image_path):123 if not os.path.exists(image_path):
121 os.mkdir(image_path)124 os.mkdir(image_path)
122 return self.update(context, image_id, metadata, data)125 return self._store(context, image_id, metadata, data)
123126
124 def update(self, context, image_id, metadata, data=None):127 def update(self, context, image_id, metadata, data=None):
125 """Replace the contents of the given image with the new data."""128 """Replace the contents of the given image with the new data."""
129 # NOTE(vish): show is to check if image is available
130 self.show(context, image_id)
131 return self._store(context, image_id, metadata, data)
132
133 def _store(self, context, image_id, metadata, data=None):
126 metadata['id'] = image_id134 metadata['id'] = image_id
127 try:135 try:
128 if data:136 if data:
@@ -140,9 +148,11 @@
140148
141 def delete(self, context, image_id):149 def delete(self, context, image_id):
142 """Delete the given image.150 """Delete the given image.
143 Raises OSError if the image does not exist.151 Raises NotFound if the image does not exist.
144152
145 """153 """
154 # NOTE(vish): show is to check if image is available
155 self.show(context, image_id)
146 try:156 try:
147 shutil.rmtree(self._path_to(image_id, None))157 shutil.rmtree(self._path_to(image_id, None))
148 except (IOError, ValueError):158 except (IOError, ValueError):
149159
=== modified file 'nova/image/s3.py'
--- nova/image/s3.py 2011-03-14 17:59:41 +0000
+++ nova/image/s3.py 2011-04-12 11:01:25 +0000
@@ -31,6 +31,7 @@
3131
32import boto.s3.connection32import boto.s3.connection
3333
34from nova import crypto
34from nova import exception35from nova import exception
35from nova import flags36from nova import flags
36from nova import utils37from nova import utils
@@ -45,6 +46,7 @@
4546
4647
47class S3ImageService(service.BaseImageService):48class S3ImageService(service.BaseImageService):
49 """Wraps an existing image service to support s3 based register"""
48 def __init__(self, service=None, *args, **kwargs):50 def __init__(self, service=None, *args, **kwargs):
49 if service == None:51 if service == None:
50 service = utils.import_object(FLAGS.image_service)52 service = utils.import_object(FLAGS.image_service)
@@ -57,52 +59,23 @@
57 return image59 return image
5860
59 def delete(self, context, image_id):61 def delete(self, context, image_id):
60 # FIXME(vish): call to show is to check filter
61 self.show(context, image_id)
62 self.service.delete(context, image_id)62 self.service.delete(context, image_id)
6363
64 def update(self, context, image_id, metadata, data=None):64 def update(self, context, image_id, metadata, data=None):
65 # FIXME(vish): call to show is to check filter
66 self.show(context, image_id)
67 image = self.service.update(context, image_id, metadata, data)65 image = self.service.update(context, image_id, metadata, data)
68 return image66 return image
6967
70 def index(self, context):68 def index(self, context):
71 images = self.service.index(context)69 return self.service.index(context)
72 # FIXME(vish): index doesn't filter so we do it manually
73 return self._filter(context, images)
7470
75 def detail(self, context):71 def detail(self, context):
76 images = self.service.detail(context)72 return self.service.detail(context)
77 # FIXME(vish): detail doesn't filter so we do it manually
78 return self._filter(context, images)
79
80 @classmethod
81 def _is_visible(cls, context, image):
82 return (context.is_admin
83 or context.project_id == image['properties']['owner_id']
84 or image['properties']['is_public'] == 'True')
85
86 @classmethod
87 def _filter(cls, context, images):
88 filtered = []
89 for image in images:
90 if not cls._is_visible(context, image):
91 continue
92 filtered.append(image)
93 return filtered
9473
95 def show(self, context, image_id):74 def show(self, context, image_id):
96 image = self.service.show(context, image_id)75 return self.service.show(context, image_id)
97 if not self._is_visible(context, image):
98 raise exception.NotFound
99 return image
10076
101 def show_by_name(self, context, name):77 def show_by_name(self, context, name):
102 image = self.service.show_by_name(context, name)78 return self.service.show(context, name)
103 if not self._is_visible(context, image):
104 raise exception.NotFound
105 return image
10679
107 @staticmethod80 @staticmethod
108 def _conn(context):81 def _conn(context):
@@ -166,7 +139,7 @@
166 arch = 'x86_64'139 arch = 'x86_64'
167140
168 properties = metadata['properties']141 properties = metadata['properties']
169 properties['owner_id'] = context.project_id142 properties['project_id'] = context.project_id
170 properties['architecture'] = arch143 properties['architecture'] = arch
171144
172 if kernel_id:145 if kernel_id:
@@ -175,8 +148,6 @@
175 if ramdisk_id:148 if ramdisk_id:
176 properties['ramdisk_id'] = ec2utils.ec2_id_to_id(ramdisk_id)149 properties['ramdisk_id'] = ec2utils.ec2_id_to_id(ramdisk_id)
177150
178 properties['is_public'] = False
179 properties['type'] = image_type
180 metadata.update({'disk_format': image_format,151 metadata.update({'disk_format': image_format,
181 'container_format': image_format,152 'container_format': image_format,
182 'status': 'queued',153 'status': 'queued',
@@ -210,7 +181,7 @@
210181
211 # FIXME(vish): grab key from common service so this can run on182 # FIXME(vish): grab key from common service so this can run on
212 # any host.183 # any host.
213 cloud_pk = os.path.join(FLAGS.ca_path, "private/cakey.pem")184 cloud_pk = crypto.key_path(context.project_id)
214185
215 decrypted_filename = os.path.join(image_path, 'image.tar.gz')186 decrypted_filename = os.path.join(image_path, 'image.tar.gz')
216 self._decrypt_image(encrypted_filename, encrypted_key,187 self._decrypt_image(encrypted_filename, encrypted_key,
217188
=== modified file 'nova/image/service.py'
--- nova/image/service.py 2011-03-24 21:13:55 +0000
+++ nova/image/service.py 2011-04-12 11:01:25 +0000
@@ -136,6 +136,33 @@
136 """136 """
137 raise NotImplementedError137 raise NotImplementedError
138138
139 @staticmethod
140 def _is_image_available(context, image_meta):
141 """
142 Images are always available if they are public or if the user is an
143 admin.
144
145 Otherwise, we filter by project_id (if present) and then fall-back to
146 images owned by user.
147 """
148 # FIXME(sirp): We should be filtering by user_id on the Glance side
149 # for security; however, we can't do that until we get authn/authz
150 # sorted out. Until then, filtering in Nova.
151 if image_meta['is_public'] or context.is_admin:
152 return True
153
154 properties = image_meta['properties']
155
156 if context.project_id and ('project_id' in properties):
157 return str(properties['project_id']) == str(context.project_id)
158
159 try:
160 user_id = properties['user_id']
161 except KeyError:
162 return False
163
164 return str(user_id) == str(context.user_id)
165
139 @classmethod166 @classmethod
140 def _translate_to_base(cls, metadata):167 def _translate_to_base(cls, metadata):
141 """Return a metadata dictionary that is BaseImageService compliant.168 """Return a metadata dictionary that is BaseImageService compliant.
142169
=== modified file 'nova/network/api.py'
--- nova/network/api.py 2011-02-22 23:50:42 +0000
+++ nova/network/api.py 2011-04-12 11:01:25 +0000
@@ -66,6 +66,21 @@
66 if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode):66 if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode):
67 fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip)67 fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip)
68 floating_ip = self.db.floating_ip_get_by_address(context, floating_ip)68 floating_ip = self.db.floating_ip_get_by_address(context, floating_ip)
69 # Check if the floating ip address is allocated
70 if floating_ip['project_id'] is None:
71 raise exception.ApiError(_("Address (%s) is not allocated") %
72 floating_ip['address'])
73 # Check if the floating ip address is allocated to the same project
74 if floating_ip['project_id'] != context.project_id:
75 LOG.warn(_("Address (%(address)s) is not allocated to your "
76 "project (%(project)s)"),
77 {'address': floating_ip['address'],
78 'project': context.project_id})
79 raise exception.ApiError(_("Address (%(address)s) is not "
80 "allocated to your project"
81 "(%(project)s)") %
82 {'address': floating_ip['address'],
83 'project': context.project_id})
69 # NOTE(vish): Perhaps we should just pass this on to compute and84 # NOTE(vish): Perhaps we should just pass this on to compute and
70 # let compute communicate with network.85 # let compute communicate with network.
71 host = fixed_ip['network']['host']86 host = fixed_ip['network']['host']
7287
=== modified file 'nova/network/linux_net.py'
--- nova/network/linux_net.py 2011-04-03 19:04:27 +0000
+++ nova/network/linux_net.py 2011-04-12 11:01:25 +0000
@@ -391,6 +391,12 @@
391 'dev', FLAGS.public_interface)391 'dev', FLAGS.public_interface)
392392
393393
394def ensure_metadata_ip():
395 """Sets up local metadata ip"""
396 _execute('sudo', 'ip', 'addr', 'add', '169.254.169.254/32',
397 'scope', 'link', 'dev', 'lo', check_exit_code=False)
398
399
394def ensure_vlan_forward(public_ip, port, private_ip):400def ensure_vlan_forward(public_ip, port, private_ip):
395 """Sets up forwarding rules for vlan"""401 """Sets up forwarding rules for vlan"""
396 iptables_manager.ipv4['filter'].add_rule("FORWARD",402 iptables_manager.ipv4['filter'].add_rule("FORWARD",
@@ -442,6 +448,7 @@
442 return interface448 return interface
443449
444450
451@utils.synchronized('ensure_bridge', external=True)
445def ensure_bridge(bridge, interface, net_attrs=None):452def ensure_bridge(bridge, interface, net_attrs=None):
446 """Create a bridge unless it already exists.453 """Create a bridge unless it already exists.
447454
@@ -495,6 +502,8 @@
495 fields = line.split()502 fields = line.split()
496 if fields and fields[0] == "0.0.0.0" and fields[-1] == interface:503 if fields and fields[0] == "0.0.0.0" and fields[-1] == interface:
497 gateway = fields[1]504 gateway = fields[1]
505 _execute('sudo', 'route', 'del', 'default', 'gw', gateway,
506 'dev', interface, check_exit_code=False)
498 out, err = _execute('sudo', 'ip', 'addr', 'show', 'dev', interface,507 out, err = _execute('sudo', 'ip', 'addr', 'show', 'dev', interface,
499 'scope', 'global')508 'scope', 'global')
500 for line in out.split("\n"):509 for line in out.split("\n"):
@@ -504,7 +513,7 @@
504 _execute(*_ip_bridge_cmd('del', params, fields[-1]))513 _execute(*_ip_bridge_cmd('del', params, fields[-1]))
505 _execute(*_ip_bridge_cmd('add', params, bridge))514 _execute(*_ip_bridge_cmd('add', params, bridge))
506 if gateway:515 if gateway:
507 _execute('sudo', 'route', 'add', '0.0.0.0', 'gw', gateway)516 _execute('sudo', 'route', 'add', 'default', 'gw', gateway)
508 out, err = _execute('sudo', 'brctl', 'addif', bridge, interface,517 out, err = _execute('sudo', 'brctl', 'addif', bridge, interface,
509 check_exit_code=False)518 check_exit_code=False)
510519
511520
=== modified file 'nova/network/manager.py'
--- nova/network/manager.py 2011-04-03 19:04:27 +0000
+++ nova/network/manager.py 2011-04-12 11:01:25 +0000
@@ -126,6 +126,7 @@
126 standalone service.126 standalone service.
127 """127 """
128 self.driver.init_host()128 self.driver.init_host()
129 self.driver.ensure_metadata_ip()
129 # Set up networking for the projects for which we're already130 # Set up networking for the projects for which we're already
130 # the designated network host.131 # the designated network host.
131 ctxt = context.get_admin_context()132 ctxt = context.get_admin_context()
132133
=== added file 'nova/network/xenapi_net.py'
--- nova/network/xenapi_net.py 1970-01-01 00:00:00 +0000
+++ nova/network/xenapi_net.py 2011-04-12 11:01:25 +0000
@@ -0,0 +1,85 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright (c) 2011 Citrix Systems, Inc.
4# Copyright 2011 OpenStack LLC.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18"""
19Implements vlans, bridges, and iptables rules using linux utilities.
20"""
21
22import os
23
24from nova import db
25from nova import exception
26from nova import flags
27from nova import log as logging
28from nova import utils
29from nova.virt.xenapi_conn import XenAPISession
30from nova.virt.xenapi import network_utils
31
32LOG = logging.getLogger("nova.xenapi_net")
33
34FLAGS = flags.FLAGS
35
36
37def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None):
38 """Create a vlan and bridge unless they already exist."""
39 # Open xenapi session
40 LOG.debug("ENTERING ensure_vlan_bridge in xenapi net")
41 url = FLAGS.xenapi_connection_url
42 username = FLAGS.xenapi_connection_username
43 password = FLAGS.xenapi_connection_password
44 session = XenAPISession(url, username, password)
45 # Check whether bridge already exists
46 # Retrieve network whose name_label is "bridge"
47 network_ref = network_utils.NetworkHelper.find_network_with_name_label(
48 session,
49 bridge)
50 if network_ref == None:
51 # If bridge does not exists
52 # 1 - create network
53 description = "network for nova bridge %s" % bridge
54 network_rec = {'name_label': bridge,
55 'name_description': description,
56 'other_config': {}}
57 network_ref = session.call_xenapi('network.create', network_rec)
58 # 2 - find PIF for VLAN
59 expr = 'field "device" = "%s" and \
60 field "VLAN" = "-1"' % FLAGS.vlan_interface
61 pifs = session.call_xenapi('PIF.get_all_records_where', expr)
62 pif_ref = None
63 # Multiple PIF are ok: we are dealing with a pool
64 if len(pifs) == 0:
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to status/vote changes: