diff options
29 files changed, 919 insertions, 504 deletions
diff --git a/.bzrignore b/.bzrignore index 583fd551f..91277d100 100644 --- a/.bzrignore +++ b/.bzrignore @@ -13,5 +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> @@ -20,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> @@ -84,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/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 5c75a9a6d..6f7573f66 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -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/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 ea62524cf..a75636390 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -96,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}'/> @@ -111,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 150173f5a..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) @@ -460,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 @@ -475,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) @@ -525,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 @@ -534,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, @@ -563,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): @@ -579,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 [] @@ -928,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. @@ -992,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) @@ -1024,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} @@ -1593,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/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/vmops.py b/nova/virt/vmwareapi/vmops.py index 568359598..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.
@@ -118,6 +124,7 @@ class VMWareVMOps(object): raise exception.NetworkNotFoundForBridge(bridge=net_name)
return network_ref
+ self.plug_vifs(instance, network_info)
network_obj = _check_if_network_bridge_exists()
def _get_datastore_ref():
@@ -475,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",
@@ -517,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.
@@ -563,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:
@@ -791,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.') |
