summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSalvatore Orlando <salvatore.orlando@eu.citrix.com>2010-12-21 11:43:06 +0000
committerSalvatore Orlando <salvatore.orlando@eu.citrix.com>2010-12-21 11:43:06 +0000
commit301dd942b533f8efbe55a74def7ae79de3a11f48 (patch)
treeaee41c5cfda5b1e8960ae11e7c3032a0f096fc9f
parentc7dada9b2f5811afbc7a181636d6be3ab8ab98cc (diff)
parent086f2d87be3c56ac8dafaf4551096868d57454db (diff)
1) Merged from trunk
2) 'type' parameter in VMHelper.fetch_image converted in enum 3) Fixed pep8 errors 4) Passed unit tests
-rw-r--r--.mailmap1
-rw-r--r--Authors4
-rw-r--r--nova/adminclient.py1
-rw-r--r--nova/api/ec2/admin.py1
-rw-r--r--nova/api/openstack/__init__.py13
-rw-r--r--nova/api/openstack/backup_schedules.py1
-rw-r--r--nova/api/openstack/servers.py29
-rw-r--r--nova/compute/api.py20
-rw-r--r--nova/compute/manager.py41
-rw-r--r--nova/db/sqlalchemy/api.py4
-rw-r--r--nova/exception.py3
-rw-r--r--nova/tests/api/openstack/test_servers.py37
-rw-r--r--nova/tests/compute_unittest.py8
-rw-r--r--nova/twistd.py2
-rw-r--r--nova/virt/fake.py13
-rw-r--r--nova/virt/libvirt_conn.py9
-rw-r--r--nova/virt/xenapi/network_utils.py1
-rw-r--r--nova/virt/xenapi/vm_utils.py28
-rw-r--r--nova/virt/xenapi/vmops.py36
-rw-r--r--nova/virt/xenapi/volumeops.py1
-rw-r--r--nova/virt/xenapi_conn.py10
21 files changed, 241 insertions, 22 deletions
diff --git a/.mailmap b/.mailmap
index 2a6eb8d7d..8041e2341 100644
--- a/.mailmap
+++ b/.mailmap
@@ -19,6 +19,7 @@
<mordred@inaugust.com> <mordred@hudson>
<paul@openstack.org> <pvoccio@castor.local>
<paul@openstack.org> <paul.voccio@rackspace.com>
+<soren.hansen@rackspace.com> <soren@linux2go.dk>
<todd@ansolabs.com> <todd@lapex>
<todd@ansolabs.com> <todd@rubidine.com>
<vishvananda@gmail.com> <vishvananda@yahoo.com>
diff --git a/Authors b/Authors
index dd91bf1c5..f33dc1468 100644
--- a/Authors
+++ b/Authors
@@ -25,12 +25,12 @@ Monty Taylor <mordred@inaugust.com>
Paul Voccio <paul@openstack.org>
Rick Clark <rick@openstack.org>
Ryan Lucio <rlucio@internap.com>
+Salvatore Orlando <salvatore.orlando@eu.citrix.com>
Sandy Walsh <sandy.walsh@rackspace.com>
Soren Hansen <soren.hansen@rackspace.com>
-Soren Hansen <soren@linux2go.dk>
Todd Willey <todd@ansolabs.com>
Trey Morris <trey.morris@rackspace.com>
Vishvananda Ishaya <vishvananda@gmail.com>
Youcef Laribi <Youcef.Laribi@eu.citrix.com>
Zhixue Wu <Zhixue.Wu@citrix.com>
-Salvatore Orlando <salvatore.orlando@eu.citrix.com>
+
diff --git a/nova/adminclient.py b/nova/adminclient.py
index 5a62cce7d..6ae9f0c0f 100644
--- a/nova/adminclient.py
+++ b/nova/adminclient.py
@@ -194,6 +194,7 @@ class HostInfo(object):
class NovaAdminClient(object):
+
def __init__(
self,
clc_url=DEFAULT_CLC_URL,
diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py
index 1c6ab688d..fac01369e 100644
--- a/nova/api/ec2/admin.py
+++ b/nova/api/ec2/admin.py
@@ -168,6 +168,7 @@ class AdminController(object):
# FIXME(vish): these host commands don't work yet, perhaps some of the
# required data can be retrieved from service objects?
+
def describe_hosts(self, _context, **_kwargs):
"""Returns status info for all nodes. Includes:
* Disk Space
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index b9ecbd9b8..210df8d24 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -170,9 +170,16 @@ class APIRouter(wsgi.Router):
def __init__(self):
mapper = routes.Mapper()
+
+ server_members = {'action': 'POST'}
+ if FLAGS.allow_admin_api:
+ logging.debug("Including admin operations in API.")
+ server_members['pause'] = 'POST'
+ server_members['unpause'] = 'POST'
+
mapper.resource("server", "servers", controller=servers.Controller(),
collection={'detail': 'GET'},
- member={'action': 'POST'})
+ member=server_members)
mapper.resource("backup_schedule", "backup_schedules",
controller=backup_schedules.Controller(),
@@ -186,10 +193,6 @@ class APIRouter(wsgi.Router):
mapper.resource("sharedipgroup", "sharedipgroups",
controller=sharedipgroups.Controller())
- if FLAGS.allow_admin_api:
- logging.debug("Including admin operations in API.")
- # TODO: Place routes for admin operations here.
-
super(APIRouter, self).__init__(mapper)
diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py
index 3ed691d7b..fc70b5c6c 100644
--- a/nova/api/openstack/backup_schedules.py
+++ b/nova/api/openstack/backup_schedules.py
@@ -24,6 +24,7 @@ import nova.image.service
class Controller(wsgi.Controller):
+
def __init__(self):
pass
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 7704f48f1..5c3322f7c 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -15,6 +15,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+import logging
+import traceback
+
from webob import exc
from nova import exception
@@ -27,6 +30,10 @@ from nova.compute import power_state
import nova.api.openstack
+LOG = logging.getLogger('server')
+LOG.setLevel(logging.DEBUG)
+
+
def _entity_list(entities):
""" Coerces a list of servers into proper dictionary format """
return dict(servers=entities)
@@ -166,3 +173,25 @@ class Controller(wsgi.Controller):
except:
return faults.Fault(exc.HTTPUnprocessableEntity())
return exc.HTTPAccepted()
+
+ def pause(self, req, id):
+ """ Permit Admins to Pause the server. """
+ ctxt = req.environ['nova.context']
+ try:
+ self.compute_api.pause(ctxt, id)
+ except:
+ readable = traceback.format_exc()
+ logging.error("Compute.api::pause %s", readable)
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+ return exc.HTTPAccepted()
+
+ def unpause(self, req, id):
+ """ Permit Admins to Unpause the server. """
+ ctxt = req.environ['nova.context']
+ try:
+ self.compute_api.unpause(ctxt, id)
+ except:
+ readable = traceback.format_exc()
+ logging.error("Compute.api::unpause %s", readable)
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+ return exc.HTTPAccepted()
diff --git a/nova/compute/api.py b/nova/compute/api.py
index aa64b8c5f..046b0fa86 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -77,7 +77,7 @@ class ComputeAPI(base.Base):
kernel_id = image.get('kernelId', None)
if ramdisk_id is None:
ramdisk_id = image.get('ramdiskId', None)
- #Salvatore - No kernel and ramdisk for raw images
+ #No kernel and ramdisk for raw images
if kernel_id == str(FLAGS.null_kernel):
kernel_id = None
ramdisk_id = None
@@ -281,6 +281,24 @@ class ComputeAPI(base.Base):
{"method": "reboot_instance",
"args": {"instance_id": instance['id']}})
+ def pause(self, context, instance_id):
+ """Pause the given instance."""
+ instance = self.db.instance_get_by_internal_id(context, instance_id)
+ host = instance['host']
+ rpc.cast(context,
+ self.db.queue_get_for(context, FLAGS.compute_topic, host),
+ {"method": "pause_instance",
+ "args": {"instance_id": instance['id']}})
+
+ def unpause(self, context, instance_id):
+ """Unpause the given instance."""
+ instance = self.db.instance_get_by_internal_id(context, instance_id)
+ host = instance['host']
+ rpc.cast(context,
+ self.db.queue_get_for(context, FLAGS.compute_topic, host),
+ {"method": "unpause_instance",
+ "args": {"instance_id": instance['id']}})
+
def rescue(self, context, instance_id):
"""Rescue the given instance."""
instance = self.db.instance_get_by_internal_id(context, instance_id)
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 7eb60e262..a84af6bb9 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -186,6 +186,47 @@ class ComputeManager(manager.Manager):
self.driver.unrescue(instance_ref)
self._update_state(context, instance_id)
+ @staticmethod
+ def _update_state_callback(self, context, instance_id, result):
+ """Update instance state when async task completes."""
+ self._update_state(context, instance_id)
+
+ @exception.wrap_exception
+ def pause_instance(self, context, instance_id):
+ """Pause an instance on this server."""
+ context = context.elevated()
+ instance_ref = self.db.instance_get(context, instance_id)
+
+ logging.debug('instance %s: pausing',
+ instance_ref['internal_id'])
+ self.db.instance_set_state(context,
+ instance_id,
+ power_state.NOSTATE,
+ 'pausing')
+ self.driver.pause(instance_ref,
+ lambda result: self._update_state_callback(self,
+ context,
+ instance_id,
+ result))
+
+ @exception.wrap_exception
+ def unpause_instance(self, context, instance_id):
+ """Unpause a paused instance on this server."""
+ context = context.elevated()
+ instance_ref = self.db.instance_get(context, instance_id)
+
+ logging.debug('instance %s: unpausing',
+ instance_ref['internal_id'])
+ self.db.instance_set_state(context,
+ instance_id,
+ power_state.NOSTATE,
+ 'unpausing')
+ self.driver.unpause(instance_ref,
+ lambda result: self._update_state_callback(self,
+ context,
+ instance_id,
+ result))
+
@exception.wrap_exception
def get_console_output(self, context, instance_id):
"""Send the console output for an instance."""
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 55036d1d1..935063609 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -528,6 +528,8 @@ def fixed_ip_update(context, address, values):
#TODO(gundlach): instance_create and volume_create are nearly identical
#and should be refactored. I expect there are other copy-and-paste
#functions between the two of them as well.
+
+
@require_context
def instance_create(context, values):
"""Create a new Instance record in the database.
@@ -913,6 +915,8 @@ def network_get(context, network_id, session=None):
# NOTE(vish): pylint complains because of the long method name, but
# it fits with the names of the rest of the methods
# pylint: disable-msg=C0103
+
+
@require_admin_context
def network_get_associated_fixed_ips(context, network_id):
session = get_session()
diff --git a/nova/exception.py b/nova/exception.py
index 6d6c37338..9af4017ba 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -27,6 +27,7 @@ import traceback
class ProcessExecutionError(IOError):
+
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
description=None):
if description is None:
@@ -39,11 +40,13 @@ class ProcessExecutionError(IOError):
class Error(Exception):
+
def __init__(self, message=None):
super(Error, self).__init__(message)
class ApiError(Error):
+
def __init__(self, message='Unknown', code='Unknown'):
self.message = message
self.code = code
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 8444b6fce..3820f5f27 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -56,11 +56,16 @@ def instance_address(context, instance_id):
def stub_instance(id, user_id=1):
- return Instance(id=id + 123456, state=0, image_id=10, user_id=user_id,
+ return Instance(id=int(id) + 123456, state=0, image_id=10, user_id=user_id,
display_name='server%s' % id, internal_id=id)
+def fake_compute_api(cls, req, id):
+ return True
+
+
class ServersTest(unittest.TestCase):
+
def setUp(self):
self.stubs = stubout.StubOutForTesting()
fakes.FakeAuthManager.auth_data = {}
@@ -82,9 +87,15 @@ class ServersTest(unittest.TestCase):
instance_address)
self.stubs.Set(nova.db.api, 'instance_get_floating_address',
instance_address)
+ self.stubs.Set(nova.compute.api.ComputeAPI, 'pause',
+ fake_compute_api)
+ self.stubs.Set(nova.compute.api.ComputeAPI, 'unpause',
+ fake_compute_api)
+ self.allow_admin = FLAGS.allow_admin_api
def tearDown(self):
self.stubs.UnsetAll()
+ FLAGS.allow_admin_api = self.allow_admin
def test_get_server_by_id(self):
req = webob.Request.blank('/v1.0/servers/1')
@@ -211,6 +222,30 @@ class ServersTest(unittest.TestCase):
self.assertEqual(s['imageId'], 10)
i += 1
+ def test_server_pause(self):
+ FLAGS.allow_admin_api = True
+ body = dict(server=dict(
+ name='server_test', imageId=2, flavorId=2, metadata={},
+ personality={}))
+ req = webob.Request.blank('/v1.0/servers/1/pause')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(nova.api.API('os'))
+ self.assertEqual(res.status_int, 202)
+
+ def test_server_unpause(self):
+ FLAGS.allow_admin_api = True
+ body = dict(server=dict(
+ name='server_test', imageId=2, flavorId=2, metadata={},
+ personality={}))
+ req = webob.Request.blank('/v1.0/servers/1/unpause')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(nova.api.API('os'))
+ self.assertEqual(res.status_int, 202)
+
def test_server_reboot(self):
body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={},
diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py
index c6353d357..187ca31de 100644
--- a/nova/tests/compute_unittest.py
+++ b/nova/tests/compute_unittest.py
@@ -127,6 +127,14 @@ class ComputeTestCase(test.TestCase):
self.assert_(instance_ref['launched_at'] < terminate)
self.assert_(instance_ref['deleted_at'] > terminate)
+ def test_pause(self):
+ """Ensure instance can be paused"""
+ instance_id = self._create_instance()
+ self.compute.run_instance(self.context, instance_id)
+ self.compute.pause_instance(self.context, instance_id)
+ self.compute.unpause_instance(self.context, instance_id)
+ self.compute.terminate_instance(self.context, instance_id)
+
def test_reboot(self):
"""Ensure instance can be rebooted"""
instance_id = self._create_instance()
diff --git a/nova/twistd.py b/nova/twistd.py
index cb5648ce6..e6c3101f1 100644
--- a/nova/twistd.py
+++ b/nova/twistd.py
@@ -43,7 +43,7 @@ else:
FLAGS = flags.FLAGS
-flags.DEFINE_string('logdir', None, 'directory to keep log files in '
+flags.DEFINE_string('logdir', None, 'directory to keep log files in '
'(will be prepended to $logfile)')
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 77bc926c2..55c6dcef9 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -130,6 +130,18 @@ class FakeConnection(object):
"""
pass
+ def pause(self, instance, callback):
+ """
+ Pause the specified instance.
+ """
+ pass
+
+ def unpause(self, instance, callback):
+ """
+ Unpause the specified instance.
+ """
+ pass
+
def destroy(self, instance):
"""
Destroy (shutdown and delete) the specified instance.
@@ -243,5 +255,6 @@ class FakeConnection(object):
class FakeInstance(object):
+
def __init__(self):
self._state = power_state.NOSTATE
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index 75c8eaa4c..ad101db2a 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -97,6 +97,7 @@ def get_connection(read_only):
class LibvirtConnection(object):
+
def __init__(self, read_only):
self.libvirt_uri = self.get_uri()
@@ -261,6 +262,14 @@ class LibvirtConnection(object):
return timer.start(interval=0.5, now=True)
@exception.wrap_exception
+ def pause(self, instance, callback):
+ raise exception.APIError("pause not supported for libvirt.")
+
+ @exception.wrap_exception
+ def unpause(self, instance, callback):
+ raise exception.APIError("unpause not supported for libvirt.")
+
+ @exception.wrap_exception
def rescue(self, instance):
self.destroy(instance, False)
diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py
index 012954394..ce2c68ce0 100644
--- a/nova/virt/xenapi/network_utils.py
+++ b/nova/virt/xenapi/network_utils.py
@@ -25,6 +25,7 @@ class NetworkHelper():
"""
The class that wraps the helper methods together.
"""
+
def __init__(self):
return
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index 4c58598c1..d92ca235a 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -43,10 +43,24 @@ XENAPI_POWER_STATE = {
XenAPI = None
+class ImageType:
+ """
+ Enumeration class for distinguishing different image types
+ 0 - kernel/ramdisk image (goes on dom0's filesystem)
+ 1 - disk image (local SR, partitioned by objectstore plugin)
+ 2 - raw disk image (local SR, NOT partitioned by plugin)
+ """
+
+ KERNEL_RAMDISK = 0
+ DISK = 1
+ DISK_RAW = 2
+
+
class VMHelper():
"""
The class that wraps the helper methods together.
"""
+
def __init__(self):
return
@@ -170,24 +184,22 @@ class VMHelper():
@classmethod
def fetch_image(cls, session, image, user, project, type):
- """type: integer field for specifying how to handle the image
- 0 - kernel/ramdisk image (goes on dom0's filesystem)
- 1 - disk image (local SR, partitioned by objectstore plugin)
- 2 - raw disk image (local SR, NOT partitioned by plugin)"""
-
+ """
+ type is interpreted as an ImageType instance
+ """
url = images.image_url(image)
access = AuthManager().get_access_key(user, project)
logging.debug("Asking xapi to fetch %s as %s", url, access)
- fn = (type != 0) and 'get_vdi' or 'get_kernel'
+ fn = (type != ImageType.KERNEL_RAMDISK) and 'get_vdi' or 'get_kernel'
args = {}
args['src_url'] = url
args['username'] = access
args['password'] = user.secret
args['add_partition'] = 'false'
args['raw'] = 'false'
- if type != 0:
+ if type != ImageType.KERNEL_RAMDISK:
args['add_partition'] = 'true'
- if type == 2:
+ if type == ImageType.DISK_RAW:
args['raw'] = 'true'
task = session.async_call_plugin('objectstore', fn, args)
uuid = session.wait_for_task(task)
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 37c64ee93..cd704d9f8 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -26,6 +26,7 @@ from nova import context
from nova.auth.manager import AuthManager
from nova.virt.xenapi.network_utils import NetworkHelper
from nova.virt.xenapi.vm_utils import VMHelper
+from nova.virt.xenapi.vm_utils import ImageType
XenAPI = None
@@ -34,6 +35,7 @@ class VMOps(object):
"""
Management class for VM-related tasks
"""
+
def __init__(self, session):
global XenAPI
if XenAPI is None:
@@ -63,9 +65,9 @@ class VMOps(object):
project = AuthManager().get_project(instance.project_id)
#if kernel is not present we must download a raw disk
if instance.kernel_id:
- disk_image_type = 1
+ disk_image_type = ImageType.DISK
else:
- disk_image_type = 2
+ disk_image_type = ImageType.DISK_RAW
vdi_uuid = VMHelper.fetch_image(self._session,
instance.image_id, user, project, disk_image_type)
vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid)
@@ -76,11 +78,11 @@ class VMOps(object):
kernel = None
if instance.kernel_id:
kernel = VMHelper.fetch_image(self._session,
- instance.kernel_id, user, project, 0)
+ instance.kernel_id, user, project, ImageType.KERNEL_RAMDISK)
ramdisk = None
if instance.ramdisk_id:
ramdisk = VMHelper.fetch_image(self._session,
- instance.ramdisk_id, user, project, 0)
+ instance.ramdisk_id, user, project, ImageType.KERNEL_RAMDISK)
vm_ref = VMHelper.create_vm(self._session,
instance, kernel, ramdisk, pv_kernel)
VMHelper.create_vbd(self._session, vm_ref, vdi_ref, 0, True)
@@ -131,6 +133,32 @@ class VMOps(object):
except XenAPI.Failure, exc:
logging.warn(exc)
+ def _wait_with_callback(self, task, callback):
+ ret = None
+ try:
+ ret = self._session.wait_for_task(task)
+ except XenAPI.Failure, exc:
+ logging.warn(exc)
+ callback(ret)
+
+ def pause(self, instance, callback):
+ """ Pause VM instance """
+ instance_name = instance.name
+ vm = VMHelper.lookup(self._session, instance_name)
+ if vm is None:
+ raise Exception('instance not present %s' % instance_name)
+ task = self._session.call_xenapi('Async.VM.pause', vm)
+ self._wait_with_callback(task, callback)
+
+ def unpause(self, instance, callback):
+ """ Unpause VM instance """
+ instance_name = instance.name
+ vm = VMHelper.lookup(self._session, instance_name)
+ if vm is None:
+ raise Exception('instance not present %s' % instance_name)
+ task = self._session.call_xenapi('Async.VM.unpause', vm)
+ self._wait_with_callback(task, callback)
+
def get_info(self, instance_id):
""" Return data about VM instance """
vm = VMHelper.lookup_blocking(self._session, instance_id)
diff --git a/nova/virt/xenapi/volumeops.py b/nova/virt/xenapi/volumeops.py
index a4c7a3861..1943ccab0 100644
--- a/nova/virt/xenapi/volumeops.py
+++ b/nova/virt/xenapi/volumeops.py
@@ -20,6 +20,7 @@ Management class for Storage-related functions (attach, detach, etc).
class VolumeOps(object):
+
def __init__(self, session):
self._session = session
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index 6beb08f5e..21ed2cd65 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -102,6 +102,7 @@ def get_connection(_):
class XenAPIConnection(object):
""" A connection to XenServer or Xen Cloud Platform """
+
def __init__(self, url, user, pw):
session = XenAPISession(url, user, pw)
self._vmops = VMOps(session)
@@ -123,6 +124,14 @@ class XenAPIConnection(object):
""" Destroy VM instance """
self._vmops.destroy(instance)
+ def pause(self, instance, callback):
+ """ Pause VM instance """
+ self._vmops.pause(instance, callback)
+
+ def unpause(self, instance, callback):
+ """ Unpause paused VM instance """
+ self._vmops.unpause(instance, callback)
+
def get_info(self, instance_id):
""" Return data about VM instance """
return self._vmops.get_info(instance_id)
@@ -148,6 +157,7 @@ class XenAPIConnection(object):
class XenAPISession(object):
""" The session to invoke XenAPI SDK calls """
+
def __init__(self, url, user, pw):
self._session = XenAPI.Session(url)
self._session.login_with_password(user, pw)