Merge "xenapi: Adding BitTorrent download handler"

This commit is contained in:
Jenkins
2013-08-08 04:30:09 +00:00
committed by Gerrit Code Review
6 changed files with 403 additions and 255 deletions

View File

@@ -2244,6 +2244,40 @@
#xenapi_login_timeout=10
#
# Options defined in nova.virt.xenapi.image.bittorrent
#
# Base URL for torrent files. (string value)
#xenapi_torrent_base_url=<None>
# Probability that peer will become a seeder. (1.0 = 100%)
# (floating point value)
#xenapi_torrent_seed_chance=1.0
# Number of seconds after downloading an image via BitTorrent
# that it should be seeded for other peers. (integer value)
#xenapi_torrent_seed_duration=3600
# Cached torrent files not accessed within this number of
# seconds can be reaped (integer value)
#xenapi_torrent_max_last_accessed=86400
# Beginning of port range to listen on (integer value)
#xenapi_torrent_listen_port_start=6881
# End of port range to listen on (integer value)
#xenapi_torrent_listen_port_end=6891
# Number of seconds a download can remain at the same progress
# percentage w/o being considered a stall (integer value)
#xenapi_torrent_download_stall_cutoff=600
# Maximum number of seeder processes to run concurrently
# within a given dom0. (-1 = no limit) (integer value)
#xenapi_torrent_max_seeder_processes_per_host=1
#
# Options defined in nova.virt.xenapi.pool
#
@@ -2305,35 +2339,6 @@
# (all|some|none). (string value)
#xenapi_torrent_images=none
# Base URL for torrent files. (string value)
#xenapi_torrent_base_url=<None>
# Probability that peer will become a seeder. (1.0 = 100%)
# (floating point value)
#xenapi_torrent_seed_chance=1.0
# Number of seconds after downloading an image via BitTorrent
# that it should be seeded for other peers. (integer value)
#xenapi_torrent_seed_duration=3600
# Cached torrent files not accessed within this number of
# seconds can be reaped (integer value)
#xenapi_torrent_max_last_accessed=86400
# Beginning of port range to listen on (integer value)
#xenapi_torrent_listen_port_start=6881
# End of port range to listen on (integer value)
#xenapi_torrent_listen_port_end=6891
# Number of seconds a download can remain at the same progress
# percentage w/o being considered a stall (integer value)
#xenapi_torrent_download_stall_cutoff=600
# Maximum number of seeder processes to run concurrently
# within a given dom0. (-1 = no limit) (integer value)
#xenapi_torrent_max_seeder_processes_per_host=1
#
# Options defined in nova.virt.xenapi.vmops
@@ -2347,7 +2352,7 @@
# value)
#xenapi_vif_driver=nova.virt.xenapi.vif.XenAPIBridgeDriver
# Object Store Driver used to handle image uploads. (string
# Dom0 plugin driver used to handle image uploads. (string
# value)
#xenapi_image_upload_handler=nova.virt.xenapi.image.glance.GlanceStore

View File

@@ -0,0 +1,147 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 OpenStack Foundation
# 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.
import pkg_resources
import mox
from nova import context
from nova.openstack.common.gettextutils import _
from nova import test
from nova.tests.virt.xenapi import stubs
from nova.virt.xenapi import driver as xenapi_conn
from nova.virt.xenapi import fake
from nova.virt.xenapi.image import bittorrent
from nova.virt.xenapi import vm_utils
class TestBittorrentStore(stubs.XenAPITestBase):
def setUp(self):
super(TestBittorrentStore, self).setUp()
self.store = bittorrent.BittorrentStore()
self.mox = mox.Mox()
self.flags(xenapi_torrent_base_url='http://foo',
xenapi_connection_url='test_url',
xenapi_connection_password='test_pass')
self.context = context.RequestContext(
'user', 'project', auth_token='foobar')
fake.reset()
stubs.stubout_session(self.stubs, fake.SessionBase)
driver = xenapi_conn.XenAPIDriver(False)
self.session = driver._session
self.stubs.Set(
vm_utils, 'get_sr_path', lambda *a, **kw: '/fake/sr/path')
self.instance = {'uuid': 'blah',
'system_metadata': {'image_xenapi_use_agent': 'true'},
'auto_disk_config': True,
'os_type': 'default',
'xenapi_use_agent': 'true'}
def test_download_image(self):
params = {'image_id': 'fake_image_uuid',
'sr_path': '/fake/sr/path',
'torrent_download_stall_cutoff': 600,
'torrent_listen_port_end': 6891,
'torrent_listen_port_start': 6881,
'torrent_max_last_accessed': 86400,
'torrent_max_seeder_processes_per_host': 1,
'torrent_seed_chance': 1.0,
'torrent_seed_duration': 3600,
'torrent_url': 'http://foo/fake_image_uuid.torrent',
'uuid_stack': ['uuid1']}
self.stubs.Set(vm_utils, '_make_uuid_stack',
lambda *a, **kw: ['uuid1'])
self.mox.StubOutWithMock(self.session, 'call_plugin_serialized')
self.session.call_plugin_serialized(
'bittorrent', 'download_vhd', **params)
self.mox.ReplayAll()
vdis = self.store.download_image(
self.context, self.session, self.instance, 'fake_image_uuid')
self.mox.VerifyAll()
def test_upload_image(self):
self.assertRaises(NotImplementedError, self.store.upload_image,
self.context, self.session, self.instance, ['fake_vdi_uuid'],
'fake_image_uuid')
def bad_fetcher(instance, image_id):
raise test.TestingException("just plain bad.")
def another_fetcher(instance, image_id):
return "http://www.foobar.com/%s" % image_id
class MockEntryPoint(object):
name = "torrent_url"
def load(self):
return another_fetcher
class LookupTorrentURLTestCase(test.TestCase):
def setUp(self):
super(LookupTorrentURLTestCase, self).setUp()
self.store = bittorrent.BittorrentStore()
self.instance = {'uuid': 'fakeuuid'}
self.image_id = 'fakeimageid'
def test_default_fetch_url_no_base_url_set(self):
self.flags(xenapi_torrent_base_url=None)
exc = self.assertRaises(
RuntimeError, self.store._lookup_torrent_url_fn)
self.assertEqual(_('Cannot create default bittorrent URL without'
' xenapi_torrent_base_url set'),
str(exc))
def test_default_fetch_url_base_url_is_set(self):
self.flags(xenapi_torrent_base_url='http://foo')
lookup_fn = self.store._lookup_torrent_url_fn()
self.assertEqual('http://foo/fakeimageid.torrent',
lookup_fn(self.instance, self.image_id))
def test_with_extension(self):
def mock_iter_single(namespace):
return [MockEntryPoint()]
self.stubs.Set(pkg_resources, 'iter_entry_points', mock_iter_single)
lookup_fn = self.store._lookup_torrent_url_fn()
self.assertEqual("http://www.foobar.com/%s" % self.image_id,
lookup_fn(self.instance, self.image_id))
def test_multiple_extensions_found(self):
def mock_iter_multiple(namespace):
return [MockEntryPoint(), MockEntryPoint()]
self.stubs.Set(pkg_resources, 'iter_entry_points', mock_iter_multiple)
exc = self.assertRaises(
RuntimeError, self.store._lookup_torrent_url_fn)
self.assertEqual(_('Multiple torrent URL fetcher extension found.'
' Failing.'),
str(exc))

View File

@@ -16,8 +16,6 @@
# under the License.
import contextlib
import pkg_resources
import urlparse
import fixtures
import mox
@@ -210,10 +208,13 @@ class XenAPIGetUUID(test.TestCase):
self.mox.VerifyAll()
class FakeSession():
class FakeSession(object):
def call_xenapi(self, *args):
pass
def call_plugin_serialized(self, plugin, fn, *args, **kwargs):
pass
def call_plugin_serialized_with_retry(self, plugin, fn, num_retries,
callback, *args, **kwargs):
pass
@@ -224,101 +225,123 @@ class FetchVhdImageTestCase(test.TestCase):
super(FetchVhdImageTestCase, self).setUp()
self.context = context.get_admin_context()
self.context.auth_token = 'auth_token'
self.session = FakeSession()
self.instance = {"uuid": "uuid"}
self.image_id = "image_id"
self.uuid_stack = ["uuid_stack"]
self.sr_path = "sr_path"
self.params = {'image_id': self.image_id,
'uuid_stack': self.uuid_stack,
'sr_path': self.sr_path}
self.vdis = {'root': {'uuid': 'vdi'}}
self.mox.StubOutWithMock(vm_utils, '_make_uuid_stack')
self.mox.StubOutWithMock(vm_utils, 'get_sr_path')
self.mox.StubOutWithMock(vm_utils, '_image_uses_bittorrent')
self.mox.StubOutWithMock(vm_utils, '_add_bittorrent_params')
self.mox.StubOutWithMock(vm_utils, 'safe_find_sr')
self.mox.StubOutWithMock(vm_utils, '_scan_sr')
self.mox.StubOutWithMock(vm_utils, '_check_vdi_size')
self.mox.StubOutWithMock(vm_utils, 'destroy_vdi')
self.mox.StubOutWithMock(
self.session, 'call_plugin_serialized_with_retry')
self.mox.StubOutWithMock(vm_utils, '_add_torrent_url')
vm_utils._make_uuid_stack().AndReturn(["uuid_stack"])
vm_utils._make_uuid_stack().AndReturn(self.uuid_stack)
vm_utils.get_sr_path(self.session).AndReturn(self.sr_path)
self.mox.StubOutWithMock(vm_utils, 'get_sr_path')
vm_utils.get_sr_path(self.session).AndReturn('sr_path')
def test_fetch_vhd_image_works_with_glance(self):
self.mox.StubOutWithMock(vm_utils, '_image_uses_bittorrent')
vm_utils._image_uses_bittorrent(
self.context, self.instance).AndReturn(False)
self.params['auth_token'] = 'auth_token'
self.mox.StubOutWithMock(
self.session, 'call_plugin_serialized_with_retry')
self.session.call_plugin_serialized_with_retry(
'glance', 'download_vhd', 0, mox.IgnoreArg(),
**self.params).AndReturn(self.vdis)
auth_token='auth_token',
image_id='image_id',
uuid_stack=["uuid_stack"],
sr_path='sr_path').AndReturn({'root': {'uuid': 'vdi'}})
self.mox.StubOutWithMock(vm_utils, 'safe_find_sr')
vm_utils.safe_find_sr(self.session).AndReturn("sr")
self.mox.StubOutWithMock(vm_utils, '_scan_sr')
vm_utils._scan_sr(self.session, "sr")
self.mox.StubOutWithMock(vm_utils, '_check_vdi_size')
vm_utils._check_vdi_size(
self.context, self.session, self.instance, "vdi")
self.mox.ReplayAll()
self.assertEqual("vdi", vm_utils._fetch_vhd_image(self.context,
self.session, self.instance, self.image_id)['root']['uuid'])
self.session, self.instance, 'image_id')['root']['uuid'])
self.mox.VerifyAll()
def _setup_bittorrent(self):
def test_fetch_vhd_image_works_with_bittorrent(self):
cfg.CONF.import_opt('xenapi_torrent_base_url',
'nova.virt.xenapi.image.bittorrent')
self.flags(xenapi_torrent_base_url='http://foo')
self.mox.StubOutWithMock(vm_utils, '_image_uses_bittorrent')
vm_utils._image_uses_bittorrent(
self.context, self.instance).AndReturn(True)
def _add_torrent_url(instance, image_id, params):
params['torrent_url'] = "%s.torrent" % image_id
vm_utils._add_torrent_url(
self.instance, self.image_id, mox.IgnoreArg()).AndReturn(True)
vm_utils._add_bittorrent_params(self.image_id, self.params)
self.session.call_plugin_serialized_with_retry(
'bittorrent', 'download_vhd', 0, None,
**self.params).AndReturn(self.vdis)
def test_fetch_vhd_image_works_with_bittorrent(self):
self._setup_bittorrent()
self.mox.StubOutWithMock(
self.session, 'call_plugin_serialized')
self.session.call_plugin_serialized('bittorrent', 'download_vhd',
image_id='image_id',
uuid_stack=["uuid_stack"],
sr_path='sr_path',
torrent_download_stall_cutoff=600,
torrent_listen_port_start=6881,
torrent_listen_port_end=6891,
torrent_max_last_accessed=86400,
torrent_max_seeder_processes_per_host=1,
torrent_seed_chance=1.0,
torrent_seed_duration=3600,
torrent_url='http://foo/image_id.torrent'
).AndReturn({'root': {'uuid': 'vdi'}})
self.mox.StubOutWithMock(vm_utils, 'safe_find_sr')
vm_utils.safe_find_sr(self.session).AndReturn("sr")
self.mox.StubOutWithMock(vm_utils, '_scan_sr')
vm_utils._scan_sr(self.session, "sr")
self.mox.StubOutWithMock(vm_utils, '_check_vdi_size')
vm_utils._check_vdi_size(self.context, self.session, self.instance,
"vdi")
self.mox.ReplayAll()
self.assertEqual("vdi", vm_utils._fetch_vhd_image(self.context,
self.session, self.instance, self.image_id)['root']['uuid'])
self.session, self.instance, 'image_id')['root']['uuid'])
self.mox.VerifyAll()
def test_fetch_vhd_image_cleans_up_vdi_on_fail(self):
self.mox.StubOutWithMock(self.session, 'call_xenapi')
self._setup_bittorrent()
self.mox.StubOutWithMock(vm_utils, '_image_uses_bittorrent')
vm_utils._image_uses_bittorrent(
self.context, self.instance).AndReturn(False)
self.mox.StubOutWithMock(
self.session, 'call_plugin_serialized_with_retry')
self.session.call_plugin_serialized_with_retry(
'glance', 'download_vhd', 0, mox.IgnoreArg(),
auth_token='auth_token',
image_id='image_id',
uuid_stack=["uuid_stack"],
sr_path='sr_path').AndReturn({'root': {'uuid': 'vdi'}})
self.mox.StubOutWithMock(vm_utils, 'safe_find_sr')
vm_utils.safe_find_sr(self.session).AndReturn("sr")
self.mox.StubOutWithMock(vm_utils, '_scan_sr')
vm_utils._scan_sr(self.session, "sr")
self.mox.StubOutWithMock(vm_utils, '_check_vdi_size')
vm_utils._check_vdi_size(self.context, self.session, self.instance,
"vdi").AndRaise(exception.InstanceTypeDiskTooSmall)
self.mox.StubOutWithMock(self.session, 'call_xenapi')
self.session.call_xenapi("VDI.get_by_uuid", "vdi").AndReturn("ref")
self.mox.StubOutWithMock(vm_utils, 'destroy_vdi')
vm_utils.destroy_vdi(self.session, "ref")
self.mox.ReplayAll()
self.assertRaises(exception.InstanceTypeDiskTooSmall,
vm_utils._fetch_vhd_image, self.context, self.session,
self.instance, self.image_id)
self.instance, 'image_id')
self.mox.VerifyAll()
@@ -846,68 +869,6 @@ class VDIOtherConfigTestCase(stubs.XenAPITestBase):
self.assertEqual(expected, other_config)
def bad_fetcher(instance, image_id):
raise test.TestingException("just plain bad.")
def another_fetcher(instance, image_id):
return "http://www.foobar.com/%s" % image_id
class MockEntryPoint(object):
name = "torrent_url"
def load(self):
return another_fetcher
class BitTorrentMiscTestCase(test.TestCase):
def tearDown(self):
vm_utils._TORRENT_URL_FN = None
super(BitTorrentMiscTestCase, self).tearDown()
def test_default_fetch_url(self):
def mock_iter_none(namespace):
return []
self.stubs.Set(pkg_resources, 'iter_entry_points', mock_iter_none)
image_id = "1-2-3-4-5"
params = {}
self.assertTrue(vm_utils._add_torrent_url({}, image_id, params))
expected = urlparse.urljoin(CONF.xenapi_torrent_base_url,
"%s.torrent" % image_id)
self.assertEqual(expected, params['torrent_url'])
self.assertEqual(vm_utils.get_torrent_url,
vm_utils._TORRENT_URL_FN)
def test_with_extension(self):
def mock_iter_single(namespace):
return [MockEntryPoint()]
self.stubs.Set(pkg_resources, 'iter_entry_points', mock_iter_single)
image_id = "1-2-3-4-5"
params = {}
self.assertTrue(vm_utils._add_torrent_url({}, image_id, params))
expected = "http://www.foobar.com/%s" % image_id
self.assertEqual(expected, params['torrent_url'])
self.assertEqual(another_fetcher, vm_utils._TORRENT_URL_FN)
def test_more_than_one_extension(self):
def mock_iter_multiple(namespace):
return [MockEntryPoint(), MockEntryPoint()]
self.stubs.Set(pkg_resources, 'iter_entry_points', mock_iter_multiple)
image_id = "1-2-3-4-5"
params = {}
self.assertRaises(RuntimeError, vm_utils._add_torrent_url, {},
image_id, params)
def test_fetch_url_failure(self):
# fetcher function fails:
vm_utils._TORRENT_URL_FN = bad_fetcher
self.assertFalse(vm_utils._add_torrent_url({}, '1-2-3-4-5', {}))
class GenerateDiskTestCase(stubs.XenAPITestBase):
def setUp(self):
super(GenerateDiskTestCase, self).setUp()

View File

@@ -0,0 +1,123 @@
# Copyright 2013 OpenStack Foundation
# 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.
import pkg_resources
import urlparse
from oslo.config import cfg
from nova.openstack.common.gettextutils import _
import nova.openstack.common.log as logging
from nova.virt.xenapi import vm_utils
LOG = logging.getLogger(__name__)
xenapi_torrent_opts = [
cfg.StrOpt('xenapi_torrent_base_url',
default=None,
help='Base URL for torrent files.'),
cfg.FloatOpt('xenapi_torrent_seed_chance',
default=1.0,
help='Probability that peer will become a seeder.'
' (1.0 = 100%)'),
cfg.IntOpt('xenapi_torrent_seed_duration',
default=3600,
help='Number of seconds after downloading an image via'
' BitTorrent that it should be seeded for other peers.'),
cfg.IntOpt('xenapi_torrent_max_last_accessed',
default=86400,
help='Cached torrent files not accessed within this number of'
' seconds can be reaped'),
cfg.IntOpt('xenapi_torrent_listen_port_start',
default=6881,
help='Beginning of port range to listen on'),
cfg.IntOpt('xenapi_torrent_listen_port_end',
default=6891,
help='End of port range to listen on'),
cfg.IntOpt('xenapi_torrent_download_stall_cutoff',
default=600,
help='Number of seconds a download can remain at the same'
' progress percentage w/o being considered a stall'),
cfg.IntOpt('xenapi_torrent_max_seeder_processes_per_host',
default=1,
help='Maximum number of seeder processes to run concurrently'
' within a given dom0. (-1 = no limit)')
]
CONF = cfg.CONF
CONF.register_opts(xenapi_torrent_opts)
class BittorrentStore(object):
@staticmethod
def _lookup_torrent_url_fn():
"""Load a "fetcher" func to get the right torrent URL via
entrypoints.
"""
matches = [ep for ep in
pkg_resources.iter_entry_points('nova.virt.xenapi.vm_utils')
if ep.name == 'torrent_url']
if not matches:
LOG.debug(_("No torrent URL fetcher extension found, using"
" default."))
if not CONF.xenapi_torrent_base_url:
raise RuntimeError(_('Cannot create default bittorrent URL'
' without xenapi_torrent_base_url set'))
def _default_torrent_url_fn(instance, image_id):
return urlparse.urljoin(CONF.xenapi_torrent_base_url,
"%s.torrent" % image_id)
fn = _default_torrent_url_fn
elif len(matches) > 1:
raise RuntimeError(_("Multiple torrent URL fetcher extension"
" found. Failing."))
else:
ep = matches[0]
LOG.debug(_("Loading torrent URL fetcher from entry points"
" %(ep)s"), {'ep': ep})
fn = ep.load()
return fn
def download_image(self, context, session, instance, image_id):
params = {}
params['image_id'] = image_id
params['uuid_stack'] = vm_utils._make_uuid_stack()
params['sr_path'] = vm_utils.get_sr_path(session)
params['torrent_seed_duration'] = CONF.xenapi_torrent_seed_duration
params['torrent_seed_chance'] = CONF.xenapi_torrent_seed_chance
params['torrent_max_last_accessed'] = \
CONF.xenapi_torrent_max_last_accessed
params['torrent_listen_port_start'] = \
CONF.xenapi_torrent_listen_port_start
params['torrent_listen_port_end'] = CONF.xenapi_torrent_listen_port_end
params['torrent_download_stall_cutoff'] = \
CONF.xenapi_torrent_download_stall_cutoff
params['torrent_max_seeder_processes_per_host'] = \
CONF.xenapi_torrent_max_seeder_processes_per_host
lookup_fn = self._lookup_torrent_url_fn()
params['torrent_url'] = lookup_fn(instance, image_id)
vdis = session.call_plugin_serialized(
'bittorrent', 'download_vhd', **params)
return vdis
def upload_image(self, context, session, instance, vdi_uuids, image_id):
raise NotImplementedError

View File

@@ -23,7 +23,6 @@ their attributes like VDIs, VIFs, as well as their lookup functions.
import contextlib
import os
import pkg_resources
import re
import time
import urllib
@@ -98,35 +97,6 @@ xenapi_vm_utils_opts = [
default='none',
help='Whether or not to download images via Bit Torrent '
'(all|some|none).'),
cfg.StrOpt('xenapi_torrent_base_url',
default=None,
help='Base URL for torrent files.'),
cfg.FloatOpt('xenapi_torrent_seed_chance',
default=1.0,
help='Probability that peer will become a seeder.'
' (1.0 = 100%)'),
cfg.IntOpt('xenapi_torrent_seed_duration',
default=3600,
help='Number of seconds after downloading an image via'
' BitTorrent that it should be seeded for other peers.'),
cfg.IntOpt('xenapi_torrent_max_last_accessed',
default=86400,
help='Cached torrent files not accessed within this number of'
' seconds can be reaped'),
cfg.IntOpt('xenapi_torrent_listen_port_start',
default=6881,
help='Beginning of port range to listen on'),
cfg.IntOpt('xenapi_torrent_listen_port_end',
default=6891,
help='End of port range to listen on'),
cfg.IntOpt('xenapi_torrent_download_stall_cutoff',
default=600,
help='Number of seconds a download can remain at the same'
' progress percentage w/o being considered a stall'),
cfg.IntOpt('xenapi_torrent_max_seeder_processes_per_host',
default=1,
help='Maximum number of seeder processes to run concurrently'
' within a given dom0. (-1 = no limit)')
]
CONF = cfg.CONF
@@ -1138,6 +1108,20 @@ def _image_uses_bittorrent(context, instance):
return bittorrent
def _default_download_handler():
# TODO(sirp): This should be configurable like upload_handler
return importutils.import_object(
'nova.virt.xenapi.image.glance.GlanceStore')
def _choose_download_handler(context, instance):
if _image_uses_bittorrent(context, instance):
return importutils.import_object(
'nova.virt.xenapi.image.bittorrent.BittorrentStore')
else:
return _default_download_handler()
def _fetch_vhd_image(context, session, instance, image_id):
"""Tell glance to download an image and put the VHDs into the SR
@@ -1146,25 +1130,23 @@ def _fetch_vhd_image(context, session, instance, image_id):
LOG.debug(_("Asking xapi to fetch vhd image %s"), image_id,
instance=instance)
params = {}
if (_image_uses_bittorrent(context, instance) and
_add_torrent_url(instance, image_id, params)):
params.update({'image_id': image_id,
'uuid_stack': _make_uuid_stack(),
'sr_path': get_sr_path(session)})
_add_bittorrent_params(image_id, params)
try:
vdis = session.call_plugin_serialized_with_retry(
'bittorrent', 'download_vhd', CONF.glance_num_retries,
None, **params)
except exception.PluginRetriesExceeded:
raise exception.CouldNotFetchImage(image_id=image_id)
handler = _choose_download_handler(context, instance)
else:
download_handler = importutils.import_object(
'nova.virt.xenapi.image.glance.GlanceStore')
try:
vdis = handler.download_image(context, session, instance, image_id)
except Exception as e:
default_handler = _default_download_handler()
vdis = download_handler.download_image(
if handler == default_handler:
raise
LOG.exception(_("Download handler '%(handler)s' raised an"
" exception, falling back to default handler"
" '%(default_handler)s'") %
{'handler': handler,
'default_handler': default_handler})
vdis = default_handler.download_image(
context, session, instance, image_id)
sr_ref = safe_find_sr(session)
@@ -1183,76 +1165,6 @@ def _fetch_vhd_image(context, session, instance, image_id):
return vdis
_TORRENT_URL_FN = None # driver function to determine torrent URL to use
def _lookup_torrent_url_fn():
"""Load a "fetcher" func to get the right torrent URL via entrypoints."""
namespace = "nova.virt.xenapi.vm_utils"
name = "torrent_url"
eps = pkg_resources.iter_entry_points(namespace)
eps = [ep for ep in eps if ep.name == name]
x = len(eps)
if x == 0:
LOG.debug(_("No torrent URL fetcher extension found."))
return None
elif x > 1:
raise RuntimeError(_("Multiple torrent URL fetcher extension found. "
"Failing."))
ep = eps[0]
LOG.debug(_("Loading torrent URL fetcher from entry points %(ep)s"),
{'ep': ep})
fn = ep.load()
return fn
def _add_torrent_url(instance, image_id, params):
"""Add the torrent URL associated with the given image.
:param instance: instance ref
:param image_id: unique id of image
:param params: BT params dict
:returns: True if the URL could be obtained
"""
global _TORRENT_URL_FN
if not _TORRENT_URL_FN:
fn = _lookup_torrent_url_fn()
if fn is None:
LOG.debug(_("No torrent URL fetcher installed."))
_TORRENT_URL_FN = get_torrent_url # default
else:
_TORRENT_URL_FN = fn
try:
url = _TORRENT_URL_FN(instance, image_id)
params['torrent_url'] = url
return True
except Exception:
LOG.exception(_("Failed to get torrent URL for image %s") % image_id)
return False # fall back to using glance
def get_torrent_url(instance, image_id):
return urlparse.urljoin(CONF.xenapi_torrent_base_url,
"%s.torrent" % image_id)
def _add_bittorrent_params(image_id, params):
params['torrent_seed_duration'] = CONF.xenapi_torrent_seed_duration
params['torrent_seed_chance'] = CONF.xenapi_torrent_seed_chance
params['torrent_max_last_accessed'] = CONF.xenapi_torrent_max_last_accessed
params['torrent_listen_port_start'] = CONF.xenapi_torrent_listen_port_start
params['torrent_listen_port_end'] = CONF.xenapi_torrent_listen_port_end
params['torrent_download_stall_cutoff'] = \
CONF.xenapi_torrent_download_stall_cutoff
params['torrent_max_seeder_processes_per_host'] = \
CONF.xenapi_torrent_max_seeder_processes_per_host
def _get_vdi_chain_size(session, vdi_uuid):
"""Compute the total size of a VDI chain, starting with the specified
VDI UUID.

View File

@@ -67,7 +67,7 @@ xenapi_vmops_opts = [
help='The XenAPI VIF driver using XenServer Network APIs.'),
cfg.StrOpt('xenapi_image_upload_handler',
default='nova.virt.xenapi.image.glance.GlanceStore',
help='Object Store Driver used to handle image uploads.'),
help='Dom0 plugin driver used to handle image uploads.'),
]
CONF = cfg.CONF