diff options
| author | Alex Meade <alex.meade@rackspace.com> | 2011-07-26 09:47:39 -0400 |
|---|---|---|
| committer | Alex Meade <alex.meade@rackspace.com> | 2011-07-26 09:47:39 -0400 |
| commit | 881344568a076270e4932bc2ba8a5f6df12c27c3 (patch) | |
| tree | 41165d5d4d46efd11cfacbba5a1a60498e56e79e | |
| parent | ff5aa1a167459b922cc8195c1e6e1368a442610d (diff) | |
| parent | 986da4b2989bbd56db29117a0e8ad6a92643180c (diff) | |
| download | nova-881344568a076270e4932bc2ba8a5f6df12c27c3.tar.gz nova-881344568a076270e4932bc2ba8a5f6df12c27c3.tar.xz nova-881344568a076270e4932bc2ba8a5f6df12c27c3.zip | |
merged trunk
50 files changed, 1758 insertions, 669 deletions
diff --git a/.bzrignore b/.bzrignore index 14d8028f7..91277d100 100644 --- a/.bzrignore +++ b/.bzrignore @@ -13,3 +13,7 @@ nova/vcsversion.py clean.sqlite run_tests.log tests.sqlite +nova/tests/instance-* +tags +.coverage +covhtml @@ -14,6 +14,7 @@ <code@term.ie> <github@anarkystic.com> <code@term.ie> <termie@preciousroy.local> <corywright@gmail.com> <cory.wright@rackspace.com> +<dan@nicira.com> <danwent@dan-xs3-cs> <devin.carlen@gmail.com> <devcamcar@illian.local> <ewan.mellor@citrix.com> <emellor@silver> <itoumsn@nttdata.co.jp> <itoumsn@shayol> @@ -1,4 +1,5 @@ Adam Gandelman <adamg@canonical.com> +Adam Johnson <adjohn@gmail.com> Alex Meade <alex.meade@rackspace.com> Alexander Sakhnov <asakhnov@mirantis.com> Andrey Brindeyev <abrindeyev@griddynamics.com> @@ -8,6 +9,7 @@ Anne Gentle <anne@openstack.org> Anthony Young <sleepsonthefloor@gmail.com> Antony Messerli <ant@openstack.org> Armando Migliaccio <Armando.Migliaccio@eu.citrix.com> +Arvind Somya <asomya@cisco.com> Bilal Akhtar <bilalakhtar@ubuntu.com> Brian Lamar <brian.lamar@rackspace.com> Brian Schott <bschott@isi.edu> @@ -19,6 +21,7 @@ Christian Berendt <berendt@b1-systems.de> Chuck Short <zulcss@ubuntu.com> Cory Wright <corywright@gmail.com> Dan Prince <dan.prince@rackspace.com> +Dan Wendlandt <dan@nicira.com> Dave Walker <DaveWalker@ubuntu.com> David Pravec <David.Pravec@danix.org> Dean Troyer <dtroyer@gmail.com> @@ -83,6 +86,7 @@ Rick Harris <rconradharris@gmail.com> Rob Kost <kost@isi.edu> Ryan Lane <rlane@wikimedia.org> Ryan Lucio <rlucio@internap.com> +Ryu Ishimoto <ryu@midokura.jp> Salvatore Orlando <salvatore.orlando@eu.citrix.com> Sandy Walsh <sandy.walsh@rackspace.com> Sateesh Chodapuneedi <sateesh.chodapuneedi@citrix.com> diff --git a/bin/nova-manage b/bin/nova-manage index 7477c213d..b63bd326f 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -61,6 +61,7 @@ import os import sys import time +from optparse import OptionParser # If ../nova/__init__.py exists, add ../ to Python search path, so that # it will override what happens to be installed in /usr/(local/)lib/python... @@ -103,6 +104,14 @@ flags.DEFINE_flag(flags.HelpshortFlag()) flags.DEFINE_flag(flags.HelpXMLFlag()) +# Decorators for actions +def args(*args, **kwargs): + def _decorator(func): + func.__dict__.setdefault('options', []).insert(0, (args, kwargs)) + return func + return _decorator + + def param2id(object_id): """Helper function to convert various id types to internal id. args: [object_id], e.g. 'vol-0000000a' or 'volume-0000000a' or '10' @@ -120,10 +129,11 @@ class VpnCommands(object): self.manager = manager.AuthManager() self.pipe = pipelib.CloudPipe() + @args('--project', dest="project", metavar='<Project name>', + help='Project name') def list(self, project=None): - """Print a listing of the VPN data for one or all projects. + """Print a listing of the VPN data for one or all projects.""" - args: [project=all]""" print "%-12s\t" % 'project', print "%-20s\t" % 'ip:port', print "%-20s\t" % 'private_ip', @@ -165,17 +175,23 @@ class VpnCommands(object): self.pipe.launch_vpn_instance(p.id) time.sleep(10) + @args('--project', dest="project_id", metavar='<Project name>', + help='Project name') def run(self, project_id): """Start the VPN for a given project.""" self.pipe.launch_vpn_instance(project_id) + @args('--project', dest="project_id", metavar='<Project name>', + help='Project name') + @args('--ip', dest="ip", metavar='<IP Address>', help='IP Address') + @args('--port', dest="port", metavar='<Port>', help='Port') def change(self, project_id, ip, port): """Change the ip and port for a vpn. this will update all networks associated with a project not sure if that's the desired behavior or not, patches accepted - args: project, ip, port""" + """ # TODO(tr3buchet): perhaps this shouldn't update all networks # associated with a project in the future project = self.manager.get_project(project_id) @@ -210,10 +226,10 @@ class ShellCommands(object): Falls back to Python shell if unavailable""" self.run('python') + @args('--shell', dest="shell", metavar='<bpython|ipython|python >', + help='Python shell') def run(self, shell=None): - """Runs a Python interactive interpreter. - - args: [shell=bpython]""" + """Runs a Python interactive interpreter.""" if not shell: shell = 'bpython' @@ -247,6 +263,7 @@ class ShellCommands(object): readline.parse_and_bind("tab:complete") code.interact() + @args('--path', dest='path', metavar='<path>', help='Script path') def script(self, path): """Runs the script from the specifed path with flags set properly. arguments: path""" @@ -259,10 +276,13 @@ class RoleCommands(object): def __init__(self): self.manager = manager.AuthManager() + @args('--user', dest="user", metavar='<user name>', help='User name') + @args('--role', dest="role", metavar='<user role>', help='User role') + @args('--project', dest="project", metavar='<Project name>', + help='Project name') def add(self, user, role, project=None): """adds role to user - if project is specified, adds project specific role - arguments: user, role [project]""" + if project is specified, adds project specific role""" if project: projobj = self.manager.get_project(project) if not projobj.has_member(user): @@ -270,17 +290,23 @@ class RoleCommands(object): return self.manager.add_role(user, role, project) + @args('--user', dest="user", metavar='<user name>', help='User name') + @args('--role', dest="role", metavar='<user role>', help='User role') + @args('--project', dest="project", metavar='<Project name>', + help='Project name') def has(self, user, role, project=None): """checks to see if user has role if project is specified, returns True if user has - the global role and the project role - arguments: user, role [project]""" + the global role and the project role""" print self.manager.has_role(user, role, project) + @args('--user', dest="user", metavar='<user name>', help='User name') + @args('--role', dest="role", metavar='<user role>', help='User role') + @args('--project', dest="project", metavar='<Project name>', + help='Project name') def remove(self, user, role, project=None): """removes role from user - if project is specified, removes project specific role - arguments: user, role [project]""" + if project is specified, removes project specific role""" self.manager.remove_role(user, role, project) @@ -304,32 +330,37 @@ class UserCommands(object): def __init__(self): self.manager = manager.AuthManager() + @args('--name', dest="name", metavar='<admin name>', help='Admin name') + @args('--access', dest="access", metavar='<access>', help='Access') + @args('--secret', dest="secret", metavar='<secret>', help='Secret') def admin(self, name, access=None, secret=None): - """creates a new admin and prints exports - arguments: name [access] [secret]""" + """creates a new admin and prints exports""" try: user = self.manager.create_user(name, access, secret, True) except exception.DBError, e: _db_error(e) self._print_export(user) + @args('--name', dest="name", metavar='<name>', help='User name') + @args('--access', dest="access", metavar='<access>', help='Access') + @args('--secret', dest="secret", metavar='<secret>', help='Secret') def create(self, name, access=None, secret=None): - """creates a new user and prints exports - arguments: name [access] [secret]""" + """creates a new user and prints exports""" try: user = self.manager.create_user(name, access, secret, False) except exception.DBError, e: _db_error(e) self._print_export(user) + @args('--name', dest="name", metavar='<name>', help='User name') def delete(self, name): """deletes an existing user arguments: name""" self.manager.delete_user(name) + @args('--name', dest="name", metavar='<admin name>', help='User name') def exports(self, name): - """prints access and secrets for user in export format - arguments: name""" + """prints access and secrets for user in export format""" user = self.manager.get_user(name) if user: self._print_export(user) @@ -337,11 +368,17 @@ class UserCommands(object): print "User %s doesn't exist" % name def list(self): - """lists all users - arguments: <none>""" + """lists all users""" for user in self.manager.get_users(): print user.name + @args('--name', dest="name", metavar='<name>', help='User name') + @args('--access', dest="access_key", metavar='<access>', + help='Access key') + @args('--secret', dest="secret_key", metavar='<secret>', + help='Secret key') + @args('--is_admin', dest='is_admin', metavar="<'T'|'F'>", + help='Is admin?') def modify(self, name, access_key, secret_key, is_admin): """update a users keys & admin flag arguments: accesskey secretkey admin @@ -355,9 +392,11 @@ class UserCommands(object): is_admin = False self.manager.modify_user(name, access_key, secret_key, is_admin) + @args('--name', dest="user_id", metavar='<name>', help='User name') + @args('--project', dest="project_id", metavar='<Project name>', + help='Project name') def revoke(self, user_id, project_id=None): - """revoke certs for a user - arguments: user_id [project_id]""" + """revoke certs for a user""" if project_id: crypto.revoke_certs_by_user_and_project(user_id, project_id) else: @@ -370,45 +409,62 @@ class ProjectCommands(object): def __init__(self): self.manager = manager.AuthManager() + @args('--project', dest="project_id", metavar='<Project name>', + help='Project name') + @args('--user', dest="user_id", metavar='<name>', help='User name') def add(self, project_id, user_id): - """Adds user to project - arguments: project_id user_id""" + """Adds user to project""" try: self.manager.add_to_project(user_id, project_id) except exception.UserNotFound as ex: print ex raise + @args('--project', dest="name", metavar='<Project name>', + help='Project name') + @args('--user', dest="project_manager", metavar='<user>', + help='Project manager') + @args('--desc', dest="description", metavar='<description>', + help='Description') def create(self, name, project_manager, description=None): - """Creates a new project - arguments: name project_manager [description]""" + """Creates a new project""" try: self.manager.create_project(name, project_manager, description) except exception.UserNotFound as ex: print ex raise + @args('--project', dest="name", metavar='<Project name>', + help='Project name') + @args('--user', dest="project_manager", metavar='<user>', + help='Project manager') + @args('--desc', dest="description", metavar='<description>', + help='Description') def modify(self, name, project_manager, description=None): - """Modifies a project - arguments: name project_manager [description]""" + """Modifies a project""" try: self.manager.modify_project(name, project_manager, description) except exception.UserNotFound as ex: print ex raise + @args('--project', dest="name", metavar='<Project name>', + help='Project name') def delete(self, name): - """Deletes an existing project - arguments: name""" + """Deletes an existing project""" try: self.manager.delete_project(name) except exception.ProjectNotFound as ex: print ex raise + @args('--project', dest="project_id", metavar='<Project name>', + help='Project name') + @args('--user', dest="user_id", metavar='<name>', help='User name') + @args('--file', dest="filename", metavar='<filename>', + help='File name(Default: novarc)') def environment(self, project_id, user_id, filename='novarc'): - """Exports environment variables to an sourcable file - arguments: project_id user_id [filename='novarc]""" + """Exports environment variables to an sourcable file""" try: rc = self.manager.get_environment_rc(user_id, project_id) except (exception.UserNotFound, exception.ProjectNotFound) as ex: @@ -420,15 +476,18 @@ class ProjectCommands(object): with open(filename, 'w') as f: f.write(rc) + @args('--user', dest="username", metavar='<username>', help='User name') def list(self, username=None): - """Lists all projects - arguments: [username]""" + """Lists all projects""" for project in self.manager.get_projects(username): print project.name + @args('--project', dest="project_id", metavar='<Project name>', + help='Project name') + @args('--key', dest="key", metavar='<key>', help='Key') + @args('--value', dest="value", metavar='<value>', help='Value') def quota(self, project_id, key=None, value=None): - """Set or display quotas for project - arguments: project_id [key] [value]""" + """Set or display quotas for project""" ctxt = context.get_admin_context() if key: if value.lower() == 'unlimited': @@ -443,18 +502,21 @@ class ProjectCommands(object): value = 'unlimited' print '%s: %s' % (key, value) + @args('--project', dest="project_id", metavar='<Project name>', + help='Project name') + @args('--user', dest="user_id", metavar='<name>', help='User name') def remove(self, project_id, user_id): - """Removes user from project - arguments: project_id user_id""" + """Removes user from project""" try: self.manager.remove_from_project(user_id, project_id) except (exception.UserNotFound, exception.ProjectNotFound) as ex: print ex raise + @args('--project', dest="project_id", metavar='<Project name>', + help='Project name') def scrub(self, project_id): - """Deletes data associated with project - arguments: project_id""" + """Deletes data associated with project""" admin_context = context.get_admin_context() networks = db.project_get_networks(admin_context, project_id) for network in networks: @@ -463,9 +525,13 @@ class ProjectCommands(object): for group in groups: db.security_group_destroy(admin_context, group['id']) + @args('--project', dest="project_id", metavar='<Project name>', + help='Project name') + @args('--user', dest="user_id", metavar='<name>', help='User name') + @args('--file', dest="filename", metavar='<filename>', + help='File name(Default: nova.zip)') def zipfile(self, project_id, user_id, filename='nova.zip'): - """Exports credentials for project to a zip file - arguments: project_id user_id [filename='nova.zip]""" + """Exports credentials for project to a zip file""" try: zip_file = self.manager.get_credentials(user_id, project_id) if filename == "-": @@ -482,9 +548,9 @@ class ProjectCommands(object): ' nova-manage network create pvt 10.0.0.0/8 10 64\n\n') except exception.ProcessExecutionError, e: print e - print _("The above error may show that the certificate db has not " - "been created.\nPlease create a database by running a " - "nova-api server on this host.") + print _("The above error may show that the certificate db has " + "not been created.\nPlease create a database by running " + "a nova-api server on this host.") AccountCommands = ProjectCommands @@ -492,8 +558,9 @@ AccountCommands = ProjectCommands class FixedIpCommands(object): """Class for managing fixed ip.""" + @args('--host', dest="host", metavar='<host>', help='Host') def list(self, host=None): - """Lists all fixed ips (optionally by host) arguments: [host]""" + """Lists all fixed ips (optionally by host)""" ctxt = context.get_admin_context() try: @@ -528,23 +595,23 @@ class FixedIpCommands(object): class FloatingIpCommands(object): """Class for managing floating ip.""" + @args('--ip_range', dest="range", metavar='<range>', help='IP range') def create(self, range): - """Creates floating ips for zone by range - arguments: ip_range""" + """Creates floating ips for zone by range""" for address in netaddr.IPNetwork(range): db.floating_ip_create(context.get_admin_context(), {'address': str(address)}) + @args('--ip_range', dest="ip_range", metavar='<range>', help='IP range') def delete(self, ip_range): - """Deletes floating ips by range - arguments: range""" + """Deletes floating ips by range""" for address in netaddr.IPNetwork(ip_range): db.floating_ip_destroy(context.get_admin_context(), str(address)) + @args('--host', dest="host", metavar='<host>', help='Host') def list(self, host=None): """Lists all floating ips (optionally by host) - arguments: [host] Note: if host is given, only active floating IPs are returned""" ctxt = context.get_admin_context() if host is None: @@ -563,21 +630,32 @@ class FloatingIpCommands(object): class NetworkCommands(object): """Class for managing networks.""" + @args('--label', dest="label", metavar='<label>', + help='Label(ex: public)') + @args('--network', dest="fixed_range", metavar='<x.x.x.x/yy>', + help='Network') + @args('--num_networks', dest="num_networks", metavar='<number>', + help='How many networks create') + @args('--network_size', dest="network_size", metavar='<number>', + help='How many hosts in network') + @args('--vlan', dest="vlan_start", metavar='<vlan id>', help='vlan id') + @args('--vpn', dest="vpn_start", help='vpn start') + @args('--fixed_range_v6', dest="fixed_range_v6", help='fixed ipv6 range') + @args('--gateway_v6', dest="gateway_v6", help='ipv6 gateway') + @args('--flat_network_bridge', dest="flat_network_bridge", + metavar='<flat network bridge>', help='Flat_network_bridge') + @args('--bridge_interface', dest="bridge_interface", + metavar='<bridge interface>', help='Bridge_interface') + @args('--multi_host', dest="multi_host", metavar="<'T'|'F'>", + help='Multi host') + @args('--dns1', dest="dns1", metavar="<DNS Address>", help='First DNS') + @args('--dns2', dest="dns2", metavar="<DNS Address>", help='Second DNS') def create(self, label=None, fixed_range=None, num_networks=None, network_size=None, multi_host=None, vlan_start=None, vpn_start=None, fixed_range_v6=None, gateway_v6=None, flat_network_bridge=None, bridge_interface=None, dns1=None, dns2=None): - """Creates fixed ips for host by range - arguments: label, fixed_range, [num_networks=FLAG], - [network_size=FLAG], [multi_host=FLAG], [vlan_start=FLAG], - [vpn_start=FLAG], [fixed_range_v6=FLAG], [gateway_v6=FLAG], - [flat_network_bridge=FLAG], [bridge_interface=FLAG] - [dns1=FLAG], [dns2] - If you wish to use a later argument fill in the gaps with ""s - Ex: network create private 10.0.0.0/8 1 16 T "" "" "" "" xenbr1 eth1 - network create private 10.0.0.0/8 1 16 - """ + """Creates fixed ips for host by range""" if not label: msg = _('a label (ex: public) is required to create networks.') print msg @@ -650,6 +728,8 @@ class NetworkCommands(object): network.vlan, network.project_id) + @args('--network', dest="fixed_range", metavar='<x.x.x.x/yy>', + help='Network to delete') def delete(self, fixed_range): """Deletes a network""" network = db.network_get_by_cidr(context.get_admin_context(), \ @@ -663,12 +743,10 @@ class NetworkCommands(object): class VmCommands(object): """Class for mangaging VM instances.""" + @args('--host', dest="host", metavar='<host>', help='Host') def list(self, host=None): - """Show a list of all instances + """Show a list of all instances""" - :param host: show all instance on specified host. - :param instance: show specificed instance. - """ print "%-10s %-15s %-10s %-10s %-26s %-9s %-9s %-9s" \ " %-10s %-10s %-10s %-5s" % ( _('instance'), @@ -706,13 +784,11 @@ class VmCommands(object): instance['availability_zone'], instance['launch_index']) + @args('--ec2_id', dest='ec2_id', metavar='<ec2 id>', help='EC2 ID') + @args('--dest', dest='dest', metavar='<Destanation>', + help='destanation node') def live_migration(self, ec2_id, dest): - """Migrates a running instance to a new machine. - - :param ec2_id: instance id which comes from euca-describe-instance. - :param dest: destination host name. - - """ + """Migrates a running instance to a new machine.""" ctxt = context.get_admin_context() instance_id = ec2utils.ec2_id_to_id(ec2_id) @@ -742,9 +818,13 @@ class VmCommands(object): class ServiceCommands(object): """Enable and disable running services""" + @args('--host', dest='host', metavar='<host>', help='Host') + @args('--service', dest='service', metavar='<service>', + help='Nova service') def list(self, host=None, service=None): - """Show a list of all running services. Filter by host & service name. - args: [host] [service]""" + """ + Show a list of all running services. Filter by host & service name. + """ ctxt = context.get_admin_context() now = utils.utcnow() services = db.service_get_all(ctxt) @@ -763,9 +843,11 @@ class ServiceCommands(object): active, art, svc['updated_at']) + @args('--host', dest='host', metavar='<host>', help='Host') + @args('--service', dest='service', metavar='<service>', + help='Nova service') def enable(self, host, service): - """Enable scheduling for a service - args: host service""" + """Enable scheduling for a service""" ctxt = context.get_admin_context() svc = db.service_get_by_args(ctxt, host, service) if not svc: @@ -773,9 +855,11 @@ class ServiceCommands(object): return db.service_update(ctxt, svc['id'], {'disabled': False}) + @args('--host', dest='host', metavar='<host>', help='Host') + @args('--service', dest='service', metavar='<service>', + help='Nova service') def disable(self, host, service): - """Disable scheduling for a service - args: host service""" + """Disable scheduling for a service""" ctxt = context.get_admin_context() svc = db.service_get_by_args(ctxt, host, service) if not svc: @@ -783,12 +867,9 @@ class ServiceCommands(object): return db.service_update(ctxt, svc['id'], {'disabled': True}) + @args('--host', dest='host', metavar='<host>', help='Host') def describe_resource(self, host): - """Describes cpu/memory/hdd info for host. - - :param host: hostname. - - """ + """Describes cpu/memory/hdd info for host.""" result = rpc.call(context.get_admin_context(), FLAGS.scheduler_topic, @@ -816,12 +897,9 @@ class ServiceCommands(object): val['memory_mb'], val['local_gb']) + @args('--host', dest='host', metavar='<host>', help='Host') def update_resource(self, host): - """Updates available vcpu/memory/disk info for host. - - :param host: hostname. - - """ + """Updates available vcpu/memory/disk info for host.""" ctxt = context.get_admin_context() service_refs = db.service_get_all_by_host(ctxt, host) @@ -865,6 +943,8 @@ class DbCommands(object): def __init__(self): pass + @args('--version', dest='version', metavar='<version>', + help='Database version') def sync(self, version=None): """Sync the database up to the most recent version.""" return migration.db_sync(version) @@ -884,14 +964,18 @@ class VersionCommands(object): print _("%s (%s)") %\ (version.version_string(), version.version_string_with_vcs()) + def __call__(self): + self.list() + class VolumeCommands(object): """Methods for dealing with a cloud in an odd state""" + @args('--volume', dest='volume_id', metavar='<volume id>', + help='Volume ID') def delete(self, volume_id): """Delete a volume, bypassing the check that it - must be available. - args: volume_id_id""" + must be available.""" ctxt = context.get_admin_context() volume = db.volume_get(ctxt, param2id(volume_id)) host = volume['host'] @@ -912,11 +996,12 @@ class VolumeCommands(object): {"method": "delete_volume", "args": {"volume_id": volume['id']}}) + @args('--volume', dest='volume_id', metavar='<volume id>', + help='Volume ID') def reattach(self, volume_id): """Re-attach a volume that has previously been attached to an instance. Typically called after a compute host - has been rebooted. - args: volume_id_id""" + has been rebooted.""" ctxt = context.get_admin_context() volume = db.volume_get(ctxt, param2id(volume_id)) if not volume['instance_id']: @@ -943,12 +1028,23 @@ class InstanceTypeCommands(object): val["flavorid"], val["swap"], val["rxtx_quota"], val["rxtx_cap"], deleted) + @args('--name', dest='name', metavar='<name>', + help='Name of instance type/flavor') + @args('--memory', dest='memory', metavar='<memory size>', + help='Memory size') + @args('--cpu', dest='vcpus', metavar='<num cores>', help='Number cpus') + @args('--local_gb', dest='local_gb', metavar='<local_gb>', + help='local_gb') + @args('--flavor', dest='flavorid', metavar='<flavor id>', + help='Flavor ID') + @args('--swap', dest='swap', metavar='<swap>', help='Swap') + @args('--rxtx_quota', dest='rxtx_quota', metavar='<rxtx_quota>', + help='rxtx_quota') + @args('--rxtx_cap', dest='rxtx_cap', metavar='<rxtx_cap>', + help='rxtx_cap') def create(self, name, memory, vcpus, local_gb, flavorid, swap=0, rxtx_quota=0, rxtx_cap=0): - """Creates instance types / flavors - arguments: name memory vcpus local_gb flavorid [swap] [rxtx_quota] - [rxtx_cap] - """ + """Creates instance types / flavors""" try: instance_types.create(name, memory, vcpus, local_gb, flavorid, swap, rxtx_quota, rxtx_cap) @@ -971,9 +1067,10 @@ class InstanceTypeCommands(object): else: print "%s created" % name + @args('--name', dest='name', metavar='<name>', + help='Name of instance type/flavor') def delete(self, name, purge=None): - """Marks instance types / flavors as deleted - arguments: name""" + """Marks instance types / flavors as deleted""" try: if purge == "--purge": instance_types.purge(name) @@ -992,9 +1089,10 @@ class InstanceTypeCommands(object): else: print "%s %s" % (name, verb) + @args('--name', dest='name', metavar='<name>', + help='Name of instance type/flavor') def list(self, name=None): - """Lists all active or specific instance types / flavors - arguments: [name]""" + """Lists all active or specific instance types / flavors""" try: if name is None: inst_types = instance_types.get_all_types() @@ -1042,11 +1140,18 @@ class ImageCommands(object): except Exception as exc: print _("Failed to register %(path)s: %(exc)s") % locals() + @args('--image', dest='image', metavar='<image>', help='Image') + @args('--kernel', dest='kernel', metavar='<kernel>', help='Kernel') + @args('--ram', dest='ramdisk', metavar='<ramdisk>', help='RAM disk') + @args('--owner', dest='owner', metavar='<owner>', help='Image owner') + @args('--name', dest='name', metavar='<name>', help='Image name') + @args('--public', dest='is_public', metavar="<'T'|'F'>", + help='Image public or not') + @args('--arch', dest='architecture', metavar='<arch>', + help='Architecture') def all_register(self, image, kernel, ramdisk, owner, name=None, is_public='T', architecture='x86_64'): - """Uploads an image, kernel, and ramdisk into the image_service - arguments: image kernel ramdisk owner [name] [is_public='T'] - [architecture='x86_64']""" + """Uploads an image, kernel, and ramdisk into the image_service""" kernel_id = self.kernel_register(kernel, owner, None, is_public, architecture) ramdisk_id = self.ramdisk_register(ramdisk, owner, None, @@ -1055,31 +1160,51 @@ class ImageCommands(object): architecture, 'ami', 'ami', kernel_id, ramdisk_id) + @args('--path', dest='path', metavar='<path>', help='Image path') + @args('--owner', dest='owner', metavar='<owner>', help='Image owner') + @args('--name', dest='name', metavar='<name>', help='Image name') + @args('--public', dest='is_public', metavar="<'T'|'F'>", + help='Image public or not') + @args('--arch', dest='architecture', metavar='<arch>', + help='Architecture') + @args('--cont_format', dest='container_format', + metavar='<container format>', + help='Container format(default bare)') + @args('--disk_format', dest='disk_format', metavar='<disk format>', + help='Disk format(default: raw)') + @args('--kernel', dest='kernel_id', metavar='<kernel>', help='Kernel') + @args('--ram', dest='ramdisk_id', metavar='<ramdisk>', help='RAM disk') def image_register(self, path, owner, name=None, is_public='T', architecture='x86_64', container_format='bare', disk_format='raw', kernel_id=None, ramdisk_id=None): - """Uploads an image into the image_service - arguments: path owner [name] [is_public='T'] [architecture='x86_64'] - [container_format='bare'] [disk_format='raw'] - [kernel_id=None] [ramdisk_id=None] - """ + """Uploads an image into the image_service""" return self._register(container_format, disk_format, path, owner, name, is_public, architecture, kernel_id, ramdisk_id) + @args('--path', dest='path', metavar='<path>', help='Image path') + @args('--owner', dest='owner', metavar='<owner>', help='Image owner') + @args('--name', dest='name', metavar='<name>', help='Image name') + @args('--public', dest='is_public', metavar="<'T'|'F'>", + help='Image public or not') + @args('--arch', dest='architecture', metavar='<arch>', + help='Architecture') def kernel_register(self, path, owner, name=None, is_public='T', architecture='x86_64'): - """Uploads a kernel into the image_service - arguments: path owner [name] [is_public='T'] [architecture='x86_64'] - """ + """Uploads a kernel into the image_service""" return self._register('aki', 'aki', path, owner, name, is_public, architecture) + @args('--path', dest='path', metavar='<path>', help='Image path') + @args('--owner', dest='owner', metavar='<owner>', help='Image owner') + @args('--name', dest='name', metavar='<name>', help='Image name') + @args('--public', dest='is_public', metavar="<'T'|'F'>", + help='Image public or not') + @args('--arch', dest='architecture', metavar='<arch>', + help='Architecture') def ramdisk_register(self, path, owner, name=None, is_public='T', architecture='x86_64'): - """Uploads a ramdisk into the image_service - arguments: path owner [name] [is_public='T'] [architecture='x86_64'] - """ + """Uploads a ramdisk into the image_service""" return self._register('ari', 'ari', path, owner, name, is_public, architecture) @@ -1128,9 +1253,10 @@ class ImageCommands(object): except Exception as exc: print _("Failed to convert %(old)s: %(exc)s") % locals() + @args('--dir', dest='directory', metavar='<path>', + help='Images directory') def convert(self, directory): - """Uploads old objectstore images in directory to new service - arguments: directory""" + """Uploads old objectstore images in directory to new service""" machine_images = {} other_images = {} directory = os.path.abspath(directory) @@ -1155,8 +1281,7 @@ class AgentBuildCommands(object): def create(self, os, architecture, version, url, md5hash, hypervisor='xen'): - """Creates a new agent build. - arguments: os architecture version url md5hash [hypervisor='xen']""" + """Creates a new agent build.""" ctxt = context.get_admin_context() agent_build = db.agent_build_create(ctxt, {'hypervisor': hypervisor, @@ -1167,8 +1292,7 @@ class AgentBuildCommands(object): 'md5hash': md5hash}) def delete(self, os, architecture, hypervisor='xen'): - """Deletes an existing agent build. - arguments: os architecture [hypervisor='xen']""" + """Deletes an existing agent build.""" ctxt = context.get_admin_context() agent_build_ref = db.agent_build_get_by_triple(ctxt, hypervisor, os, architecture) @@ -1202,9 +1326,7 @@ class AgentBuildCommands(object): def modify(self, os, architecture, version, url, md5hash, hypervisor='xen'): - """Update an existing agent build. - arguments: os architecture version url md5hash [hypervisor='xen'] - """ + """Update an existing agent build.""" ctxt = context.get_admin_context() agent_build_ref = db.agent_build_get_by_triple(ctxt, hypervisor, os, architecture) @@ -1300,21 +1422,42 @@ def main(): command_object = fn() actions = methods_of(command_object) if len(argv) < 1: - print script_name + " category action [<args>]" - print _("Available actions for %s category:") % category - for k, _v in actions: - print "\t%s" % k - sys.exit(2) - action = argv.pop(0) - matches = lazy_match(action, actions) - action, fn = matches[0] + if hasattr(command_object, '__call__'): + action = '' + fn = command_object.__call__ + else: + print script_name + " category action [<args>]" + print _("Available actions for %s category:") % category + for k, _v in actions: + print "\t%s" % k + sys.exit(2) + else: + action = argv.pop(0) + matches = lazy_match(action, actions) + action, fn = matches[0] + + # For not decorated methods + options = getattr(fn, 'options', []) + + usage = "%%prog %s %s <args> [options]" % (category, action) + parser = OptionParser(usage=usage) + for ar, kw in options: + parser.add_option(*ar, **kw) + (opts, fn_args) = parser.parse_args(argv) + fn_kwargs = vars(opts) + + for k, v in fn_kwargs.items(): + if v is None: + del fn_kwargs[k] + # call the action with the remaining arguments try: - fn(*argv) + fn(*fn_args, **fn_kwargs) sys.exit(0) except TypeError: print _("Possible wrong number of arguments supplied") - print "%s %s: %s" % (category, action, fn.__doc__) + print fn.__doc__ + parser.print_help() raise except Exception: print _("Command failed, please check log for more info") diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 16ca1ed2a..10720a804 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -530,7 +530,52 @@ class CloudController(object): g['ipPermissions'] += [r] return g - def _revoke_rule_args_to_dict(self, context, to_port=None, from_port=None, + def _rule_args_to_dict(self, context, kwargs): + rules = [] + if not 'groups' in kwargs and not 'ip_ranges' in kwargs: + rule = self._rule_dict_last_step(context, **kwargs) + if rule: + rules.append(rule) + return rules + if 'ip_ranges' in kwargs: + rules = self._cidr_args_split(kwargs) + finalset = [] + for rule in rules: + if 'groups' in rule: + groups_values = self._groups_args_split(rule) + for groups_value in groups_values: + finalset.append(groups_value) + else: + if rule: + finalset.append(rule) + return finalset + + def _cidr_args_split(self, kwargs): + cidr_args_split = [] + cidrs = kwargs['ip_ranges'] + for key, cidr in cidrs.iteritems(): + mykwargs = kwargs.copy() + del mykwargs['ip_ranges'] + mykwargs['cidr_ip'] = cidr['cidr_ip'] + cidr_args_split.append(mykwargs) + return cidr_args_split + + def _groups_args_split(self, kwargs): + groups_args_split = [] + groups = kwargs['groups'] + for key, group in groups.iteritems(): + mykwargs = kwargs.copy() + del mykwargs['groups'] + if 'group_name' in group: + mykwargs['source_security_group_name'] = group['group_name'] + if 'user_id' in group: + mykwargs['source_security_group_owner_id'] = group['user_id'] + if 'group_id' in group: + mykwargs['source_security_group_id'] = group['group_id'] + groups_args_split.append(mykwargs) + return groups_args_split + + def _rule_dict_last_step(self, context, to_port=None, from_port=None, ip_protocol=None, cidr_ip=None, user_id=None, source_security_group_name=None, source_security_group_owner_id=None): @@ -615,7 +660,7 @@ class CloudController(object): msg = "Revoke security group ingress %s" LOG.audit(_(msg), security_group['name'], context=context) - criteria = self._revoke_rule_args_to_dict(context, **kwargs) + criteria = self._rule_args_to_dict(context, kwargs)[0] if criteria is None: raise exception.ApiError(_("Not enough parameters to build a " "valid rule.")) @@ -656,21 +701,34 @@ class CloudController(object): msg = "Authorize security group ingress %s" LOG.audit(_(msg), security_group['name'], context=context) - values = self._revoke_rule_args_to_dict(context, **kwargs) - if values is None: - raise exception.ApiError(_("Not enough parameters to build a " - "valid rule.")) - values['parent_group_id'] = security_group.id - - if self._security_group_rule_exists(security_group, values): - raise exception.ApiError(_('This rule already exists in group %s') - % group_name) - - security_group_rule = db.security_group_rule_create(context, values) + prevalues = [] + try: + prevalues = kwargs['ip_permissions'] + except KeyError: + prevalues.append(kwargs) + postvalues = [] + for values in prevalues: + rulesvalues = self._rule_args_to_dict(context, values) + if not rulesvalues: + err = "%s Not enough parameters to build a valid rule" + raise exception.ApiError(_(err % rulesvalues)) + for values_for_rule in rulesvalues: + values_for_rule['parent_group_id'] = security_group.id + if self._security_group_rule_exists(security_group, + values_for_rule): + err = '%s - This rule already exists in group' + raise exception.ApiError(_(err) % values_for_rule) + postvalues.append(values_for_rule) + + for values_for_rule in postvalues: + security_group_rule = db.security_group_rule_create(context, + values_for_rule) self.compute_api.trigger_security_group_rules_refresh(context, - security_group_id=security_group['id']) + security_group_id=security_group['id']) + group = db.security_group_get_by_name(context, context.project_id, + security_group['name']) return True def _get_source_project_id(self, context, source_security_group_owner_id): @@ -1147,7 +1205,7 @@ class CloudController(object): def rescue_instance(self, context, instance_id, **kwargs): """This is an extension to the normal ec2_api""" - self._do_instance(self.compute_api.rescue, contect, instnace_id) + self._do_instance(self.compute_api.rescue, context, instance_id) return True def unrescue_instance(self, context, instance_id, **kwargs): diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index e87d7c754..868b98a31 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -164,11 +164,17 @@ class APIRouterV11(APIRouter): def _setup_routes(self, mapper): super(APIRouterV11, self)._setup_routes(mapper, '1.1') - mapper.resource("image_meta", "meta", - controller=image_metadata.create_resource(), + image_metadata_controller = image_metadata.create_resource() + mapper.resource("image_meta", "metadata", + controller=image_metadata_controller, parent_resource=dict(member_name='image', collection_name='images')) + mapper.connect("metadata", "/images/{image_id}/metadata", + controller=image_metadata_controller, + action='update_all', + conditions={"method": ['PUT']}) + mapper.resource("server_meta", "meta", controller=server_metadata.create_resource(), parent_resource=dict(member_name='server', diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 57031ebf1..bd14a1389 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -167,3 +167,28 @@ def remove_version_from_href(href): msg = _('href does not contain version') raise ValueError(msg) return new_href + + +def get_version_from_href(href): + """Returns the api version in the href. + + Returns the api version in the href. + If no version is found, 1.0 is returned + + Given: 'http://www.nova.com/123' + Returns: '1.0' + + Given: 'http://www.nova.com/v1.1' + Returns: '1.1' + + """ + try: + #finds the first instance that matches /v#.#/ + version = re.findall(r'[/][v][0-9]+\.[0-9]+[/]', href) + #if no version was found, try finding /v#.# at the end of the string + if not version: + version = re.findall(r'[/][v][0-9]+\.[0-9]+$', href) + version = re.findall(r'[0-9]+\.[0-9]', version[0])[0] + except IndexError: + version = '1.0' + return version diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 7249f1261..f8317565e 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -71,9 +71,12 @@ class CreateInstanceHelper(object): if not body: raise exc.HTTPUnprocessableEntity() - context = req.environ['nova.context'] + if not 'server' in body: + raise exc.HTTPUnprocessableEntity() - password = self.controller._get_server_admin_password(body['server']) + server_dict = body['server'] + context = req.environ['nova.context'] + password = self.controller._get_server_admin_password(server_dict) key_name = None key_data = None @@ -95,7 +98,7 @@ class CreateInstanceHelper(object): locals()) raise exc.HTTPBadRequest(explanation=msg) - personality = body['server'].get('personality') + personality = server_dict.get('personality') injected_files = [] if personality: @@ -107,18 +110,18 @@ class CreateInstanceHelper(object): msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) - if not 'name' in body['server']: + if not 'name' in server_dict: msg = _("Server name is not defined") raise exc.HTTPBadRequest(explanation=msg) - zone_blob = body['server'].get('blob') - name = body['server']['name'] + zone_blob = server_dict.get('blob') + name = server_dict['name'] self._validate_server_name(name) name = name.strip() - reservation_id = body['server'].get('reservation_id') - min_count = body['server'].get('min_count') - max_count = body['server'].get('max_count') + reservation_id = server_dict.get('reservation_id') + min_count = server_dict.get('min_count') + max_count = server_dict.get('max_count') # min_count and max_count are optional. If they exist, they come # in as strings. We want to default 'min_count' to 1, and default # 'max_count' to be 'min_count'. @@ -145,7 +148,7 @@ class CreateInstanceHelper(object): display_description=name, key_name=key_name, key_data=key_data, - metadata=body['server'].get('metadata', {}), + metadata=server_dict.get('metadata', {}), injected_files=injected_files, admin_password=password, zone_blob=zone_blob, @@ -282,7 +285,7 @@ class CreateInstanceHelper(object): return password -class ServerXMLDeserializer(wsgi.XMLDeserializer): +class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): """ Deserializer to handle xml-formatted server create requests. @@ -299,11 +302,12 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): def _extract_server(self, node): """Marshal the server attribute of a parsed request""" server = {} - server_node = self._find_first_child_named(node, 'server') + server_node = self.find_first_child_named(node, 'server') for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]: if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) - metadata = self._extract_metadata(server_node) + metadata_node = self.find_first_child_named(server_node, "metadata") + metadata = self.extract_metadata(metadata_node) if metadata is not None: server["metadata"] = metadata personality = self._extract_personality(server_node) @@ -311,49 +315,17 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): server["personality"] = personality return server - def _extract_metadata(self, server_node): - """Marshal the metadata attribute of a parsed request""" - metadata_node = self._find_first_child_named(server_node, "metadata") - if metadata_node is None: - return None - metadata = {} - for meta_node in self._find_children_named(metadata_node, "meta"): - key = meta_node.getAttribute("key") - metadata[key] = self._extract_text(meta_node) - return metadata - def _extract_personality(self, server_node): """Marshal the personality attribute of a parsed request""" personality_node = \ - self._find_first_child_named(server_node, "personality") + self.find_first_child_named(server_node, "personality") if personality_node is None: return None personality = [] - for file_node in self._find_children_named(personality_node, "file"): + for file_node in self.find_children_named(personality_node, "file"): item = {} if file_node.hasAttribute("path"): item["path"] = file_node.getAttribute("path") - item["contents"] = self._extract_text(file_node) + item["contents"] = self.extract_text(file_node) personality.append(item) return personality - - def _find_first_child_named(self, parent, name): - """Search a nodes children for the first child with a given name""" - for node in parent.childNodes: - if node.nodeName == name: - return node - return None - - def _find_children_named(self, parent, name): - """Return all of a nodes children who have the given name""" - for node in parent.childNodes: - if node.nodeName == name: - yield node - - def _extract_text(self, node): - """Get the text field contained by the given node""" - if len(node.childNodes) == 1: - child = node.childNodes[0] - if child.nodeType == child.TEXT_NODE: - return child.nodeValue - return "" diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index 24cde69e4..1ab45d4f1 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -19,6 +19,7 @@ import webob.dec import webob.exc +from nova.api.openstack import common from nova.api.openstack import wsgi @@ -61,9 +62,13 @@ class Fault(webob.exc.HTTPException): content_type = req.best_match_content_type() + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10), + '1.1': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V11), + }[common.get_version_from_href(req.url)] + serializer = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, - xmlns=wsgi.XMLNS_V10), + 'application/xml': xml_serializer, 'application/json': wsgi.JSONDictSerializer(), }[content_type] @@ -100,9 +105,13 @@ class OverLimitFault(webob.exc.HTTPException): content_type = request.best_match_content_type() metadata = {"attributes": {"overLimitFault": "code"}} + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V10), + '1.1': wsgi.XMLDictSerializer(metadata, wsgi.XMLNS_V11), + }[common.get_version_from_href(request.url)] + serializer = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, - xmlns=wsgi.XMLNS_V10), + 'application/xml': xml_serializer, 'application/json': wsgi.JSONDictSerializer(), }[content_type] diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index c0fc8c09b..ee181c924 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -96,8 +96,16 @@ class Controller(object): self._check_quota_limit(context, metadata) img['properties'] = metadata self.image_service.update(context, image_id, img, None) + return dict(meta=meta) - return req.body + def update_all(self, req, image_id, body): + context = req.environ['nova.context'] + img = self.image_service.show(context, image_id) + metadata = body.get('metadata', {}) + self._check_quota_limit(context, metadata) + img['properties'] = metadata + self.image_service.update(context, image_id, img, None) + return dict(metadata=metadata) def delete(self, req, image_id, id): context = req.environ['nova.context'] @@ -110,6 +118,32 @@ class Controller(object): self.image_service.update(context, image_id, img, None) +class ImageMetadataXMLDeserializer(wsgi.MetadataXMLDeserializer): + + def _extract_metadata_container(self, datastring): + dom = minidom.parseString(datastring) + metadata_node = self.find_first_child_named(dom, "metadata") + metadata = self.extract_metadata(metadata_node) + return {'body': {'metadata': metadata}} + + def create(self, datastring): + return self._extract_metadata_container(datastring) + + def update_all(self, datastring): + return self._extract_metadata_container(datastring) + + def update(self, datastring): + dom = minidom.parseString(datastring) + metadata_item = self.extract_metadata(dom) + return {'body': {'meta': metadata_item}} + + +class HeadersSerializer(wsgi.ResponseHeadersSerializer): + + def delete(self, response, data): + response.status_int = 204 + + class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): def __init__(self, xmlns=wsgi.XMLNS_V11): super(ImageMetadataXMLSerializer, self).__init__(xmlns=xmlns) @@ -143,6 +177,9 @@ class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): def create(self, metadata_dict): return self._meta_list_to_xml_string(metadata_dict) + def update_all(self, metadata_dict): + return self._meta_list_to_xml_string(metadata_dict) + def _meta_item_to_xml_string(self, meta_item_dict): xml_doc = minidom.Document() item_key, item_value = meta_item_dict.items()[0] @@ -157,11 +194,21 @@ class ImageMetadataXMLSerializer(wsgi.XMLDictSerializer): def update(self, meta_item_dict): return self._meta_item_to_xml_string(meta_item_dict['meta']) + def default(self, *args, **kwargs): + return '' + def create_resource(): + headers_serializer = HeadersSerializer() + + body_deserializers = { + 'application/xml': ImageMetadataXMLDeserializer(), + } + body_serializers = { 'application/xml': ImageMetadataXMLSerializer(), } - serializer = wsgi.ResponseSerializer(body_serializers) + serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) + deserializer = wsgi.RequestDeserializer(body_deserializers) - return wsgi.Resource(Controller(), serializer=serializer) + return wsgi.Resource(Controller(), deserializer, serializer) diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 2996b032d..a74fae487 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -16,6 +16,7 @@ # under the License. import time +from xml.dom import minidom from webob import exc @@ -100,17 +101,51 @@ class ControllerV11(Controller): return nova.api.openstack.views.addresses.ViewBuilderV11() +class IPXMLSerializer(wsgi.XMLDictSerializer): + def __init__(self, xmlns=wsgi.XMLNS_V11): + super(IPXMLSerializer, self).__init__(xmlns=xmlns) + + def _ip_to_xml(self, xml_doc, ip_dict): + ip_node = xml_doc.createElement('ip') + ip_node.setAttribute('addr', ip_dict['addr']) + ip_node.setAttribute('version', str(ip_dict['version'])) + return ip_node + + def _network_to_xml(self, xml_doc, network_id, ip_dicts): + network_node = xml_doc.createElement('network') + network_node.setAttribute('id', network_id) + + for ip_dict in ip_dicts: + ip_node = self._ip_to_xml(xml_doc, ip_dict) + network_node.appendChild(ip_node) + + return network_node + + def networks_to_xml(self, xml_doc, networks_container): + addresses_node = xml_doc.createElement('addresses') + for (network_id, ip_dicts) in networks_container.items(): + network_node = self._network_to_xml(xml_doc, network_id, ip_dicts) + addresses_node.appendChild(network_node) + return addresses_node + + def show(self, network_container): + (network_id, ip_dicts) = network_container.items()[0] + xml_doc = minidom.Document() + node = self._network_to_xml(xml_doc, network_id, ip_dicts) + return self.to_xml_string(node, False) + + def index(self, addresses_container): + xml_doc = minidom.Document() + node = self.networks_to_xml(xml_doc, addresses_container['addresses']) + return self.to_xml_string(node, False) + + def create_resource(version): controller = { '1.0': ControllerV10, '1.1': ControllerV11, }[version]() - xmlns = { - '1.0': wsgi.XMLNS_V10, - '1.1': wsgi.XMLNS_V11, - }[version] - metadata = { 'list_collections': { 'public': {'item_name': 'ip', 'item_key': 'addr'}, @@ -118,10 +153,11 @@ def create_resource(version): }, } - body_serializers = { - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, - xmlns=xmlns), - } - serializer = wsgi.ResponseSerializer(body_serializers) + xml_serializer = { + '1.0': wsgi.XMLDictSerializer(metadata=metadata, xmlns=wsgi.XMLNS_V11), + '1.1': IPXMLSerializer(), + }[version] + + serializer = wsgi.ResponseSerializer({'application/xml': xml_serializer}) return wsgi.Resource(controller, serializer=serializer) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index c10cb3bc9..a28443d12 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -136,10 +136,44 @@ class XMLDeserializer(TextDeserializer): listnames) return result + def find_first_child_named(self, parent, name): + """Search a nodes children for the first child with a given name""" + for node in parent.childNodes: + if node.nodeName == name: + return node + return None + + def find_children_named(self, parent, name): + """Return all of a nodes children who have the given name""" + for node in parent.childNodes: + if node.nodeName == name: + yield node + + def extract_text(self, node): + """Get the text field contained by the given node""" + if len(node.childNodes) == 1: + child = node.childNodes[0] + if child.nodeType == child.TEXT_NODE: + return child.nodeValue + return "" + def default(self, datastring): return {'body': self._from_xml(datastring)} +class MetadataXMLDeserializer(XMLDeserializer): + + def extract_metadata(self, metadata_node): + """Marshal the metadata attribute of a parsed request""" + if metadata_node is None: + return None + metadata = {} + for meta_node in self.find_children_named(metadata_node, "meta"): + key = meta_node.getAttribute("key") + metadata[key] = self.extract_text(meta_node) + return metadata + + class RequestHeadersDeserializer(ActionDispatcher): """Default request headers deserializer""" @@ -397,8 +431,9 @@ class ResponseSerializer(object): def serialize_body(self, response, data, content_type, action): response.headers['Content-Type'] = content_type - serializer = self.get_body_serializer(content_type) - response.body = serializer.serialize(data, action) + if data is not None: + serializer = self.get_body_serializer(content_type) + response.body = serializer.serialize(data, action) def get_body_serializer(self, content_type): try: @@ -444,7 +479,7 @@ class Resource(wsgi.Application): action, args, accept = self.deserializer.deserialize(request) except exception.InvalidContentType: msg = _("Unsupported Content-Type") - return webob.exc.HTTPBadRequest(explanation=msg) + return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) except exception.MalformedRequestBody: msg = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) @@ -455,7 +490,6 @@ class Resource(wsgi.Application): LOG.info(_("HTTP exception thrown: %s"), unicode(ex)) action_result = faults.Fault(ex) - #TODO(bcwaldon): find a more elegant way to pass through non-dict types if type(action_result) is dict or action_result is None: response = self.serializer.serialize(action_result, accept, diff --git a/nova/compute/api.py b/nova/compute/api.py index 9994e5724..c49c0d95c 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -127,7 +127,7 @@ class API(base.Base): quota_metadata = quota.allowed_metadata_items(context, num_metadata) if quota_metadata < num_metadata: pid = context.project_id - msg = _("Quota exceeeded for %(pid)s, tried to set " + msg = _("Quota exceeded for %(pid)s, tried to set " "%(num_metadata)s metadata properties") % locals() LOG.warn(msg) raise quota.QuotaError(msg, "MetadataLimitExceeded") @@ -138,7 +138,7 @@ class API(base.Base): for k, v in metadata.iteritems(): if len(k) > 255 or len(v) > 255: pid = context.project_id - msg = _("Quota exceeeded for %(pid)s, metadata property " + msg = _("Quota exceeded for %(pid)s, metadata property " "key or value too long") % locals() LOG.warn(msg) raise quota.QuotaError(msg, "MetadataLimitExceeded") @@ -165,7 +165,7 @@ class API(base.Base): instance_type) if num_instances < min_count: pid = context.project_id - LOG.warn(_("Quota exceeeded for %(pid)s," + LOG.warn(_("Quota exceeded for %(pid)s," " tried to run %(min_count)s instances") % locals()) if num_instances <= 0: message = _("Instance quota exceeded. You cannot run any " diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 5819a520a..31627fe3b 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -214,6 +214,15 @@ class ComputeManager(manager.SchedulerDependentManager): """This call passes straight through to the virtualization driver.""" return self.driver.refresh_provider_fw_rules() + def _get_instance_nw_info(self, context, instance): + """Get a list of dictionaries of network data of an instance. + Returns an empty list if stub_network flag is set.""" + network_info = [] + if not FLAGS.stub_network: + network_info = self.network_api.get_instance_nw_info(context, + instance) + return network_info + def _setup_block_device_mapping(self, context, instance_id): """setup volumes for block device mapping""" self.db.instance_set_state(context, @@ -304,8 +313,6 @@ class ComputeManager(manager.SchedulerDependentManager): network_info = self.network_api.allocate_for_instance(context, instance, vpn=is_vpn) LOG.debug(_("instance network_info: |%s|"), network_info) - self.network_manager.setup_compute_network(context, - instance_id) else: # TODO(tr3buchet) not really sure how this should be handled. # virt requires network_info to be passed in but stub_network @@ -359,6 +366,7 @@ class ComputeManager(manager.SchedulerDependentManager): {'action_str': action_str, 'instance_id': instance_id}, context=context) + network_info = self._get_instance_nw_info(context, instance) if not FLAGS.stub_network: self.network_api.deallocate_for_instance(context, instance) @@ -371,7 +379,7 @@ class ComputeManager(manager.SchedulerDependentManager): self.db.instance_destroy(context, instance_id) raise exception.Error(_('trying to destroy already destroyed' ' instance: %s') % instance_id) - self.driver.destroy(instance) + self.driver.destroy(instance, network_info) if action_str == 'Terminating': terminate_volumes(self.db, context, instance_id) @@ -416,11 +424,13 @@ class ComputeManager(manager.SchedulerDependentManager): self._update_state(context, instance_id, power_state.BUILDING) - self.driver.destroy(instance_ref) + network_info = self._get_instance_nw_info(context, instance_ref) + + self.driver.destroy(instance_ref, network_info) image_ref = kwargs.get('image_ref') instance_ref.image_ref = image_ref instance_ref.injected_files = kwargs.get('injected_files', []) - self.driver.spawn(instance_ref) + self.driver.spawn(instance_ref, network_info) self._update_image_ref(context, instance_id, image_ref) self._update_launched_at(context, instance_id) @@ -453,8 +463,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_id, power_state.NOSTATE, 'rebooting') - self.network_manager.setup_compute_network(context, instance_id) - self.driver.reboot(instance_ref) + network_info = self._get_instance_nw_info(context, instance_ref) + self.driver.reboot(instance_ref, network_info) self._update_state(context, instance_id) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @@ -644,10 +654,10 @@ class ComputeManager(manager.SchedulerDependentManager): instance_id, power_state.NOSTATE, 'rescuing') - self.network_manager.setup_compute_network(context, instance_id) _update_state = lambda result: self._update_state_callback( self, context, instance_id, result) - self.driver.rescue(instance_ref, _update_state) + network_info = self._get_instance_nw_info(context, instance_ref) + self.driver.rescue(instance_ref, _update_state, network_info) self._update_state(context, instance_id) @exception.wrap_exception(notifier=notifier, publisher_id=publisher_id()) @@ -663,7 +673,8 @@ class ComputeManager(manager.SchedulerDependentManager): 'unrescuing') _update_state = lambda result: self._update_state_callback( self, context, instance_id, result) - self.driver.unrescue(instance_ref, _update_state) + network_info = self._get_instance_nw_info(context, instance_ref) + self.driver.unrescue(instance_ref, _update_state, network_info) self._update_state(context, instance_id) @staticmethod @@ -679,7 +690,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref = self.db.instance_get_by_uuid(context, migration_ref.instance_uuid) - self.driver.destroy(instance_ref) + network_info = self._get_instance_nw_info(context, instance_ref) + self.driver.destroy(instance_ref, network_info) usage_info = utils.usage_from_instance(instance_ref) notifier.notify('compute.%s' % self.host, 'compute.instance.resize.confirm', @@ -699,7 +711,8 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref = self.db.instance_get_by_uuid(context, migration_ref.instance_uuid) - self.driver.destroy(instance_ref) + network_info = self._get_instance_nw_info(context, instance_ref) + self.driver.destroy(instance_ref, network_info) topic = self.db.queue_get_for(context, FLAGS.compute_topic, instance_ref['host']) rpc.cast(context, topic, @@ -841,8 +854,7 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref = self.db.instance_get_by_uuid(context, instance_ref.uuid) - network_info = self.network_api.get_instance_nw_info(context, - instance_ref) + network_info = self._get_instance_nw_info(context, instance_ref) self.driver.finish_resize(instance_ref, disk_info, network_info) self.db.migration_update(context, migration_id, @@ -996,8 +1008,7 @@ class ComputeManager(manager.SchedulerDependentManager): LOG.debug(_('instance %s: inject network info'), instance_id, context=context) instance = self.db.instance_get(context, instance_id) - network_info = self.network_api.get_instance_nw_info(context, - instance) + network_info = self._get_instance_nw_info(context, instance) LOG.debug(_("network_info to inject: |%s|"), network_info) self.driver.inject_network_info(instance, network_info) @@ -1215,17 +1226,17 @@ class ComputeManager(manager.SchedulerDependentManager): # # Retry operation is necessary because continuously request comes, # concorrent request occurs to iptables, then it complains. + network_info = self._get_instance_nw_info(context, instance_ref) max_retry = FLAGS.live_migration_retry_count for cnt in range(max_retry): try: - self.network_manager.setup_compute_network(context, - instance_id) + self.driver.plug_vifs(instance_ref, network_info) break except exception.ProcessExecutionError: if cnt == max_retry - 1: raise else: - LOG.warn(_("setup_compute_network() failed %(cnt)d." + LOG.warn(_("plug_vifs() failed %(cnt)d." "Retry up to %(max_retry)d for %(hostname)s.") % locals()) time.sleep(1) @@ -1303,8 +1314,9 @@ class ComputeManager(manager.SchedulerDependentManager): # Releasing vlan. # (not necessary in current implementation?) + network_info = self._get_instance_nw_info(ctxt, instance_ref) # Releasing security group ingress rule. - self.driver.unfilter_instance(instance_ref) + self.driver.unfilter_instance(instance_ref, network_info) # Database updating. i_name = instance_ref.name diff --git a/nova/network/api.py b/nova/network/api.py index c2360f0d0..33a9fe239 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -45,6 +45,10 @@ class API(base.Base): context.project_id) return ips + def get_vifs_by_instance(self, context, instance_id): + vifs = self.db.virtual_interface_get_by_instance(context, instance_id) + return vifs + def allocate_floating_ip(self, context): """Adds a floating ip to a project.""" # NOTE(vish): We don't know which network host should get the ip diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index a8ce1c16a..8ace07884 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -455,6 +455,7 @@ def ensure_vlan_bridge(vlan_num, bridge, bridge_interface, net_attrs=None): """Create a vlan and bridge unless they already exist.""" interface = ensure_vlan(vlan_num, bridge_interface) ensure_bridge(bridge, interface, net_attrs) + return interface @utils.synchronized('ensure_vlan', external=True) diff --git a/nova/network/manager.py b/nova/network/manager.py index 4f984296c..6f7573f66 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -258,7 +258,7 @@ class FloatingIP(object): # NOTE(tr3buchet): all networks hosts in zone now use the same pool LOG.debug("QUOTA: %s" % quota.allowed_floating_ips(context, 1)) if quota.allowed_floating_ips(context, 1) < 1: - LOG.warn(_('Quota exceeeded for %s, tried to allocate ' + LOG.warn(_('Quota exceeded for %s, tried to allocate ' 'address'), context.project_id) raise quota.QuotaError(_('Address quota exceeded. You cannot ' @@ -300,6 +300,12 @@ class NetworkManager(manager.SchedulerDependentManager): The one at a time part is to flatten the layout to help scale """ + # If True, this manager requires VIF to create a bridge. + SHOULD_CREATE_BRIDGE = False + + # If True, this manager requires VIF to create VLAN tag. + SHOULD_CREATE_VLAN = False + timeout_fixed_ips = True def __init__(self, network_driver=None, *args, **kwargs): @@ -426,7 +432,12 @@ class NetworkManager(manager.SchedulerDependentManager): and info = dict containing pertinent networking data """ # TODO(tr3buchet) should handle floating IPs as well? - fixed_ips = self.db.fixed_ip_get_by_instance(context, instance_id) + try: + fixed_ips = self.db.fixed_ip_get_by_instance(context, instance_id) + except exception.FixedIpNotFoundForInstance: + LOG.warn(_('No fixed IPs for instance %s'), instance_id) + fixed_ips = [] + vifs = self.db.virtual_interface_get_by_instance(context, instance_id) flavor = self.db.instance_type_get(context, instance_type_id) network_info = [] @@ -458,7 +469,10 @@ class NetworkManager(manager.SchedulerDependentManager): 'id': network['id'], 'cidr': network['cidr'], 'cidr_v6': network['cidr_v6'], - 'injected': network['injected']} + 'injected': network['injected'], + 'vlan': network['vlan'], + 'bridge_interface': network['bridge_interface'], + 'multi_host': network['multi_host']} if network['multi_host']: dhcp_server = self._get_dhcp_ip(context, network, host) else: @@ -473,7 +487,10 @@ class NetworkManager(manager.SchedulerDependentManager): 'mac': vif['address'], 'rxtx_cap': flavor['rxtx_cap'], 'dns': [], - 'ips': [ip_dict(ip) for ip in network_IPs]} + 'ips': [ip_dict(ip) for ip in network_IPs], + 'should_create_bridge': self.SHOULD_CREATE_BRIDGE, + 'should_create_vlan': self.SHOULD_CREATE_VLAN} + if network['cidr_v6']: info['ip6s'] = [ip6_dict()] # TODO(tr3buchet): handle ip6 routes here as well @@ -698,14 +715,6 @@ class NetworkManager(manager.SchedulerDependentManager): """Sets up network on this host.""" raise NotImplementedError() - def setup_compute_network(self, context, instance_id): - """Sets up matching network for compute hosts. - - this code is run on and by the compute host, not on network - hosts - """ - raise NotImplementedError() - class FlatManager(NetworkManager): """Basic network where no vlans are used. @@ -749,13 +758,6 @@ class FlatManager(NetworkManager): **kwargs) self.db.fixed_ip_disassociate(context, address) - def setup_compute_network(self, context, instance_id): - """Network is created manually. - - this code is run on and by the compute host, not on network hosts - """ - pass - def _setup_network(self, context, network_ref): """Setup Network on this host.""" net = {} @@ -772,6 +774,8 @@ class FlatDHCPManager(FloatingIP, RPCAllocateFixedIP, NetworkManager): """ + SHOULD_CREATE_BRIDGE = True + def init_host(self): """Do any initialization that needs to be run if this is a standalone service. @@ -784,17 +788,6 @@ class FlatDHCPManager(FloatingIP, RPCAllocateFixedIP, NetworkManager): self.driver.metadata_forward() - def setup_compute_network(self, context, instance_id): - """Sets up matching networks for compute hosts. - - this code is run on and by the compute host, not on network hosts - """ - networks = db.network_get_all_by_instance(context, instance_id) - for network in networks: - if not network['multi_host']: - self.driver.ensure_bridge(network['bridge'], - network['bridge_interface']) - def _setup_network(self, context, network_ref): """Sets up network on this host.""" network_ref['dhcp_server'] = self._get_dhcp_ip(context, network_ref) @@ -825,6 +818,9 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): """ + SHOULD_CREATE_BRIDGE = True + SHOULD_CREATE_VLAN = True + def init_host(self): """Do any initialization that needs to be run if this is a standalone service. @@ -863,17 +859,6 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): """Force adds another network to a project.""" self.db.network_associate(context, project_id, force=True) - def setup_compute_network(self, context, instance_id): - """Sets up matching network for compute hosts. - this code is run on and by the compute host, not on network hosts - """ - networks = self.db.network_get_all_by_instance(context, instance_id) - for network in networks: - if not network['multi_host']: - self.driver.ensure_vlan_bridge(network['vlan'], - network['bridge'], - network['bridge_interface']) - def _get_networks_for_instance(self, context, instance_id, project_id): """Determine which networks an instance should connect to.""" # get networks associated with project diff --git a/nova/network/vmwareapi_net.py b/nova/network/vmwareapi_net.py deleted file mode 100644 index b32cf3303..000000000 --- a/nova/network/vmwareapi_net.py +++ /dev/null @@ -1,82 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2011 Citrix Systems, Inc. -# Copyright 2011 OpenStack LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Implements vlans for vmwareapi.""" - -from nova import db -from nova import exception -from nova import flags -from nova import log as logging -from nova import utils -from nova.virt.vmwareapi_conn import VMWareAPISession -from nova.virt.vmwareapi import network_utils - - -LOG = logging.getLogger("nova.network.vmwareapi_net") - - -FLAGS = flags.FLAGS -FLAGS['vlan_interface'].SetDefault('vmnic0') - - -def ensure_vlan_bridge(vlan_num, bridge, bridge_interface, net_attrs=None): - """Create a vlan and bridge unless they already exist.""" - # Open vmwareapi session - host_ip = FLAGS.vmwareapi_host_ip - host_username = FLAGS.vmwareapi_host_username - host_password = FLAGS.vmwareapi_host_password - if not host_ip or host_username is None or host_password is None: - raise Exception(_('Must specify vmwareapi_host_ip, ' - 'vmwareapi_host_username ' - 'and vmwareapi_host_password to use ' - 'connection_type=vmwareapi')) - session = VMWareAPISession(host_ip, host_username, host_password, - FLAGS.vmwareapi_api_retry_count) - vlan_interface = bridge_interface - # Check if the vlan_interface physical network adapter exists on the host - if not network_utils.check_if_vlan_interface_exists(session, - vlan_interface): - raise exception.NetworkAdapterNotFound(adapter=vlan_interface) - - # Get the vSwitch associated with the Physical Adapter - vswitch_associated = network_utils.get_vswitch_for_vlan_interface( - session, vlan_interface) - if vswitch_associated is None: - raise exception.SwicthNotFoundForNetworkAdapter(adapter=vlan_interface) - # Check whether bridge already exists and retrieve the the ref of the - # network whose name_label is "bridge" - network_ref = network_utils.get_network_with_the_name(session, bridge) - if network_ref is None: - # Create a port group on the vSwitch associated with the vlan_interface - # corresponding physical network adapter on the ESX host - network_utils.create_port_group(session, bridge, vswitch_associated, - vlan_num) - else: - # Get the vlan id and vswitch corresponding to the port group - pg_vlanid, pg_vswitch = \ - network_utils.get_vlanid_and_vswitch_for_portgroup(session, bridge) - - # Check if the vswitch associated is proper - if pg_vswitch != vswitch_associated: - raise exception.InvalidVLANPortGroup(bridge=bridge, - expected=vswitch_associated, - actual=pg_vswitch) - - # Check if the vlan id is proper for the port group - if pg_vlanid != vlan_num: - raise exception.InvalidVLANTag(bridge=bridge, tag=vlan_num, - pgroup=pg_vlanid) diff --git a/nova/network/xenapi_net.py b/nova/network/xenapi_net.py deleted file mode 100644 index e86f4017d..000000000 --- a/nova/network/xenapi_net.py +++ /dev/null @@ -1,87 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2011 Citrix Systems, Inc. -# Copyright 2011 OpenStack LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Implements vlans, bridges, and iptables rules using linux utilities.""" - -import os - -from nova import db -from nova import exception -from nova import flags -from nova import log as logging -from nova import utils -from nova.virt import xenapi_conn -from nova.virt.xenapi import network_utils - - -LOG = logging.getLogger("nova.xenapi_net") - - -FLAGS = flags.FLAGS - - -def ensure_vlan_bridge(vlan_num, bridge, bridge_interface, net_attrs=None): - """Create a vlan and bridge unless they already exist.""" - # Open xenapi session - LOG.debug('ENTERING ensure_vlan_bridge in xenapi net') - url = FLAGS.xenapi_connection_url - username = FLAGS.xenapi_connection_username - password = FLAGS.xenapi_connection_password - session = xenapi_conn.XenAPISession(url, username, password) - # Check whether bridge already exists - # Retrieve network whose name_label is "bridge" - network_ref = network_utils.NetworkHelper.find_network_with_name_label( - session, - bridge) - if network_ref is None: - # If bridge does not exists - # 1 - create network - description = 'network for nova bridge %s' % bridge - network_rec = {'name_label': bridge, - 'name_description': description, - 'other_config': {}} - network_ref = session.call_xenapi('network.create', network_rec) - # 2 - find PIF for VLAN - # NOTE(salvatore-orlando): using double quotes inside single quotes - # as xapi filter only support tokens in double quotes - expr = 'field "device" = "%s" and \ - field "VLAN" = "-1"' % bridge_interface - pifs = session.call_xenapi('PIF.get_all_records_where', expr) - pif_ref = None - # Multiple PIF are ok: we are dealing with a pool - if len(pifs) == 0: - raise Exception( - _('Found no PIF for device %s') % bridge_interface) - # 3 - create vlan for network - for pif_ref in pifs.keys(): - session.call_xenapi('VLAN.create', - pif_ref, - str(vlan_num), - network_ref) - else: - # Check VLAN tag is appropriate - network_rec = session.call_xenapi('network.get_record', network_ref) - # Retrieve PIFs from network - for pif_ref in network_rec['PIFs']: - # Retrieve VLAN from PIF - pif_rec = session.call_xenapi('PIF.get_record', pif_ref) - pif_vlan = int(pif_rec['VLAN']) - # Raise an exception if VLAN != vlan_num - if pif_vlan != vlan_num: - raise Exception(_("PIF %(pif_rec['uuid'])s for network " - "%(bridge)s has VLAN id %(pif_vlan)d. " - "Expected %(vlan_num)d") % locals()) diff --git a/nova/tests/api/openstack/test_common.py b/nova/tests/api/openstack/test_common.py index 4c4d03995..f09270b34 100644 --- a/nova/tests/api/openstack/test_common.py +++ b/nova/tests/api/openstack/test_common.py @@ -247,3 +247,21 @@ class MiscFunctionsTest(test.TestCase): self.assertRaises(ValueError, common.get_id_from_href, fixture) + + def test_get_version_from_href(self): + fixture = 'http://www.testsite.com/v1.1/images' + expected = '1.1' + actual = common.get_version_from_href(fixture) + self.assertEqual(actual, expected) + + def test_get_version_from_href_2(self): + fixture = 'http://www.testsite.com/v1.1' + expected = '1.1' + actual = common.get_version_from_href(fixture) + self.assertEqual(actual, expected) + + def test_get_version_from_href_default(self): + fixture = 'http://www.testsite.com/images' + expected = '1.0' + actual = common.get_version_from_href(fixture) + self.assertEqual(actual, expected) diff --git a/nova/tests/api/openstack/test_faults.py b/nova/tests/api/openstack/test_faults.py index 927009e77..6da27540a 100644 --- a/nova/tests/api/openstack/test_faults.py +++ b/nova/tests/api/openstack/test_faults.py @@ -16,6 +16,7 @@ # under the License. import json +from xml.dom import minidom import webob import webob.dec @@ -24,6 +25,7 @@ import webob.exc from nova import test from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack import wsgi class TestFaults(test.TestCase): @@ -144,3 +146,108 @@ class TestFaults(test.TestCase): """Ensure the status_int is set correctly on faults""" fault = faults.Fault(webob.exc.HTTPBadRequest(explanation='what?')) self.assertEqual(fault.status_int, 400) + + def test_v10_xml_serializer(self): + """Ensure that a v1.0 request responds with a v1.0 xmlns""" + request = webob.Request.blank('/', + headers={"Accept": "application/xml"}) + + fault = faults.Fault(webob.exc.HTTPBadRequest(explanation='scram')) + response = request.get_response(fault) + + self.assertTrue(common.XML_NS_V10 in response.body) + self.assertEqual(response.content_type, "application/xml") + self.assertEqual(response.status_int, 400) + + def test_v11_xml_serializer(self): + """Ensure that a v1.1 request responds with a v1.1 xmlns""" + request = webob.Request.blank('/v1.1', + headers={"Accept": "application/xml"}) + + fault = faults.Fault(webob.exc.HTTPBadRequest(explanation='scram')) + response = request.get_response(fault) + + self.assertTrue(common.XML_NS_V11 in response.body) + self.assertEqual(response.content_type, "application/xml") + self.assertEqual(response.status_int, 400) + + +class FaultsXMLSerializationTestV11(test.TestCase): + """Tests covering `nova.api.openstack.faults:Fault` class.""" + + def _prepare_xml(self, xml_string): + xml_string = xml_string.replace(" ", "") + xml_string = xml_string.replace("\n", "") + xml_string = xml_string.replace("\t", "") + return xml_string + + def test_400_fault(self): + metadata = {'attributes': {"badRequest": 'code'}} + serializer = wsgi.XMLDictSerializer(metadata=metadata, + xmlns=common.XML_NS_V11) + + fixture = { + "badRequest": { + "message": "scram", + "code": 400, + }, + } + + output = serializer.serialize(fixture) + actual = minidom.parseString(self._prepare_xml(output)) + + expected = minidom.parseString(self._prepare_xml(""" + <badRequest code="400" xmlns="%s"> + <message>scram</message> + </badRequest> + """) % common.XML_NS_V11) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_413_fault(self): + metadata = {'attributes': {"overLimit": 'code'}} + serializer = wsgi.XMLDictSerializer(metadata=metadata, + xmlns=common.XML_NS_V11) + + fixture = { + "overLimit": { + "message": "sorry", + "code": 413, + "retryAfter": 4, + }, + } + + output = serializer.serialize(fixture) + actual = minidom.parseString(self._prepare_xml(output)) + + expected = minidom.parseString(self._prepare_xml(""" + <overLimit code="413" xmlns="%s"> + <message>sorry</message> + <retryAfter>4</retryAfter> + </overLimit> + """) % common.XML_NS_V11) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_404_fault(self): + metadata = {'attributes': {"itemNotFound": 'code'}} + serializer = wsgi.XMLDictSerializer(metadata=metadata, + xmlns=common.XML_NS_V11) + + fixture = { + "itemNotFound": { + "message": "sorry", + "code": 404, + }, + } + + output = serializer.serialize(fixture) + actual = minidom.parseString(self._prepare_xml(output)) + + expected = minidom.parseString(self._prepare_xml(""" + <itemNotFound code="404" xmlns="%s"> + <message>sorry</message> + </itemNotFound> + """) % common.XML_NS_V11) + + self.assertEqual(expected.toxml(), actual.toxml()) diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index d9fb61e2a..31ca18497 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -103,8 +103,7 @@ class ImageMetaDataTest(test.TestCase): super(ImageMetaDataTest, self).tearDown() def test_index(self): - req = webob.Request.blank('/v1.1/images/1/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/images/1/metadata') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -114,8 +113,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(value, res_dict['metadata'][key]) def test_show(self): - req = webob.Request.blank('/v1.1/images/1/meta/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/images/1/metadata/key1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -124,42 +122,66 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual('value1', res_dict['meta']['key1']) def test_show_not_found(self): - req = webob.Request.blank('/v1.1/images/1/meta/key9') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/images/1/metadata/key9') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) def test_create(self): - req = webob.Request.blank('/v1.1/images/2/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/images/2/metadata') req.method = 'POST' req.body = '{"metadata": {"key9": "value9"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) - res_dict = json.loads(res.body) + + self.assertEqual(200, res.status_int) + actual_output = json.loads(res.body) + + expected_output = { + 'metadata': { + 'key1': 'value1', + 'key2': 'value2', + 'key9': 'value9', + }, + } + + self.assertEqual(expected_output, actual_output) + + def test_update_all(self): + req = webob.Request.blank('/v1.1/images/2/metadata') + req.method = 'PUT' + req.body = '{"metadata": {"key9": "value9"}}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) - self.assertEqual('value9', res_dict['metadata']['key9']) - # other items should not be modified - self.assertEqual('value1', res_dict['metadata']['key1']) - self.assertEqual('value2', res_dict['metadata']['key2']) - self.assertEqual(1, len(res_dict)) + actual_output = json.loads(res.body) + + expected_output = { + 'metadata': { + 'key9': 'value9', + }, + } + + self.assertEqual(expected_output, actual_output) def test_update_item(self): - req = webob.Request.blank('/v1.1/images/1/meta/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/images/1/metadata/key1') req.method = 'PUT' req.body = '{"meta": {"key1": "zz"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) - res_dict = json.loads(res.body) - self.assertTrue('meta' in res_dict) - self.assertEqual(len(res_dict['meta']), 1) - self.assertEqual('zz', res_dict['meta']['key1']) + actual_output = json.loads(res.body) + expected_output = { + 'meta': { + 'key1': 'zz', + }, + } + self.assertEqual(actual_output, expected_output) def test_update_item_bad_body(self): - req = webob.Request.blank('/v1.1/images/1/meta/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/images/1/metadata/key1') req.method = 'PUT' req.body = '{"key1": "zz"}' req.headers["content-type"] = "application/json" @@ -167,8 +189,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_update_item_too_many_keys(self): - req = webob.Request.blank('/v1.1/images/1/meta/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/images/1/metadata/key1') req.method = 'PUT' req.body = '{"meta": {"key1": "value1", "key2": "value2"}}' req.headers["content-type"] = "application/json" @@ -176,24 +197,38 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_update_item_body_uri_mismatch(self): - req = webob.Request.blank('/v1.1/images/1/meta/bad') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/images/1/metadata/bad') req.method = 'PUT' req.body = '{"meta": {"key1": "value1"}}' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) + def test_update_item_xml(self): + req = webob.Request.blank('/v1.1/images/1/metadata/key1') + req.method = 'PUT' + req.body = '<meta key="key1">five</meta>' + req.headers["content-type"] = "application/xml" + res = req.get_response(fakes.wsgi_app()) + + self.assertEqual(200, res.status_int) + actual_output = json.loads(res.body) + expected_output = { + 'meta': { + 'key1': 'five', + }, + } + self.assertEqual(actual_output, expected_output) + def test_delete(self): - req = webob.Request.blank('/v1.1/images/2/meta/key1') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/images/2/metadata/key1') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) - self.assertEqual(200, res.status_int) + self.assertEqual(204, res.status_int) + self.assertEqual('', res.body) def test_delete_not_found(self): - req = webob.Request.blank('/v1.1/images/2/meta/blah') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/images/2/metadata/blah') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -203,8 +238,7 @@ class ImageMetaDataTest(test.TestCase): for num in range(FLAGS.quota_metadata_items + 1): data['metadata']['key%i' % num] = "blah" json_string = str(data).replace("\'", "\"") - req = webob.Request.blank('/v1.1/images/2/meta') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/images/2/metadata') req.method = 'POST' req.body = json_string req.headers["content-type"] = "application/json" @@ -212,8 +246,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_too_many_metadata_items_on_put(self): - req = webob.Request.blank('/v1.1/images/3/meta/blah') - req.environ['api.version'] = '1.1' + req = webob.Request.blank('/v1.1/images/3/metadata/blah') req.method = 'PUT' req.body = '{"meta": {"blah": "blah"}}' req.headers["content-type"] = "application/json" @@ -221,9 +254,49 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) +class ImageMetadataXMLDeserializationTest(test.TestCase): + + deserializer = openstack.image_metadata.ImageMetadataXMLDeserializer() + + def test_create(self): + request_body = """ + <metadata xmlns="http://docs.openstack.org/compute/api/v1.1"> + <meta key='123'>asdf</meta> + <meta key='567'>jkl;</meta> + </metadata>""" + output = self.deserializer.deserialize(request_body, 'create') + expected = {"body": {"metadata": {"123": "asdf", "567": "jkl;"}}} + self.assertEquals(output, expected) + + def test_create_empty(self): + request_body = """ + <metadata xmlns="http://docs.openstack.org/compute/api/v1.1"/>""" + output = self.deserializer.deserialize(request_body, 'create') + expected = {"body": {"metadata": {}}} + self.assertEquals(output, expected) + + def test_update_all(self): + request_body = """ + <metadata xmlns="http://docs.openstack.org/compute/api/v1.1"> + <meta key='123'>asdf</meta> + <meta key='567'>jkl;</meta> + </metadata>""" + output = self.deserializer.deserialize(request_body, 'update_all') + expected = {"body": {"metadata": {"123": "asdf", "567": "jkl;"}}} + self.assertEquals(output, expected) + + def test_update(self): + request_body = """ + <meta xmlns="http://docs.openstack.org/compute/api/v1.1" + key='123'>asdf</meta>""" + output = self.deserializer.deserialize(request_body, 'update') + expected = {"body": {"meta": {"123": "asdf"}}} + self.assertEquals(output, expected) + + class ImageMetadataXMLSerializationTest(test.TestCase): - def test_index_xml(self): + def test_index(self): serializer = openstack.image_metadata.ImageMetadataXMLSerializer() fixture = { 'metadata': { @@ -247,7 +320,7 @@ class ImageMetadataXMLSerializationTest(test.TestCase): self.assertEqual(expected.toxml(), actual.toxml()) - def test_index_xml_null(self): + def test_index_null(self): serializer = openstack.image_metadata.ImageMetadataXMLSerializer() fixture = { 'metadata': { @@ -267,7 +340,7 @@ class ImageMetadataXMLSerializationTest(test.TestCase): self.assertEqual(expected.toxml(), actual.toxml()) - def test_index_xml_unicode(self): + def test_index_unicode(self): serializer = openstack.image_metadata.ImageMetadataXMLSerializer() fixture = { 'metadata': { @@ -287,7 +360,7 @@ class ImageMetadataXMLSerializationTest(test.TestCase): self.assertEqual(expected.toxml(), actual.toxml()) - def test_show_xml(self): + def test_show(self): serializer = openstack.image_metadata.ImageMetadataXMLSerializer() fixture = { 'meta': { @@ -305,7 +378,31 @@ class ImageMetadataXMLSerializationTest(test.TestCase): self.assertEqual(expected.toxml(), actual.toxml()) - def test_update_item_xml(self): + def test_update_all(self): + serializer = openstack.image_metadata.ImageMetadataXMLSerializer() + fixture = { + 'metadata': { + 'key6': 'value6', + 'key4': 'value4', + }, + } + output = serializer.serialize(fixture, 'update_all') + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + <metadata xmlns="http://docs.openstack.org/compute/api/v1.1"> + <meta key="key6"> + value6 + </meta> + <meta key="key4"> + value4 + </meta> + </metadata> + """.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_update_item(self): serializer = openstack.image_metadata.ImageMetadataXMLSerializer() fixture = { 'meta': { @@ -323,7 +420,7 @@ class ImageMetadataXMLSerializationTest(test.TestCase): self.assertEqual(expected.toxml(), actual.toxml()) - def test_create_xml(self): + def test_create(self): serializer = openstack.image_metadata.ImageMetadataXMLSerializer() fixture = { 'metadata': { @@ -350,3 +447,8 @@ class ImageMetadataXMLSerializationTest(test.TestCase): """.replace(" ", "")) self.assertEqual(expected.toxml(), actual.toxml()) + + def test_delete(self): + serializer = openstack.image_metadata.ImageMetadataXMLSerializer() + output = serializer.serialize(None, 'delete') + self.assertEqual(output, '') diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 17f2fb755..87a695dde 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -538,7 +538,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): # because the element hasn't changed definition expected = minidom.parseString(""" <itemNotFound code="404" - xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"> + xmlns="http://docs.openstack.org/compute/api/v1.1"> <message> Image not found. </message> diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index fa7fbab20..e6895086a 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -30,8 +30,8 @@ from nova import flags from nova import test from nova import utils import nova.api.openstack -from nova.api.openstack import servers from nova.api.openstack import create_instance_helper +from nova.api.openstack import servers from nova.api.openstack import wsgi import nova.compute.api from nova.compute import instance_types @@ -1206,6 +1206,18 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_create_instance_no_server_entity(self): + self._setup_for_create_instance() + + body = {} + + req = webob.Request.blank('/v1.0/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 422) + def test_create_instance_whitespace_name(self): self._setup_for_create_instance() @@ -2507,6 +2519,62 @@ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==""", "http://localhost:8774/v1.1/images/1") +class TextAddressesXMLSerialization(test.TestCase): + + serializer = nova.api.openstack.ips.IPXMLSerializer() + + def test_show(self): + fixture = { + 'network_2': [ + {'addr': '192.168.0.1', 'version': 4}, + {'addr': 'fe80::beef', 'version': 6}, + ], + } + output = self.serializer.serialize(fixture, 'show') + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + <network xmlns="http://docs.openstack.org/compute/api/v1.1" + id="network_2"> + <ip version="4" addr="192.168.0.1"/> + <ip version="6" addr="fe80::beef"/> + </network> + """.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_index(self): + fixture = { + 'addresses': { + 'network_1': [ + {'addr': '192.168.0.3', 'version': 4}, + {'addr': '192.168.0.5', 'version': 4}, + ], + 'network_2': [ + {'addr': '192.168.0.1', 'version': 4}, + {'addr': 'fe80::beef', 'version': 6}, + ], + }, + } + output = self.serializer.serialize(fixture, 'index') + actual = minidom.parseString(output.replace(" ", "")) + + expected = minidom.parseString(""" + <addresses xmlns="http://docs.openstack.org/compute/api/v1.1"> + <network id="network_2"> + <ip version="4" addr="192.168.0.1"/> + <ip version="6" addr="fe80::beef"/> + </network> + <network id="network_1"> + <ip version="4" addr="192.168.0.3"/> + <ip version="4" addr="192.168.0.5"/> + </network> + </addresses> + """.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) + + class TestServerInstanceCreation(test.TestCase): def setUp(self): diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py index 5bdda7c7e..6dea78d17 100644 --- a/nova/tests/api/openstack/test_wsgi.py +++ b/nova/tests/api/openstack/test_wsgi.py @@ -256,6 +256,13 @@ class ResponseSerializerTest(test.TestCase): self.assertEqual(response.body, 'pew_json') self.assertEqual(response.status_int, 404) + def test_serialize_response_None(self): + response = self.serializer.serialize(None, 'application/json') + print response + self.assertEqual(response.headers['Content-Type'], 'application/json') + self.assertEqual(response.body, '') + self.assertEqual(response.status_int, 404) + def test_serialize_response_dict_to_unknown_content_type(self): self.assertRaises(exception.InvalidContentType, self.serializer.serialize, diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 8cdc73a66..136082cc1 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -269,25 +269,64 @@ class CloudTestCase(test.TestCase): delete = self.cloud.delete_security_group self.assertRaises(exception.ApiError, delete, self.context) - def test_authorize_revoke_security_group_ingress(self): + def test_authorize_security_group_ingress(self): kwargs = {'project_id': self.context.project_id, 'name': 'test'} sec = db.security_group_create(self.context, kwargs) authz = self.cloud.authorize_security_group_ingress kwargs = {'to_port': '999', 'from_port': '999', 'ip_protocol': 'tcp'} - authz(self.context, group_name=sec['name'], **kwargs) + self.assertTrue(authz(self.context, group_name=sec['name'], **kwargs)) + + def test_authorize_security_group_ingress_ip_permissions_ip_ranges(self): + kwargs = {'project_id': self.context.project_id, 'name': 'test'} + sec = db.security_group_create(self.context, kwargs) + authz = self.cloud.authorize_security_group_ingress + kwargs = {'ip_permissions': [{'to_port': 81, 'from_port': 81, + 'ip_ranges': + {'1': {'cidr_ip': u'0.0.0.0/0'}, + '2': {'cidr_ip': u'10.10.10.10/32'}}, + 'ip_protocol': u'tcp'}]} + self.assertTrue(authz(self.context, group_name=sec['name'], **kwargs)) + + def test_authorize_security_group_ingress_ip_permissions_groups(self): + kwargs = {'project_id': self.context.project_id, 'name': 'test'} + sec = db.security_group_create(self.context, kwargs) + authz = self.cloud.authorize_security_group_ingress + kwargs = {'ip_permissions': [{'to_port': 81, 'from_port': 81, + 'ip_ranges':{'1': {'cidr_ip': u'0.0.0.0/0'}, + '2': {'cidr_ip': u'10.10.10.10/32'}}, + 'groups': {'1': {'user_id': u'someuser', + 'group_name': u'somegroup1'}, + '2': {'user_id': u'someuser', + 'group_name': u'othergroup2'}}, + 'ip_protocol': u'tcp'}]} + self.assertTrue(authz(self.context, group_name=sec['name'], **kwargs)) + + def test_revoke_security_group_ingress(self): + kwargs = {'project_id': self.context.project_id, 'name': 'test'} + sec = db.security_group_create(self.context, kwargs) + authz = self.cloud.authorize_security_group_ingress + kwargs = {'to_port': '999', 'from_port': '999', 'ip_protocol': 'tcp'} + authz(self.context, group_id=sec['id'], **kwargs) revoke = self.cloud.revoke_security_group_ingress self.assertTrue(revoke(self.context, group_name=sec['name'], **kwargs)) - def test_authorize_revoke_security_group_ingress_by_id(self): - sec = db.security_group_create(self.context, - {'project_id': self.context.project_id, - 'name': 'test'}) + def test_revoke_security_group_ingress_by_id(self): + kwargs = {'project_id': self.context.project_id, 'name': 'test'} + sec = db.security_group_create(self.context, kwargs) authz = self.cloud.authorize_security_group_ingress kwargs = {'to_port': '999', 'from_port': '999', 'ip_protocol': 'tcp'} authz(self.context, group_id=sec['id'], **kwargs) revoke = self.cloud.revoke_security_group_ingress self.assertTrue(revoke(self.context, group_id=sec['id'], **kwargs)) + def test_authorize_security_group_ingress_by_id(self): + sec = db.security_group_create(self.context, + {'project_id': self.context.project_id, + 'name': 'test'}) + authz = self.cloud.authorize_security_group_ingress + kwargs = {'to_port': '999', 'from_port': '999', 'ip_protocol': 'tcp'} + self.assertTrue(authz(self.context, group_id=sec['id'], **kwargs)) + def test_authorize_security_group_ingress_missing_protocol_params(self): sec = db.security_group_create(self.context, {'project_id': self.context.project_id, @@ -908,6 +947,21 @@ class CloudTestCase(test.TestCase): self._wait_for_running(ec2_instance_id) return ec2_instance_id + def test_rescue_unrescue_instance(self): + instance_id = self._run_instance( + image_id='ami-1', + instance_type=FLAGS.default_instance_type, + max_count=1) + self.cloud.rescue_instance(context=self.context, + instance_id=instance_id) + # NOTE(vish): This currently does no validation, it simply makes sure + # that the code path doesn't throw an exception. + self.cloud.unrescue_instance(context=self.context, + instance_id=instance_id) + # TODO(soren): We need this until we can stop polling in the rpc code + # for unit tests. + self.cloud.terminate_instances(self.context, [instance_id]) + def test_console_output(self): instance_id = self._run_instance( image_id='ami-1', diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 5d59b628a..2a8f33dd3 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -624,7 +624,6 @@ class ComputeTestCase(test.TestCase): self._setup_other_managers() dbmock = self.mox.CreateMock(db) volmock = self.mox.CreateMock(self.volume_manager) - netmock = self.mox.CreateMock(self.network_manager) drivermock = self.mox.CreateMock(self.compute_driver) dbmock.instance_get(c, i_ref['id']).AndReturn(i_ref) @@ -632,12 +631,11 @@ class ComputeTestCase(test.TestCase): for i in range(len(i_ref['volumes'])): vid = i_ref['volumes'][i]['id'] volmock.setup_compute_volume(c, vid).InAnyOrder('g1') - netmock.setup_compute_network(c, i_ref['id']) + drivermock.plug_vifs(i_ref, []) drivermock.ensure_filtering_rules_for_instance(i_ref) self.compute.db = dbmock self.compute.volume_manager = volmock - self.compute.network_manager = netmock self.compute.driver = drivermock self.mox.ReplayAll() @@ -652,18 +650,16 @@ class ComputeTestCase(test.TestCase): self._setup_other_managers() dbmock = self.mox.CreateMock(db) - netmock = self.mox.CreateMock(self.network_manager) drivermock = self.mox.CreateMock(self.compute_driver) dbmock.instance_get(c, i_ref['id']).AndReturn(i_ref) dbmock.instance_get_fixed_addresses(c, i_ref['id']).AndReturn('dummy') self.mox.StubOutWithMock(compute_manager.LOG, 'info') compute_manager.LOG.info(_("%s has no volume."), i_ref['hostname']) - netmock.setup_compute_network(c, i_ref['id']) + drivermock.plug_vifs(i_ref, []) drivermock.ensure_filtering_rules_for_instance(i_ref) self.compute.db = dbmock - self.compute.network_manager = netmock self.compute.driver = drivermock self.mox.ReplayAll() @@ -684,18 +680,20 @@ class ComputeTestCase(test.TestCase): dbmock = self.mox.CreateMock(db) netmock = self.mox.CreateMock(self.network_manager) volmock = self.mox.CreateMock(self.volume_manager) + drivermock = self.mox.CreateMock(self.compute_driver) dbmock.instance_get(c, i_ref['id']).AndReturn(i_ref) dbmock.instance_get_fixed_addresses(c, i_ref['id']).AndReturn('dummy') for i in range(len(i_ref['volumes'])): volmock.setup_compute_volume(c, i_ref['volumes'][i]['id']) for i in range(FLAGS.live_migration_retry_count): - netmock.setup_compute_network(c, i_ref['id']).\ + drivermock.plug_vifs(i_ref, []).\ AndRaise(exception.ProcessExecutionError()) self.compute.db = dbmock self.compute.network_manager = netmock self.compute.volume_manager = volmock + self.compute.driver = drivermock self.mox.ReplayAll() self.assertRaises(exception.ProcessExecutionError, @@ -830,7 +828,7 @@ class ComputeTestCase(test.TestCase): for v in i_ref['volumes']: self.compute.volume_manager.remove_compute_volume(c, v['id']) self.mox.StubOutWithMock(self.compute.driver, 'unfilter_instance') - self.compute.driver.unfilter_instance(i_ref) + self.compute.driver.unfilter_instance(i_ref, []) # executing self.mox.ReplayAll() diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 6e2ec7ed6..ad0931a89 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -54,9 +54,13 @@ def _create_network_info(count=1, ipv6=None): fake_ip = '0.0.0.0/0' fake_ip_2 = '0.0.0.1/0' fake_ip_3 = '0.0.0.1/0' + fake_vlan = 100 + fake_bridge_interface = 'eth0' network = {'bridge': fake, 'cidr': fake_ip, - 'cidr_v6': fake_ip} + 'cidr_v6': fake_ip, + 'vlan': fake_vlan, + 'bridge_interface': fake_bridge_interface} mapping = {'mac': fake, 'dhcp_server': fake, 'gateway': fake, @@ -219,9 +223,19 @@ class LibvirtConnTestCase(test.TestCase): def setattr(self, key, val): self.__setattr__(key, val) + # A fake VIF driver + class FakeVIFDriver(object): + + def __init__(self, **kwargs): + pass + + def setattr(self, key, val): + self.__setattr__(key, val) + # Creating mocks fake = FakeLibvirtConnection() fakeip = FakeIptablesFirewallDriver + fakevif = FakeVIFDriver() # Customizing above fake if necessary for key, val in kwargs.items(): fake.__setattr__(key, val) @@ -229,6 +243,8 @@ class LibvirtConnTestCase(test.TestCase): # Inevitable mocks for connection.LibvirtConnection self.mox.StubOutWithMock(connection.utils, 'import_class') connection.utils.import_class(mox.IgnoreArg()).AndReturn(fakeip) + self.mox.StubOutWithMock(connection.utils, 'import_object') + connection.utils.import_object(mox.IgnoreArg()).AndReturn(fakevif) self.mox.StubOutWithMock(connection.LibvirtConnection, '_conn') connection.LibvirtConnection._conn = fake @@ -280,22 +296,6 @@ class LibvirtConnTestCase(test.TestCase): _create_network_info(2)) self.assertTrue(len(result['nics']) == 2) - def test_get_nic_for_xml_v4(self): - conn = connection.LibvirtConnection(True) - network, mapping = _create_network_info()[0] - self.flags(use_ipv6=False) - params = conn._get_nic_for_xml(network, mapping)['extra_params'] - self.assertTrue(params.find('PROJNETV6') == -1) - self.assertTrue(params.find('PROJMASKV6') == -1) - - def test_get_nic_for_xml_v6(self): - conn = connection.LibvirtConnection(True) - network, mapping = _create_network_info()[0] - self.flags(use_ipv6=True) - params = conn._get_nic_for_xml(network, mapping)['extra_params'] - self.assertTrue(params.find('PROJNETV6') > -1) - self.assertTrue(params.find('PROJMASKV6') > -1) - @test.skip_test("skipping libvirt tests depends on get_network_info shim") def test_xml_and_uri_no_ramdisk_no_kernel(self): instance_data = dict(self.test_instance) @@ -722,6 +722,9 @@ class LibvirtConnTestCase(test.TestCase): return vdmock self.create_fake_libvirt_mock(lookupByName=fake_lookup) + self.mox.StubOutWithMock(self.compute, "recover_live_migration") + self.compute.recover_live_migration(self.context, instance_ref, + dest='dest') # Start test self.mox.ReplayAll() diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index fbe7d769e..28f50d328 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -149,7 +149,10 @@ class FlatNetworkTestCase(test.TestCase): 'cidr': '192.168.%s.0/24' % i, 'cidr_v6': '2001:db%s::/64' % i8, 'id': i, - 'injected': 'DONTCARE'} + 'multi_host': False, + 'injected': 'DONTCARE', + 'bridge_interface': 'fake_fa%s' % i, + 'vlan': None} self.assertDictMatch(nw[0], check) @@ -162,7 +165,9 @@ class FlatNetworkTestCase(test.TestCase): 'ips': 'DONTCARE', 'label': 'test%s' % i, 'mac': 'DE:AD:BE:EF:00:0%s' % i, - 'rxtx_cap': 'DONTCARE'} + 'rxtx_cap': 'DONTCARE', + 'should_create_vlan': False, + 'should_create_bridge': False} self.assertDictMatch(nw[1], check) check = [{'enabled': 'DONTCARE', diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 4cb7447d3..199a8bc52 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -647,7 +647,7 @@ class XenAPIVMTestCase(test.TestCase): self.flags(xenapi_inject_image=False) instance = self._create_instance() conn = xenapi_conn.get_connection(False) - conn.rescue(instance, None) + conn.rescue(instance, None, []) def test_unrescue(self): instance = self._create_instance() diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 178279d31..34dc5f544 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -61,11 +61,11 @@ class ComputeDriver(object): """Return a list of InstanceInfo for all registered VMs""" raise NotImplementedError() - def spawn(self, instance, network_info=None, block_device_mapping=None): + def spawn(self, instance, network_info, block_device_mapping=None): """Launch a VM for the specified instance""" raise NotImplementedError() - def destroy(self, instance, cleanup=True): + def destroy(self, instance, network_info, cleanup=True): """Destroy (shutdown and delete) the specified instance. The given parameter is an instance of nova.compute.service.Instance, @@ -81,7 +81,7 @@ class ComputeDriver(object): """ raise NotImplementedError() - def reboot(self, instance): + def reboot(self, instance, network_info): """Reboot specified VM""" raise NotImplementedError() @@ -146,11 +146,11 @@ class ComputeDriver(object): """resume the specified instance""" raise NotImplementedError() - def rescue(self, instance, callback): + def rescue(self, instance, callback, network_info): """Rescue the specified instance""" raise NotImplementedError() - def unrescue(self, instance, callback): + def unrescue(self, instance, callback, network_info): """Unrescue the specified instance""" raise NotImplementedError() @@ -224,7 +224,7 @@ class ComputeDriver(object): """ raise NotImplementedError() - def unfilter_instance(self, instance): + def unfilter_instance(self, instance, network_info): """Stop filtering instance""" raise NotImplementedError() @@ -253,3 +253,7 @@ class ComputeDriver(object): def set_host_enabled(self, host, enabled): """Sets the specified host's ability to accept new instances.""" raise NotImplementedError() + + def plug_vifs(self, instance, network_info): + """Plugs in VIFs to networks.""" + raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index ea0a59f21..26bc421c0 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -167,7 +167,7 @@ class FakeConnection(driver.ComputeDriver): """ pass - def reboot(self, instance): + def reboot(self, instance, network_info): """ Reboot the specified instance. @@ -240,13 +240,13 @@ class FakeConnection(driver.ComputeDriver): """ pass - def rescue(self, instance): + def rescue(self, instance, callback, network_info): """ Rescue the specified instance. """ pass - def unrescue(self, instance): + def unrescue(self, instance, callback, network_info): """ Unrescue the specified instance. """ @@ -293,7 +293,7 @@ class FakeConnection(driver.ComputeDriver): """ pass - def destroy(self, instance): + def destroy(self, instance, network_info): key = instance.name if key in self.instances: del self.instances[key] @@ -499,7 +499,7 @@ class FakeConnection(driver.ComputeDriver): """This method is supported only by libvirt.""" return - def unfilter_instance(self, instance_ref): + def unfilter_instance(self, instance_ref, network_info=None): """This method is supported only by libvirt.""" raise NotImplementedError('This method is supported only by libvirt.') diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 5c1dc772d..81c7dea58 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -139,7 +139,7 @@ class HyperVConnection(driver.ComputeDriver): return instance_infos - def spawn(self, instance, network_info=None, block_device_mapping=None): + def spawn(self, instance, network_info, block_device_mapping=None): """ Create a new VM and start it.""" vm = self._lookup(instance.name) if vm is not None: @@ -368,14 +368,14 @@ class HyperVConnection(driver.ComputeDriver): wmi_obj.Properties_.Item(prop).Value return newinst - def reboot(self, instance): + def reboot(self, instance, network_info): """Reboot the specified instance.""" vm = self._lookup(instance.name) if vm is None: raise exception.InstanceNotFound(instance_id=instance.id) self._set_vm_state(instance.name, 'Reboot') - def destroy(self, instance): + def destroy(self, instance, network_info): """Destroy the VM. Also destroy the associated VHD disk files""" LOG.debug(_("Got request to destroy vm %s"), instance.name) vm = self._lookup(instance.name) diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index e1a683da8..a75636390 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -82,9 +82,13 @@ </disk> #end if #for $vol in $volumes - <disk type='block'> + <disk type='${vol.type}'> <driver type='raw'/> + #if $vol.type == 'network' + <source protocol='${vol.protocol}' name='${vol.name}'/> + #else <source dev='${vol.device_path}'/> + #end if <target dev='${vol.mount_device}' bus='${disk_bus}'/> </disk> #end for @@ -92,6 +96,22 @@ #end if #for $nic in $nics + #if $vif_type == 'ethernet' + <interface type='ethernet'> + <target dev='${nic.name}' /> + <mac address='${nic.mac_address}' /> + <script path='${nic.script}' /> + </interface> + #else if $vif_type == '802.1Qbh' + <interface type='direct'> + <mac address='${nic.mac_address}'/> + <source dev='${nic.device_name}' mode='private'/> + <virtualport type='802.1Qbh'> + <parameters profileid='${nic.profile_name}'/> + </virtualport> + <model type='virtio'/> + </interface> + #else <interface type='bridge'> <source bridge='${nic.bridge_name}'/> <mac address='${nic.mac_address}'/> @@ -107,6 +127,8 @@ #end if </filterref> </interface> + #end if + #end for <!-- The order is significant here. File must be defined first --> <serial type="file"> diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 342dea98f..96f9c41f9 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -123,6 +123,11 @@ flags.DEFINE_string('qemu_img', 'qemu-img', 'binary to use for qemu-img commands') flags.DEFINE_bool('start_guests_on_host_boot', False, 'Whether to restart guests when the host reboots') +flags.DEFINE_string('libvirt_vif_type', 'bridge', + 'Type of VIF to create.') +flags.DEFINE_string('libvirt_vif_driver', + 'nova.virt.libvirt.vif.LibvirtBridgeDriver', + 'The libvirt VIF driver to configure the VIFs.') def get_connection(read_only): @@ -165,6 +170,7 @@ class LibvirtConnection(driver.ComputeDriver): fw_class = utils.import_class(FLAGS.firewall_driver) self.firewall_driver = fw_class(get_connection=self._get_connection) + self.vif_driver = utils.import_object(FLAGS.libvirt_vif_driver) def init_host(self, host): # Adopt existing VM's running here @@ -256,7 +262,12 @@ class LibvirtConnection(driver.ComputeDriver): infos.append(info) return infos - def destroy(self, instance, cleanup=True): + def plug_vifs(self, instance, network_info): + """Plugin VIFs into networks.""" + for (network, mapping) in network_info: + self.vif_driver.plug(instance, network, mapping) + + def destroy(self, instance, network_info, cleanup=True): instance_name = instance['name'] try: @@ -300,6 +311,9 @@ class LibvirtConnection(driver.ComputeDriver): locals()) raise + for (network, mapping) in network_info: + self.vif_driver.unplug(instance, network, mapping) + def _wait_for_destroy(): """Called at an interval until the VM is gone.""" instance_name = instance['name'] @@ -314,7 +328,8 @@ class LibvirtConnection(driver.ComputeDriver): timer = utils.LoopingCall(_wait_for_destroy) timer.start(interval=0.5, now=True) - self.firewall_driver.unfilter_instance(instance) + self.firewall_driver.unfilter_instance(instance, + network_info=network_info) if cleanup: self._cleanup(instance) @@ -335,21 +350,20 @@ class LibvirtConnection(driver.ComputeDriver): def attach_volume(self, instance_name, device_path, mountpoint): virt_dom = self._lookup_by_name(instance_name) mount_device = mountpoint.rpartition("/")[2] - if device_path.startswith('/dev/'): + (type, protocol, name) = \ + self._get_volume_device_info(vol['device_path']) + if type == 'block': xml = """<disk type='block'> <driver name='qemu' type='raw'/> <source dev='%s'/> <target dev='%s' bus='virtio'/> </disk>""" % (device_path, mount_device) - elif ':' in device_path: - (protocol, name) = device_path.split(':') + elif type == 'network': xml = """<disk type='network'> <driver name='qemu' type='raw'/> <source protocol='%s' name='%s'/> <target dev='%s' bus='virtio'/> - </disk>""" % (protocol, - name, - mount_device) + </disk>""" % (protocol, name, mount_device) else: raise exception.InvalidDevicePath(path=device_path) @@ -461,7 +475,7 @@ class LibvirtConnection(driver.ComputeDriver): shutil.rmtree(temp_dir) @exception.wrap_exception() - def reboot(self, instance): + def reboot(self, instance, network_info): """Reboot a virtual machine, given an instance reference. This method actually destroys and re-creates the domain to ensure the @@ -476,7 +490,8 @@ class LibvirtConnection(driver.ComputeDriver): # NOTE(itoumsn): self.shutdown() and wait instead of self.destroy() is # better because we cannot ensure flushing dirty buffers # in the guest OS. But, in case of KVM, shutdown() does not work... - self.destroy(instance, False) + self.destroy(instance, network_info, cleanup=False) + self.plug_vifs(instance, network_info) self.firewall_driver.setup_basic_filtering(instance) self.firewall_driver.prepare_instance_filter(instance) self._create_new_domain(xml) @@ -526,7 +541,7 @@ class LibvirtConnection(driver.ComputeDriver): dom.create() @exception.wrap_exception() - def rescue(self, instance): + def rescue(self, instance, callback, network_info): """Loads a VM using rescue images. A rescue is normally performed when something goes wrong with the @@ -535,7 +550,7 @@ class LibvirtConnection(driver.ComputeDriver): data recovery. """ - self.destroy(instance, False) + self.destroy(instance, network_info, cleanup=False) xml = self.to_xml(instance, rescue=True) rescue_images = {'image_id': FLAGS.rescue_image_id, @@ -564,14 +579,14 @@ class LibvirtConnection(driver.ComputeDriver): return timer.start(interval=0.5, now=True) @exception.wrap_exception() - def unrescue(self, instance): + def unrescue(self, instance, network_info): """Reboot the VM which is being rescued back into primary images. Because reboot destroys and re-creates instances, unresue should simply call reboot. """ - self.reboot(instance) + self.reboot(instance, network_info) @exception.wrap_exception() def poll_rescued_instances(self, timeout): @@ -580,7 +595,7 @@ class LibvirtConnection(driver.ComputeDriver): # NOTE(ilyaalekseyev): Implementation like in multinics # for xenapi(tr3buchet) @exception.wrap_exception() - def spawn(self, instance, network_info=None, block_device_mapping=None): + def spawn(self, instance, network_info, block_device_mapping=None): xml = self.to_xml(instance, False, network_info=network_info, block_device_mapping=block_device_mapping) block_device_mapping = block_device_mapping or [] @@ -929,39 +944,6 @@ class LibvirtConnection(driver.ComputeDriver): if FLAGS.libvirt_type == 'uml': utils.execute('sudo', 'chown', 'root', basepath('disk')) - def _get_nic_for_xml(self, network, mapping): - # Assume that the gateway also acts as the dhcp server. - gateway6 = mapping.get('gateway6') - mac_id = mapping['mac'].replace(':', '') - - if FLAGS.allow_project_net_traffic: - template = "<parameter name=\"%s\"value=\"%s\" />\n" - net, mask = netutils.get_net_and_mask(network['cidr']) - values = [("PROJNET", net), ("PROJMASK", mask)] - if FLAGS.use_ipv6: - net_v6, prefixlen_v6 = netutils.get_net_and_prefixlen( - network['cidr_v6']) - values.extend([("PROJNETV6", net_v6), - ("PROJMASKV6", prefixlen_v6)]) - - extra_params = "".join([template % value for value in values]) - else: - extra_params = "\n" - - result = { - 'id': mac_id, - 'bridge_name': network['bridge'], - 'mac_address': mapping['mac'], - 'ip_address': mapping['ips'][0]['ip'], - 'dhcp_server': mapping['dhcp_server'], - 'extra_params': extra_params, - } - - if gateway6: - result['gateway6'] = gateway6 + "/128" - - return result - root_mount_device = 'vda' # FIXME for now. it's hard coded. local_mount_device = 'vdb' # FIXME for now. it's hard coded. @@ -973,6 +955,16 @@ class LibvirtConnection(driver.ComputeDriver): return True return False + @exception.wrap_exception + def _get_volume_device_info(self, device_path): + if device_path.startswith('/dev/'): + return ('block', None, None) + elif ':' in device_path: + (protocol, name) = device_path.split(':') + return ('network', protocol, name) + else: + raise exception.InvalidDevicePath(path=device_path) + def _prepare_xml_info(self, instance, rescue=False, network_info=None, block_device_mapping=None): block_device_mapping = block_device_mapping or [] @@ -983,7 +975,7 @@ class LibvirtConnection(driver.ComputeDriver): nics = [] for (network, mapping) in network_info: - nics.append(self._get_nic_for_xml(network, mapping)) + nics.append(self.vif_driver.plug(instance, network, mapping)) # FIXME(vish): stick this in db inst_type_id = instance['instance_type_id'] inst_type = instance_types.get_instance_type(inst_type_id) @@ -995,6 +987,9 @@ class LibvirtConnection(driver.ComputeDriver): for vol in block_device_mapping: vol['mount_device'] = _strip_dev(vol['mount_device']) + (vol['type'], vol['protocol'], vol['name']) = \ + self._get_volume_device_info(vol['device_path']) + ebs_root = self._volume_in_mapping(self.root_mount_device, block_device_mapping) if self._volume_in_mapping(self.local_mount_device, @@ -1012,6 +1007,7 @@ class LibvirtConnection(driver.ComputeDriver): 'rescue': rescue, 'local': local_gb, 'driver_type': driver_type, + 'vif_type': FLAGS.libvirt_vif_type, 'nics': nics, 'ebs_root': ebs_root, 'volumes': block_device_mapping} @@ -1581,9 +1577,10 @@ class LibvirtConnection(driver.ComputeDriver): timer.f = wait_for_live_migration timer.start(interval=0.5, now=True) - def unfilter_instance(self, instance_ref): + def unfilter_instance(self, instance_ref, network_info): """See comments of same method in firewall_driver.""" - self.firewall_driver.unfilter_instance(instance_ref) + self.firewall_driver.unfilter_instance(instance_ref, + network_info=network_info) def update_host_status(self): """See xenapi_conn.py implementation.""" diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py index 379197398..9ce57b6c9 100644 --- a/nova/virt/libvirt/firewall.py +++ b/nova/virt/libvirt/firewall.py @@ -46,7 +46,7 @@ class FirewallDriver(object): At this point, the instance isn't running yet.""" raise NotImplementedError() - def unfilter_instance(self, instance): + def unfilter_instance(self, instance, network_info=None): """Stop filtering instance""" raise NotImplementedError() @@ -300,9 +300,10 @@ class NWFilterFirewall(FirewallDriver): # execute in a native thread and block current greenthread until done tpool.execute(self._conn.nwfilterDefineXML, xml) - def unfilter_instance(self, instance): + def unfilter_instance(self, instance, network_info=None): """Clear out the nwfilter rules.""" - network_info = netutils.get_network_info(instance) + if not network_info: + network_info = netutils.get_network_info(instance) instance_name = instance.name for (network, mapping) in network_info: nic_id = mapping['mac'].replace(':', '') @@ -542,11 +543,11 @@ class IptablesFirewallDriver(FirewallDriver): """No-op. Everything is done in prepare_instance_filter""" pass - def unfilter_instance(self, instance): + def unfilter_instance(self, instance, network_info=None): if self.instances.pop(instance['id'], None): self.remove_filters_for_instance(instance) self.iptables.apply() - self.nwfilter.unfilter_instance(instance) + self.nwfilter.unfilter_instance(instance, network_info) else: LOG.info(_('Attempted to unfilter instance %s which is not ' 'filtered'), instance['id']) diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py new file mode 100644 index 000000000..24d45d1a7 --- /dev/null +++ b/nova/virt/libvirt/vif.py @@ -0,0 +1,134 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (C) 2011 Midokura KK +# Copyright (C) 2011 Nicira, Inc +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""VIF drivers for libvirt.""" + +from nova import flags +from nova import log as logging +from nova.network import linux_net +from nova.virt.libvirt import netutils +from nova import utils +from nova.virt.vif import VIFDriver + +LOG = logging.getLogger('nova.virt.libvirt.vif') + +FLAGS = flags.FLAGS + +flags.DEFINE_string('libvirt_ovs_bridge', 'br-int', + 'Name of Integration Bridge used by Open vSwitch') + + +class LibvirtBridgeDriver(VIFDriver): + """VIF driver for Linux bridge.""" + + def _get_configurations(self, network, mapping): + """Get a dictionary of VIF configurations for bridge type.""" + # Assume that the gateway also acts as the dhcp server. + gateway6 = mapping.get('gateway6') + mac_id = mapping['mac'].replace(':', '') + + if FLAGS.allow_project_net_traffic: + template = "<parameter name=\"%s\"value=\"%s\" />\n" + net, mask = netutils.get_net_and_mask(network['cidr']) + values = [("PROJNET", net), ("PROJMASK", mask)] + if FLAGS.use_ipv6: + net_v6, prefixlen_v6 = netutils.get_net_and_prefixlen( + network['cidr_v6']) + values.extend([("PROJNETV6", net_v6), + ("PROJMASKV6", prefixlen_v6)]) + + extra_params = "".join([template % value for value in values]) + else: + extra_params = "\n" + + result = { + 'id': mac_id, + 'bridge_name': network['bridge'], + 'mac_address': mapping['mac'], + 'ip_address': mapping['ips'][0]['ip'], + 'dhcp_server': mapping['dhcp_server'], + 'extra_params': extra_params, + } + + if gateway6: + result['gateway6'] = gateway6 + "/128" + + return result + + def plug(self, instance, network, mapping): + """Ensure that the bridge exists, and add VIF to it.""" + if (not network.get('multi_host') and + mapping.get('should_create_bridge')): + if mapping.get('should_create_vlan'): + LOG.debug(_('Ensuring vlan %(vlan)s and bridge %(bridge)s'), + {'vlan': network['vlan'], + 'bridge': network['bridge']}) + linux_net.ensure_vlan_bridge(network['vlan'], + network['bridge'], + network['bridge_interface']) + else: + LOG.debug(_("Ensuring bridge %s"), network['bridge']) + linux_net.ensure_bridge(network['bridge'], + network['bridge_interface']) + + return self._get_configurations(network, mapping) + + def unplug(self, instance, network, mapping): + """No manual unplugging required.""" + pass + + +class LibvirtOpenVswitchDriver(VIFDriver): + """VIF driver for Open vSwitch.""" + + def plug(self, instance, network, mapping): + vif_id = str(instance['id']) + "-" + str(network['id']) + dev = "tap-%s" % vif_id + iface_id = "nova-" + vif_id + if not linux_net._device_exists(dev): + utils.execute('sudo', 'ip', 'tuntap', 'add', dev, 'mode', 'tap') + utils.execute('sudo', 'ip', 'link', 'set', dev, 'up') + utils.execute('sudo', 'ovs-vsctl', '--', '--may-exist', 'add-port', + FLAGS.libvirt_ovs_bridge, dev, + '--', 'set', 'Interface', dev, + "external-ids:iface-id=%s" % iface_id, + '--', 'set', 'Interface', dev, + "external-ids:iface-status=active", + '--', 'set', 'Interface', dev, + "external-ids:attached-mac=%s" % mapping['mac']) + + result = { + 'script': '', + 'name': dev, + 'mac_address': mapping['mac']} + return result + + def unplug(self, instance, network, mapping): + """Unplug the VIF from the network by deleting the port from + the bridge.""" + vif_id = str(instance['id']) + "-" + str(network['id']) + dev = "tap-%s" % vif_id + try: + utils.execute('sudo', 'ovs-vsctl', 'del-port', + FLAGS.flat_network_bridge, dev) + utils.execute('sudo', 'ip', 'link', 'delete', dev) + except: + LOG.warning(_("Failed while unplugging vif of instance '%s'"), + instance['name']) + raise diff --git a/nova/virt/vif.py b/nova/virt/vif.py new file mode 100644 index 000000000..b78689957 --- /dev/null +++ b/nova/virt/vif.py @@ -0,0 +1,30 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (C) 2011 Midokura KK +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""VIF module common to all virt layers.""" + + +class VIFDriver(object): + """Abstract class that defines generic interfaces for all VIF drivers.""" + + def plug(self, instance, network, mapping): + """Plug VIF into network.""" + raise NotImplementedError() + + def unplug(self, instance, network, mapping): + """Unplug VIF from network.""" + raise NotImplementedError() diff --git a/nova/virt/vmwareapi/network_utils.py b/nova/virt/vmwareapi/network_utils.py index e77842535..08e3bf0b1 100644 --- a/nova/virt/vmwareapi/network_utils.py +++ b/nova/virt/vmwareapi/network_utils.py @@ -45,10 +45,30 @@ def get_network_with_the_name(session, network_name="vmnet0"): networks = session._call_method(vim_util,
"get_properties_for_a_collection_of_objects",
"Network", vm_networks, ["summary.name"])
- for network in networks:
- if network.propSet[0].val == network_name:
- return network.obj
- return None
+ network_obj = {}
+ for network in vm_networks:
+ # Get network properties
+ if network._type == 'DistributedVirtualPortgroup':
+ props = session._call_method(vim_util,
+ "get_dynamic_property", network,
+ "DistributedVirtualPortgroup", "config")
+ # NOTE(asomya): This only works on ESXi if the port binding is
+ # set to ephemeral
+ if props.name == network_name:
+ network_obj['type'] = 'DistributedVirtualPortgroup'
+ network_obj['dvpg'] = props.key
+ network_obj['dvsw'] = props.distributedVirtualSwitch.value
+ else:
+ props = session._call_method(vim_util,
+ "get_dynamic_property", network,
+ "Network", "summary.name")
+ if props == network_name:
+ network_obj['type'] = 'Network'
+ network_obj['name'] = network_name
+ if (len(network_obj) > 0):
+ return network_obj
+ else:
+ return None
def get_vswitch_for_vlan_interface(session, vlan_interface):
diff --git a/nova/virt/vmwareapi/vif.py b/nova/virt/vmwareapi/vif.py new file mode 100644 index 000000000..b3e43b209 --- /dev/null +++ b/nova/virt/vmwareapi/vif.py @@ -0,0 +1,95 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Citrix Systems, Inc. +# Copyright 2011 OpenStack LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""VIF drivers for VMWare.""" + +from nova import db +from nova import exception +from nova import flags +from nova import log as logging +from nova import utils +from nova.virt.vif import VIFDriver +from nova.virt.vmwareapi_conn import VMWareAPISession +from nova.virt.vmwareapi import network_utils + + +LOG = logging.getLogger("nova.virt.vmwareapi.vif") + +FLAGS = flags.FLAGS + + +class VMWareVlanBridgeDriver(VIFDriver): + """VIF Driver to setup bridge/VLAN networking using VMWare API.""" + + def plug(self, instance, network, mapping): + """Create a vlan and bridge unless they already exist.""" + vlan_num = network['vlan'] + bridge = network['bridge'] + bridge_interface = network['bridge_interface'] + + # Open vmwareapi session + host_ip = FLAGS.vmwareapi_host_ip + host_username = FLAGS.vmwareapi_host_username + host_password = FLAGS.vmwareapi_host_password + if not host_ip or host_username is None or host_password is None: + raise Exception(_('Must specify vmwareapi_host_ip, ' + 'vmwareapi_host_username ' + 'and vmwareapi_host_password to use ' + 'connection_type=vmwareapi')) + session = VMWareAPISession(host_ip, host_username, host_password, + FLAGS.vmwareapi_api_retry_count) + vlan_interface = bridge_interface + # Check if the vlan_interface physical network adapter exists on the + # host. + if not network_utils.check_if_vlan_interface_exists(session, + vlan_interface): + raise exception.NetworkAdapterNotFound(adapter=vlan_interface) + + # Get the vSwitch associated with the Physical Adapter + vswitch_associated = network_utils.get_vswitch_for_vlan_interface( + session, vlan_interface) + if vswitch_associated is None: + raise exception.SwicthNotFoundForNetworkAdapter( + adapter=vlan_interface) + # Check whether bridge already exists and retrieve the the ref of the + # network whose name_label is "bridge" + network_ref = network_utils.get_network_with_the_name(session, bridge) + if network_ref is None: + # Create a port group on the vSwitch associated with the + # vlan_interface corresponding physical network adapter on the ESX + # host. + network_utils.create_port_group(session, bridge, + vswitch_associated, vlan_num) + else: + # Get the vlan id and vswitch corresponding to the port group + pg_vlanid, pg_vswitch = \ + network_utils.get_vlanid_and_vswitch_for_portgroup(session, + bridge) + + # Check if the vswitch associated is proper + if pg_vswitch != vswitch_associated: + raise exception.InvalidVLANPortGroup( + bridge=bridge, expected=vswitch_associated, + actual=pg_vswitch) + + # Check if the vlan id is proper for the port group + if pg_vlanid != vlan_num: + raise exception.InvalidVLANTag(bridge=bridge, tag=vlan_num, + pgroup=pg_vlanid) + + def unplug(self, instance, network, mapping): + pass diff --git a/nova/virt/vmwareapi/vm_util.py b/nova/virt/vmwareapi/vm_util.py index 1638149f1..55578dd3c 100644 --- a/nova/virt/vmwareapi/vm_util.py +++ b/nova/virt/vmwareapi/vm_util.py @@ -40,7 +40,7 @@ def split_datastore_path(datastore_path): def get_vm_create_spec(client_factory, instance, data_store_name,
network_name="vmnet0",
- os_type="otherGuest"):
+ os_type="otherGuest", network_ref=None):
"""Builds the VM Create spec."""
config_spec = client_factory.create('ns0:VirtualMachineConfigSpec')
config_spec.name = instance.name
@@ -93,7 +93,8 @@ def create_controller_spec(client_factory, key): return virtual_device_config
-def create_network_spec(client_factory, network_name, mac_address):
+def create_network_spec(client_factory, network_name, mac_address,
+ network_ref=None):
"""
Builds a config spec for the addition of a new network
adapter to the VM.
@@ -105,9 +106,24 @@ def create_network_spec(client_factory, network_name, mac_address): # Get the recommended card type for the VM based on the guest OS of the VM
net_device = client_factory.create('ns0:VirtualPCNet32')
- backing = \
- client_factory.create('ns0:VirtualEthernetCardNetworkBackingInfo')
- backing.deviceName = network_name
+ # NOTE(asomya): Only works on ESXi if the portgroup binding is set to
+ # ephemeral. Invalid configuration if set to static and the NIC does
+ # not come up on boot if set to dynamic.
+ backing = None
+ if (network_ref['type'] == "DistributedVirtualPortgroup"):
+ backing_name = \
+ 'ns0:VirtualEthernetCardDistributedVirtualPortBackingInfo'
+ backing = \
+ client_factory.create(backing_name)
+ portgroup = \
+ client_factory.create('ns0:DistributedVirtualSwitchPortConnection')
+ portgroup.switchUuid = network_ref['dvsw']
+ portgroup.portgroupKey = network_ref['dvpg']
+ backing.port = portgroup
+ else:
+ backing = \
+ client_factory.create('ns0:VirtualEthernetCardNetworkBackingInfo')
+ backing.deviceName = network_name
connectable_spec = \
client_factory.create('ns0:VirtualDeviceConnectInfo')
@@ -278,9 +294,11 @@ def get_dummy_vm_create_spec(client_factory, name, data_store_name): return config_spec
-def get_machine_id_change_spec(client_factory, mac, ip_addr, netmask, gateway):
+def get_machine_id_change_spec(client_factory, mac, ip_addr, netmask,
+ gateway, broadcast, dns):
"""Builds the machine id change config spec."""
- machine_id_str = "%s;%s;%s;%s" % (mac, ip_addr, netmask, gateway)
+ machine_id_str = "%s;%s;%s;%s;%s;%s" % (mac, ip_addr, netmask,
+ gateway, broadcast, dns)
virtual_machine_config_spec = \
client_factory.create('ns0:VirtualMachineConfigSpec')
diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index 94d9e6226..7e7d2dac3 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -31,6 +31,7 @@ from nova import db from nova import exception
from nova import flags
from nova import log as logging
+from nova import utils
from nova.compute import power_state
from nova.virt.vmwareapi import vim_util
from nova.virt.vmwareapi import vm_util
@@ -38,6 +39,10 @@ from nova.virt.vmwareapi import vmware_images from nova.virt.vmwareapi import network_utils
FLAGS = flags.FLAGS
+flags.DEFINE_string('vmware_vif_driver',
+ 'nova.virt.vmwareapi.vif.VMWareVlanBridgeDriver',
+ 'The VMWare VIF driver to configure the VIFs.')
+
LOG = logging.getLogger("nova.virt.vmwareapi.vmops")
VMWARE_POWER_STATES = {
@@ -52,6 +57,7 @@ class VMWareVMOps(object): def __init__(self, session):
"""Initializer."""
self._session = session
+ self._vif_driver = utils.import_object(FLAGS.vmware_vif_driver)
def _wait_with_callback(self, instance_id, task, callback):
"""Waits for the task to finish and does a callback after."""
@@ -83,7 +89,7 @@ class VMWareVMOps(object): LOG.debug(_("Got total of %s instances") % str(len(lst_vm_names)))
return lst_vm_names
- def spawn(self, instance):
+ def spawn(self, instance, network_info):
"""
Creates a VM instance.
@@ -116,8 +122,10 @@ class VMWareVMOps(object): net_name)
if network_ref is None:
raise exception.NetworkNotFoundForBridge(bridge=net_name)
+ return network_ref
- _check_if_network_bridge_exists()
+ self.plug_vifs(instance, network_info)
+ network_obj = _check_if_network_bridge_exists()
def _get_datastore_ref():
"""Get the datastore list and choose the first local storage."""
@@ -175,8 +183,10 @@ class VMWareVMOps(object): vm_folder_mor, res_pool_mor = _get_vmfolder_and_res_pool_mors()
# Get the create vm config spec
- config_spec = vm_util.get_vm_create_spec(client_factory, instance,
- data_store_name, net_name, os_type)
+ config_spec = vm_util.get_vm_create_spec(
+ client_factory, instance,
+ data_store_name, net_name, os_type,
+ network_obj)
def _execute_create_vm():
"""Create VM on ESX host."""
@@ -472,11 +482,14 @@ class VMWareVMOps(object): _clean_temp_data()
- def reboot(self, instance):
+ def reboot(self, instance, network_info):
"""Reboot a VM instance."""
vm_ref = self._get_vm_ref_from_the_name(instance.name)
if vm_ref is None:
raise exception.InstanceNotFound(instance_id=instance.id)
+
+ self.plug_vifs(instance, network_info)
+
lst_properties = ["summary.guest.toolsStatus", "runtime.powerState",
"summary.guest.toolsRunningStatus"]
props = self._session._call_method(vim_util, "get_object_properties",
@@ -514,7 +527,7 @@ class VMWareVMOps(object): self._session._wait_for_task(instance.id, reset_task)
LOG.debug(_("Did hard reboot of VM %s") % instance.name)
- def destroy(self, instance):
+ def destroy(self, instance, network_info):
"""
Destroy a VM instance. Steps followed are:
1. Power off the VM, if it is in poweredOn state.
@@ -560,6 +573,8 @@ class VMWareVMOps(object): LOG.warn(_("In vmwareapi:vmops:destroy, got this exception"
" while un-registering the VM: %s") % str(excep))
+ self._unplug_vifs(instance, network_info)
+
# Delete the folder holding the VM related content on
# the datastore.
try:
@@ -718,13 +733,17 @@ class VMWareVMOps(object): net_mask = network["netmask"]
gateway = network["gateway"]
+ broadcast = network["broadcast"]
+ dns = network["dns"]
+
addresses = db.instance_get_fixed_addresses(admin_context,
instance['id'])
ip_addr = addresses[0] if addresses else None
machine_id_chanfge_spec = \
vm_util.get_machine_id_change_spec(client_factory, mac_address,
- ip_addr, net_mask, gateway)
+ ip_addr, net_mask, gateway,
+ broadcast, dns)
LOG.debug(_("Reconfiguring VM instance %(name)s to set the machine id "
"with ip - %(ip_addr)s") %
({'name': instance.name,
@@ -784,3 +803,13 @@ class VMWareVMOps(object): if vm.propSet[0].val == vm_name:
return vm.obj
return None
+
+ def plug_vifs(self, instance, network_info):
+ """Plug VIFs into networks."""
+ for (network, mapping) in network_info:
+ self._vif_driver.plug(instance, network, mapping)
+
+ def _unplug_vifs(self, instance, network_info):
+ """Unplug VIFs from networks."""
+ for (network, mapping) in network_info:
+ self._vif_driver.unplug(instance, network, mapping)
diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index d80e14931..ce57847b2 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -124,21 +124,21 @@ class VMWareESXConnection(driver.ComputeDriver): """List VM instances."""
return self._vmops.list_instances()
- def spawn(self, instance, network_info=None, block_device_mapping=None):
+ def spawn(self, instance, network_info, block_device_mapping=None):
"""Create VM instance."""
- self._vmops.spawn(instance)
+ self._vmops.spawn(instance, network_info)
def snapshot(self, instance, name):
"""Create snapshot from a running VM instance."""
self._vmops.snapshot(instance, name)
- def reboot(self, instance):
+ def reboot(self, instance, network_info):
"""Reboot VM instance."""
- self._vmops.reboot(instance)
+ self._vmops.reboot(instance, network_info)
- def destroy(self, instance):
+ def destroy(self, instance, network_info):
"""Destroy VM instance."""
- self._vmops.destroy(instance)
+ self._vmops.destroy(instance, network_info)
def pause(self, instance, callback):
"""Pause VM instance."""
@@ -194,6 +194,10 @@ class VMWareESXConnection(driver.ComputeDriver): """Sets the specified host's ability to accept new instances."""
pass
+ def plug_vifs(self, instance, network_info):
+ """Plugs in VIFs to networks."""
+ self._vmops.plug_vifs(instance, network_info)
+
class VMWareAPISession(object):
"""
diff --git a/nova/virt/xenapi/vif.py b/nova/virt/xenapi/vif.py new file mode 100644 index 000000000..527602243 --- /dev/null +++ b/nova/virt/xenapi/vif.py @@ -0,0 +1,140 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Citrix Systems, Inc. +# Copyright 2011 OpenStack LLC. +# Copyright (C) 2011 Nicira, Inc +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""VIF drivers for XenAPI.""" + +from nova import flags +from nova import log as logging +from nova.virt.vif import VIFDriver +from nova.virt.xenapi.network_utils import NetworkHelper + +FLAGS = flags.FLAGS +flags.DEFINE_string('xenapi_ovs_integration_bridge', 'xapi1', + 'Name of Integration Bridge used by Open vSwitch') + +LOG = logging.getLogger("nova.virt.xenapi.vif") + + +class XenAPIBridgeDriver(VIFDriver): + """VIF Driver for XenAPI that uses XenAPI to create Networks.""" + + def plug(self, xenapi_session, vm_ref, instance, device, network, + network_mapping): + if network_mapping.get('should_create_vlan'): + network_ref = self.ensure_vlan_bridge(xenapi_session, network) + else: + network_ref = NetworkHelper.find_network_with_bridge( + xenapi_session, network['bridge']) + rxtx_cap = network_mapping.pop('rxtx_cap') + vif_rec = {} + vif_rec['device'] = str(device) + vif_rec['network'] = network_ref + vif_rec['VM'] = vm_ref + vif_rec['MAC'] = network_mapping['mac'] + vif_rec['MTU'] = '1500' + vif_rec['other_config'] = {} + vif_rec['qos_algorithm_type'] = "ratelimit" if rxtx_cap else '' + vif_rec['qos_algorithm_params'] = \ + {"kbps": str(rxtx_cap * 1024)} if rxtx_cap else {} + return vif_rec + + def ensure_vlan_bridge(self, xenapi_session, network): + """Ensure that a VLAN bridge exists""" + + vlan_num = network['vlan'] + bridge = network['bridge'] + bridge_interface = network['bridge_interface'] + # Check whether bridge already exists + # Retrieve network whose name_label is "bridge" + network_ref = NetworkHelper.find_network_with_name_label( + xenapi_session, bridge) + if network_ref is None: + # If bridge does not exists + # 1 - create network + description = 'network for nova bridge %s' % bridge + network_rec = {'name_label': bridge, + 'name_description': description, + 'other_config': {}} + network_ref = xenapi_session.call_xenapi('network.create', + network_rec) + # 2 - find PIF for VLAN NOTE(salvatore-orlando): using double + # quotes inside single quotes as xapi filter only support + # tokens in double quotes + expr = 'field "device" = "%s" and \ + field "VLAN" = "-1"' % bridge_interface + pifs = xenapi_session.call_xenapi('PIF.get_all_records_where', + expr) + pif_ref = None + # Multiple PIF are ok: we are dealing with a pool + if len(pifs) == 0: + raise Exception(_('Found no PIF for device %s') % \ + bridge_interface) + for pif_ref in pifs.keys(): + xenapi_session.call_xenapi('VLAN.create', + pif_ref, + str(vlan_num), + network_ref) + else: + # Check VLAN tag is appropriate + network_rec = xenapi_session.call_xenapi('network.get_record', + network_ref) + # Retrieve PIFs from network + for pif_ref in network_rec['PIFs']: + # Retrieve VLAN from PIF + pif_rec = xenapi_session.call_xenapi('PIF.get_record', + pif_ref) + pif_vlan = int(pif_rec['VLAN']) + # Raise an exception if VLAN != vlan_num + if pif_vlan != vlan_num: + raise Exception(_( + "PIF %(pif_rec['uuid'])s for network " + "%(bridge)s has VLAN id %(pif_vlan)d. " + "Expected %(vlan_num)d") % locals()) + + return network_ref + + def unplug(self, instance, network, mapping): + pass + + +class XenAPIOpenVswitchDriver(VIFDriver): + """VIF driver for Open vSwitch with XenAPI.""" + + def plug(self, xenapi_session, vm_ref, instance, device, network, + network_mapping): + # with OVS model, always plug into an OVS integration bridge + # that is already created + network_ref = NetworkHelper.find_network_with_bridge(xenapi_session, + FLAGS.xenapi_ovs_integration_bridge) + vif_rec = {} + vif_rec['device'] = str(device) + vif_rec['network'] = network_ref + vif_rec['VM'] = vm_ref + vif_rec['MAC'] = network_mapping['mac'] + vif_rec['MTU'] = '1500' + vif_id = "nova-" + str(instance['id']) + "-" + str(network['id']) + vif_rec['qos_algorithm_type'] = "" + vif_rec['qos_algorithm_params'] = {} + # OVS on the hypervisor monitors this key and uses it to + # set the iface-id attribute + vif_rec['other_config'] = {"nicira-iface-id": vif_id} + return vif_rec + + def unplug(self, instance, network, mapping): + pass diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 71107aff4..62863c6d8 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -283,28 +283,6 @@ class VMHelper(HelperBase): raise StorageError(_('Unable to destroy VDI %s') % vdi_ref) @classmethod - def create_vif(cls, session, vm_ref, network_ref, mac_address, - dev, rxtx_cap=0): - """Create a VIF record. Returns a Deferred that gives the new - VIF reference.""" - vif_rec = {} - vif_rec['device'] = str(dev) - vif_rec['network'] = network_ref - vif_rec['VM'] = vm_ref - vif_rec['MAC'] = mac_address - vif_rec['MTU'] = '1500' - vif_rec['other_config'] = {} - vif_rec['qos_algorithm_type'] = "ratelimit" if rxtx_cap else '' - vif_rec['qos_algorithm_params'] = \ - {"kbps": str(rxtx_cap * 1024)} if rxtx_cap else {} - LOG.debug(_('Creating VIF for VM %(vm_ref)s,' - ' network %(network_ref)s.') % locals()) - vif_ref = session.call_xenapi('VIF.create', vif_rec) - LOG.debug(_('Created VIF %(vif_ref)s for VM %(vm_ref)s,' - ' network %(network_ref)s.') % locals()) - return vif_ref - - @classmethod def create_vdi(cls, session, sr_ref, name_label, virtual_size, read_only): """Create a VDI record and returns its reference.""" vdi_ref = session.get_xenapi().VDI.create( diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 7995576a6..0473abb97 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -52,6 +52,9 @@ FLAGS = flags.FLAGS flags.DEFINE_integer('windows_version_timeout', 300, 'number of seconds to wait for windows agent to be ' 'fully operational') +flags.DEFINE_string('xenapi_vif_driver', + 'nova.virt.xenapi.vif.XenAPIBridgeDriver', + 'The XenAPI VIF driver using XenServer Network APIs.') def cmp_version(a, b): @@ -78,6 +81,7 @@ class VMOps(object): self._session = session self.poll_rescue_last_ran = None VMHelper.XenAPI = self.XenAPI + self.vif_driver = utils.import_object(FLAGS.xenapi_vif_driver) def list_instances(self): """List VM instances.""" @@ -255,7 +259,7 @@ class VMOps(object): VMHelper.preconfigure_instance(self._session, instance, first_vdi_ref, network_info) - self.create_vifs(vm_ref, network_info) + self.create_vifs(vm_ref, instance, network_info) self.inject_network_info(instance, network_info, vm_ref) return vm_ref @@ -467,7 +471,7 @@ class VMOps(object): self._session, instance, template_vdi_uuids, image_id) finally: if template_vm_ref: - self._destroy(instance, template_vm_ref, + self._destroy(instance, template_vm_ref, None, shutdown=False, destroy_kernel_ramdisk=False) logging.debug(_("Finished snapshot and upload for VM %s"), instance) @@ -837,7 +841,7 @@ class VMOps(object): self._session.call_xenapi("Async.VM.destroy", rescue_vm_ref) - def destroy(self, instance): + def destroy(self, instance, network_info): """Destroy VM instance. This is the method exposed by xenapi_conn.destroy(). The rest of the @@ -847,9 +851,9 @@ class VMOps(object): instance_id = instance.id LOG.info(_("Destroying VM for Instance %(instance_id)s") % locals()) vm_ref = VMHelper.lookup(self._session, instance.name) - return self._destroy(instance, vm_ref, shutdown=True) + return self._destroy(instance, vm_ref, network_info, shutdown=True) - def _destroy(self, instance, vm_ref, shutdown=True, + def _destroy(self, instance, vm_ref, network_info, shutdown=True, destroy_kernel_ramdisk=True): """Destroys VM instance by performing: @@ -871,6 +875,10 @@ class VMOps(object): self._destroy_kernel_ramdisk(instance, vm_ref) self._destroy_vm(instance, vm_ref) + if network_info: + for (network, mapping) in network_info: + self.vif_driver.unplug(instance, network, mapping) + def _wait_with_callback(self, instance_id, task, callback): ret = None try: @@ -1066,7 +1074,7 @@ class VMOps(object): # catch KeyError for domid if instance isn't running pass - def create_vifs(self, vm_ref, network_info): + def create_vifs(self, vm_ref, instance, network_info): """Creates vifs for an instance.""" logging.debug(_("creating vif(s) for vm: |%s|"), vm_ref) @@ -1075,14 +1083,19 @@ class VMOps(object): self._session.get_xenapi().VM.get_record(vm_ref) for device, (network, info) in enumerate(network_info): - mac_address = info['mac'] - bridge = network['bridge'] - rxtx_cap = info.pop('rxtx_cap') - network_ref = \ - NetworkHelper.find_network_with_bridge(self._session, - bridge) - VMHelper.create_vif(self._session, vm_ref, network_ref, - mac_address, device, rxtx_cap) + vif_rec = self.vif_driver.plug(self._session, + vm_ref, instance, device, network, info) + network_ref = vif_rec['network'] + LOG.debug(_('Creating VIF for VM %(vm_ref)s,' \ + ' network %(network_ref)s.') % locals()) + vif_ref = self._session.call_xenapi('VIF.create', vif_rec) + LOG.debug(_('Created VIF %(vif_ref)s for VM %(vm_ref)s,' + ' network %(network_ref)s.') % locals()) + + def plug_vifs(instance, network_info): + """Set up VIF networking on the host.""" + for (network, mapping) in network_info: + self.vif_driver.plug(self._session, instance, network, mapping) def reset_network(self, instance, vm_ref=None): """Creates uuid arg to pass to make_agent_call and calls it.""" diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index ec8c44c1c..7c355a55b 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -210,7 +210,7 @@ class XenAPIConnection(driver.ComputeDriver): """ Create snapshot from a running VM instance """ self._vmops.snapshot(instance, image_id) - def reboot(self, instance): + def reboot(self, instance, network_info): """Reboot VM instance""" self._vmops.reboot(instance) @@ -224,9 +224,9 @@ class XenAPIConnection(driver.ComputeDriver): """ self._vmops.inject_file(instance, b64_path, b64_contents) - def destroy(self, instance): + def destroy(self, instance, network_info): """Destroy VM instance""" - self._vmops.destroy(instance) + self._vmops.destroy(instance, network_info) def pause(self, instance, callback): """Pause VM instance""" @@ -249,11 +249,11 @@ class XenAPIConnection(driver.ComputeDriver): """resume the specified instance""" self._vmops.resume(instance, callback) - def rescue(self, instance, callback): + def rescue(self, instance, callback, network_info): """Rescue the specified instance""" self._vmops.rescue(instance, callback) - def unrescue(self, instance, callback): + def unrescue(self, instance, callback, network_info): """Unrescue the specified instance""" self._vmops.unrescue(instance, callback) @@ -269,6 +269,9 @@ class XenAPIConnection(driver.ComputeDriver): """inject network info for specified instance""" self._vmops.inject_network_info(instance, network_info) + def plug_vifs(self, instance_ref, network_info): + self._vmops.plug_vifs(instance_ref, network_info) + def get_info(self, instance_id): """Return data about VM instance""" return self._vmops.get_info(instance_id) @@ -322,7 +325,7 @@ class XenAPIConnection(driver.ComputeDriver): """This method is supported only by libvirt.""" return - def unfilter_instance(self, instance_ref): + def unfilter_instance(self, instance_ref, network_info): """This method is supported only by libvirt.""" raise NotImplementedError('This method is supported only by libvirt.') diff --git a/nova/volume/api.py b/nova/volume/api.py index cfc274c77..52b3a9fed 100644 --- a/nova/volume/api.py +++ b/nova/volume/api.py @@ -52,7 +52,7 @@ class API(base.Base): if quota.allowed_volumes(context, 1, size) < 1: pid = context.project_id - LOG.warn(_("Quota exceeeded for %(pid)s, tried to create" + LOG.warn(_("Quota exceeded for %(pid)s, tried to create" " %(size)sG volume") % locals()) raise quota.QuotaError(_("Volume quota exceeded. You cannot " "create a volume of size %sG") % size) diff --git a/run_tests.sh b/run_tests.sh index b8078e150..8f2b51757 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -11,6 +11,7 @@ function usage { echo " -x, --stop Stop running tests after the first error or failure." echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -p, --pep8 Just run pep8" + echo " -c, --coverage Generate coverage report" echo " -h, --help Print this usage message" echo " --hide-elapsed Don't print the elapsed time for each test along with slow test list" echo "" @@ -29,6 +30,7 @@ function process_option { -n|--no-recreate-db) let recreate_db=0;; -f|--force) let force=1;; -p|--pep8) let just_pep8=1;; + -c|--coverage) let coverage=1;; -*) noseopts="$noseopts $1";; *) noseargs="$noseargs $1" esac @@ -43,12 +45,18 @@ noseargs= noseopts= wrapper="" just_pep8=0 +coverage=0 recreate_db=1 for arg in "$@"; do process_option $arg done +# If enabled, tell nose to collect coverage data +if [ $coverage -eq 1 ]; then + noseopts="$noseopts --with-coverage --cover-package=nova" +fi + function run_tests { # Just run the test suites in current environment ${wrapper} $NOSETESTS 2> run_tests.log @@ -108,6 +116,11 @@ then fi fi +# Delete old coverage data from previous runs +if [ $coverage -eq 1 ]; then + ${wrapper} coverage erase +fi + if [ $just_pep8 -eq 1 ]; then run_pep8 exit @@ -126,3 +139,8 @@ run_tests || exit if [ -z "$noseargs" ]; then run_pep8 fi + +if [ $coverage -eq 1 ]; then + echo "Generating coverage report in covhtml/" + ${wrapper} coverage html -d covhtml -i +fi diff --git a/tools/esx/guest_tool.py b/tools/esx/guest_tool.py index 13b0f8d33..97b5302ba 100644 --- a/tools/esx/guest_tool.py +++ b/tools/esx/guest_tool.py @@ -21,6 +21,7 @@ On Windows we require pyWin32 installed on Python. """
import array
+import gettext
import logging
import os
import platform
@@ -30,6 +31,8 @@ import subprocess import sys
import time
+gettext.install('nova', unicode=1)
+
PLATFORM_WIN = 'win32'
PLATFORM_LINUX = 'linux2'
ARCH_32_BIT = '32bit'
@@ -275,7 +278,8 @@ def _filter_duplicates(all_entries): return final_list
-def _set_rhel_networking(network_details=[]):
+def _set_rhel_networking(network_details=None):
+ network_details = network_details or []
all_dns_servers = []
for network_detail in network_details:
mac_address, ip_address, subnet_mask, gateway, broadcast,\
@@ -315,6 +319,46 @@ def _set_rhel_networking(network_details=[]): _execute(['/sbin/service', 'network', 'restart'])
+def _set_ubuntu_networking(network_details=None):
+ network_details = network_details or []
+ """ Set IPv4 network settings for Ubuntu """
+ all_dns_servers = []
+ for network_detail in network_details:
+ mac_address, ip_address, subnet_mask, gateway, broadcast,\
+ dns_servers = network_detail
+ all_dns_servers.extend(dns_servers)
+ adapter_name, current_ip_address = \
+ _get_linux_adapter_name_and_ip_address(mac_address)
+
+ if adapter_name and not ip_address == current_ip_address:
+ interface_file_name = \
+ '/etc/network/interfaces'
+ # Remove file
+ os.remove(interface_file_name)
+ # Touch file
+ _execute(['touch', interface_file_name])
+ interface_file = open(interface_file_name, 'w')
+ interface_file.write('\nauto %s' % adapter_name)
+ interface_file.write('\niface %s inet static' % adapter_name)
+ interface_file.write('\nbroadcast %s' % broadcast)
+ interface_file.write('\ngateway %s' % gateway)
+ interface_file.write('\nnetmask %s' % subnet_mask)
+ interface_file.write('\naddress %s' % ip_address)
+ interface_file.close()
+ if all_dns_servers:
+ dns_file_name = "/etc/resolv.conf"
+ os.remove(dns_file_name)
+ _execute(['touch', dns_file_name])
+ dns_file = open(dns_file_name, 'w')
+ dns_file.write("; generated by OpenStack guest tools")
+ unique_entries = _filter_duplicates(all_dns_servers)
+ for dns_server in unique_entries:
+ dns_file.write("\nnameserver %s" % dns_server)
+ dns_file.close()
+ print "\nRestarting networking....\n"
+ _execute(['/etc/init.d/networking', 'restart'])
+
+
def _linux_set_networking():
"""Set IP address for the Linux VM."""
vmware_tools_bin = None
@@ -330,8 +374,13 @@ def _linux_set_networking(): cmd = [vmware_tools_bin, '--cmd', 'machine.id.get']
network_details = _parse_network_details(_execute(cmd,
check_exit_code=False))
- # TODO(sateesh): For other distros like ubuntu, suse, debian, BSD, etc.
- _set_rhel_networking(network_details)
+ # TODO(sateesh): For other distros like suse, debian, BSD, etc.
+ if(platform.dist()[0] == 'Ubuntu'):
+ _set_ubuntu_networking(network_details)
+ elif (platform.dist()[0] == 'redhat'):
+ _set_rhel_networking(network_details)
+ else:
+ logging.warn(_("Distro '%s' not supported") % platform.dist()[0])
else:
logging.warn(_("VMware Tools is not installed"))
|
