-cinder (1:2013.2-0ubuntu1~cloud0) precise-havana; urgency=low
+cinder (1:2014.2~b2-0ubuntu1) utopic; urgency=medium
- * New upstream release for the Ubuntu Cloud Archive.
+ * New upstream release.
+ * Clean up dependencies:
+ - debian/control: Dropped python-d2to1, python-hp3parclient,
+ python-hplefthandclient, python-lockfile, python-amqplib
+ - debian/control: Add python-oslosphinx, python-requests,
+ python-hacking, python-oslo.db.
+ * debian/patches/fix-requirements.patch: Refreshed.
+
+ -- Chuck Short <zulcss@ubuntu.com> Thu, 24 Jul 2014 13:44:04 -0400
+
+cinder (1:2014.2~b1-0ubuntu2) utopic; urgency=medium
+
+ * SECURITY UPDATE: specify /etc/nova/rootwrap.conf for use with
+ nova-rootwrap
+ - CVE-2013-1068 (LP: #1185019)
+
+ -- Chuck Short <zulcss@ubuntu.com> Wed, 18 Jun 2014 11:37:45 -0400
+
+cinder (1:2014.2~b1-0ubuntu1) utopic; urgency=medium
+
+ * New upstream release.
+ * debian/control: Open up juno release
+ * debian/patches/fix-requirements.patch: Refreshed.
+
+ -- Chuck Short <zulcss@ubuntu.com> Thu, 12 Jun 2014 10:35:06 -0400
+
+cinder (1:2014.1-0ubuntu1) trusty; urgency=medium
+
+ * New upstream release (LP: #1299055).
+
+ -- Corey Bryant <corey.bryant@canonical.com> Wed, 16 Apr 2014 13:06:37 -0400
+
+cinder (1:2014.1~rc3-0ubuntu1) trusty; urgency=medium
+
+ * New release candidate (LP: #1299010).
+
+ -- Chuck Short <zulcss@ubuntu.com> Tue, 15 Apr 2014 09:02:40 -0400
+
+cinder (1:2014.1~rc2-0ubuntu1) trusty; urgency=medium
+
+ * New upstream release (LP: #1299010).
+
+ -- Chuck Short <zulcss@ubuntu.com> Mon, 07 Apr 2014 11:18:57 -0400
+
+cinder (1:2014.1~rc1-0ubuntu1) trusty; urgency=medium
+
+ * New upstream release (LP: #1299010).
+ * debian/patches/fixup-rbd-str-handling.patch: Dropped no longer needed.
+ * debian/patches/fix-requirements.patch: Rediffed.
+ * debian/control: Add python-oslo.messaging.
+
+ -- Chuck Short <zulcss@ubuntu.com> Fri, 28 Mar 2014 09:48:21 -0400
+
+cinder (1:2014.1~b3-0ubuntu3) trusty; urgency=medium
+
+ * d/p/fixup-rbd-str-handling.patch: Cherry pick fix from upstream
+ Gerrit to resolve issue deleting Ceph volumes and snapshots
+ (LP: #1292433).
+ * d/control,rules: Use upstream run_tests.sh wrapper to execute unit tests,
+ add subunit to BD's to ensure output is correctly formatted.
+
+ -- James Page <james.page@ubuntu.com> Fri, 14 Mar 2014 11:19:16 +0000
+
+cinder (1:2014.1~b3-0ubuntu2) trusty; urgency=medium
+
+ * d/cinder-common.postinst: Correct use of getent (LP: #1224275).
+ * d/cinder-common.postinst: Tidy detection of local sqlite database use
+ for db sync operations (LP: #1290423).
+
+ -- James Page <james.page@ubuntu.com> Thu, 13 Mar 2014 10:11:20 +0000
+
+cinder (1:2014.1~b3-0ubuntu1) trusty; urgency=medium
+
+ [ Chuck Short ]
+ * debian/patches/fix-requirements.patch: Refreshed.
+ * debian/control: Bump python-keystoneclient to 0.4.2.
+ * debian/patches/skip-tests.patch: Temporarily skip
+ testlefthand tests since the needed python library hasnt been
+ packaged yet.
+
+ [ James Page ]
+ * d/p/fix-requirements.patch: Refreshed.
+ * d/control,d/p/series/skip-tests.patch: Add BD on python-hplefthandclient,
+ bump version requirement on python-hp3parclient to >= 3.0.0 and drop
+ patch that skips hplefthandclient tests.
+
+ [ Corey Bryant ]
+ * New upstream release.
+
+ -- Corey Bryant <corey.bryant@canonical.com> Thu, 06 Mar 2014 13:16:02 -0500
+
+cinder (1:2014.1~b2-0ubuntu1) trusty; urgency=low
+
+ [ James Page ]
+ * d/control: Add versioned dependency python-six >= 1.4.1 (LP: #1259203).
+ * d/p/*: Refreshed.
+
+ [Chuck Short]
+ * New upstream release.
+ * debian/control: Add python-taskflow as a dependency.
+ * debian/control: Add python-oslo.rootwrap as a dependency.
+
+ -- Chuck Short <zulcss@ubuntu.com> Thu, 23 Jan 2014 12:41:54 -0500
+
+cinder (1:2014.1~b1-0ubuntu1) trusty; urgency=low
+
+ [ Chuck Short ]
+ * New upstream release.
+ * debian/control:
+ - Open icehouse release.
+ - Add python-rtslib as a dependency.
+ * debian/patches/fix-sqlalchemy-requirements.patch: Dropped no longer needed.
+ * debian/patches/fix-babel-requirements.patch: Dropped no longer needed.
+ * debian/paches/fix-requirements.patch: Fixed up requirements.txt for
+ dependencies we have in Ubuntu.
+ * debian/cinder-common.install: Add "cinder-rtstool".
+
+ [ James Page ]
+ * debian/patches/*: Refresh.
+
+ -- Chuck Short <zulcss@ubuntu.com> Thu, 05 Dec 2013 21:30:40 -0500
+
+cinder (1:2013.2-0ubuntu1) saucy; urgency=low
+
+ * New upstream release (LP: #1236462).
- -- James Page <james.page@ubuntu.com> Wed, 16 Oct 2013 11:27:06 +0100
+ -- Chuck Short <zulcss@ubuntu.com> Thu, 17 Oct 2013 10:00:43 -0400
cinder (1:2013.2~rc3-0ubuntu1) saucy; urgency=low
etc/cinder/rootwrap.conf etc/cinder
usr/bin/cinder-manage
usr/bin/cinder-rootwrap
-usr/bin/cinder-rpc-zmq-receiver
+usr/bin/cinder-rtstool
#!/bin/sh -e
if [ "$1" = "configure" ]; then
- if ! getenv group cinder > /dev/null 2>&1; then
+ if ! getent group cinder > /dev/null 2>&1; then
addgroup --system cinder >/dev/null
fi
- if ! getenv passwd cinder > /dev/null 2>&1; then
+ if ! getent passwd cinder > /dev/null 2>&1; then
adduser --system --home /var/lib/cinder --ingroup cinder --no-create-home \
--shell /bin/false cinder
fi
chmod 0755 /etc/cinder/rootwrap.d
chown root:root /etc/cinder/rootwrap.conf
- if ! grep -q sql_connection /etc/cinder/cinder.conf
+ if ! grep -qE "^(sql_)?connection.*" /etc/cinder/cinder.conf || \
+ grep -qE "^(sql_)?connection.*sqlite.*" /etc/cinder/cinder.conf
then
su -s /bin/sh -c 'cinder-manage db sync' cinder
fi
debian/cinder_tgt.conf etc/tgt/conf.d
etc/cinder/rootwrap.d/volume.filters /etc/cinder/rootwrap.d
-usr/bin/cinder-clear-rabbit-queues
usr/bin/cinder-volume
usr/bin/cinder-volume-usage-audit
Defaults:cinder !requiretty
-cinder ALL = (root) NOPASSWD: /usr/bin/cinder-rootwrap
+cinder ALL = (root) NOPASSWD: /usr/bin/cinder-rootwrap /etc/cinder/rootwrap.conf
Maintainer: Chuck Short <zulcss@ubuntu.com>
Build-Depends: debhelper (>= 8.0.0), python-all (>= 2.6)
Build-Depends-Indep:
- python-amqplib (>= 0.6.1),
python-anyjson (>= 0.3.3),
- python-babel,
+ python-babel (>= 1.3),
python-coverage,
- python-d2to1,
python-eventlet (>= 0.13.0),
python-fixtures (>= 0.3.14),
python-glanceclient (>= 1:0.9.0),
python-greenlet (>= 0.3.2),
- python-hp3parclient (>= 2.0.0),
+ python-hacking,
python-iso8601,
- python-keystoneclient (>= 1:0.3.0),
+ python-keystoneclient (>= 1:0.4.2),
+ python-keystonemiddleware (>= 1.0.0),
python-kombu (>= 2.5.12),
- python-lockfile,
python-lxml (>= 2.3),
- python-migrate,
+ python-migrate (>= 0.9.1),
python-mock,
python-mox,
- python-mysqldb,
+ python-mysqldb ,
python-netaddr,
- python-novaclient (>= 1:2.15.0),
- python-oslo.config (>= 1:1.1.0),
- python-paramiko (>= 1.8),
+ python-novaclient (>= 2.17.0),
+ python-oslo.config (>= 1.2.1),
+ python-oslo.db,
+ python-oslo.rootwrap,
+ python-oslo.messaging,
+ python-oslosphinx,
+ python-paramiko (>= 1.13),
python-paste,
- python-pastedeploy,
- python-pbr (>= 0.5.21),
+ python-pastedeploy (>= 1.5),
+ python-requests,
+ python-pbr (>= 0.6), python-pbr (<< 0.7) | python-pbr (>> 0.7), python-pbr (<< 1.0),
python-routes,
python-setuptools,
- python-six,
+ python-six (>= 1.7.0),
python-sphinx,
- python-sqlalchemy (>= 0.8.2),
- python-stevedore (>= 0.10),
+ python-sqlalchemy (>= 0.8.4), python-sqlalchemy (<< 0.9.5) | python-sqlalchemy (>> 0.9.5), python-sqlalchemy (<= 0.9.99),
+ python-stevedore (>= 0.14),
python-suds,
- python-swiftclient (>= 1:1.5),
+ python-swiftclient (>= 2.0.2),
+ python-taskflow,
python-testtools (>= 0.9.32),
python-webob (>= 1.2.3),
+ subunit,
testrepository (>= 0.0.17)
Standards-Version: 3.9.4
Homepage: http://launchpad.net/cinder
-Vcs-Browser: http://bazaar.launchpad.net/~ubuntu-server-dev/cinder/havana/files
-Vcs-Bzr: https://code.launchpad.net/~ubuntu-server-dev/cinder/havana
+Vcs-Browser: http://bazaar.launchpad.net/~ubuntu-server-dev/cinder/juno/files
+Vcs-Bzr: https://code.launchpad.net/~ubuntu-server-dev/cinder/juno
XS-Testsuite: autopkgtest
Package: python-cinder
Depends:
python-amqplib (>= 0.6.1),
python-anyjson (>= 0.3.3),
- python-babel,
+ python-babel (>= 1.3),
python-eventlet (>= 0.13.0),
python-glanceclient (>= 1:0.9.0),
python-greenlet (>= 0.3.2),
- python-importlib,
- python-iso8601,
+ python-iso8601 (>= 0.1.9) ,
python-keystoneclient (>= 1:0.3.0),
+ python-keystonemiddleware (>= 1.0.0),
python-kombu (>= 2.5.12),
python-lockfile,
python-lxml (>= 2.3),
- python-migrate,
- python-mysqldb,
- python-netaddr,
+ python-migrate (>= 0.9.1),
+ python-netaddr (>= 0.7.6),
python-novaclient (>= 1:2.12.0),
- python-oslo.config (>= 1:1.1.0),
- python-paramiko (>= 1.8),
+ python-oslo.config (>= 1:1.2.1),
+ python-oslo.messaging,
+ python-paramiko (>= 1.13),
python-paste,
- python-pastedeploy,
- python-routes,
- python-six,
- python-sqlalchemy (>= 0.8.2),
- python-stevedore (>= 0.10),
+ python-pastedeploy (>= 1.5),
+ python-routes (>= 1.12.3), python-routes (<< 2.0) | python-routes (> 2.0),
+ python-six (>= 1.7.0),
+ python-sqlalchemy (>= 0.8.4), python-sqlalchemy (<< 0.9.5) | python-sqlalchemy (>> 0.9.5), python-sqlalchemy (<= 0.9.99),
+ python-stevedore (>= 0.14),
python-suds,
- python-swiftclient (>= 1:1.5),
+ python-swiftclient (>= 2.0.2),
+ python-taskflow,
+ python-rtslib-fb (>= 2.1.39),
python-webob (>= 1.2.3),
${misc:Depends},
${python:Depends}
+++ /dev/null
-From fd7e9dd59ffa346bed5a11e5312f4bb1bf114ab4 Mon Sep 17 00:00:00 2001
-From: Dmitry Borodaenko <angdraug@gmail.com>
-Date: Wed, 27 Nov 2013 14:33:00 -0800
-Subject: [PATCH] Do not clone non-raw images in rbd backend
-
-RBD backend only supports booting from images in raw format. A volume
-that was cloned from an image in any other format is not bootable. The
-RBD driver will consider non-raw images to be uncloneable to trigger
-automatic conversion to raw format.
-
-Includes conversion of the corresponding unit test to use mock (instead
-of mox) and expanded comments and error messages from patchset #58893 by
-Edward Hope-Morley.
-
-Change-Id: I5725d2f7576bc1b3e9b874ba944ad17d33a6e2cb
-Closes-Bug: #1246219
-Closes-Bug: #1247998
----
- cinder/tests/test_gpfs.py | 6 +-
- cinder/tests/test_netapp_nfs.py | 12 +-
- cinder/tests/test_rbd.py | 168 +++++++++++++++++---------
- cinder/tests/test_volume.py | 2 +-
- cinder/volume/driver.py | 7 +-
- cinder/volume/drivers/gpfs.py | 2 +-
- cinder/volume/drivers/lvm.py | 2 +-
- cinder/volume/drivers/netapp/nfs.py | 2 +-
- cinder/volume/drivers/rbd.py | 14 ++-
- cinder/volume/drivers/scality.py | 2 +-
- cinder/volume/flows/create_volume/__init__.py | 2 +-
- 11 files changed, 144 insertions(+), 75 deletions(-)
-
-diff --git a/cinder/tests/test_gpfs.py b/cinder/tests/test_gpfs.py
-index 4fdb788..1f47c6b 100644
---- a/cinder/tests/test_gpfs.py
-+++ b/cinder/tests/test_gpfs.py
-@@ -288,7 +288,8 @@ class GPFSDriverTestCase(test.TestCase):
- CONF.gpfs_images_share_mode = 'copy_on_write'
- self.driver.clone_image(volume,
- None,
-- self.image_id)
-+ self.image_id,
-+ {})
-
- self.assertTrue(os.path.exists(volumepath))
- self.volume.delete_volume(self.context, volume['id'])
-@@ -309,7 +310,8 @@ class GPFSDriverTestCase(test.TestCase):
- CONF.gpfs_images_share_mode = 'copy'
- self.driver.clone_image(volume,
- None,
-- self.image_id)
-+ self.image_id,
-+ {})
-
- self.assertTrue(os.path.exists(volumepath))
- self.volume.delete_volume(self.context, volume['id'])
-diff --git a/cinder/tests/test_netapp_nfs.py b/cinder/tests/test_netapp_nfs.py
-index 950efc8..042280e 100644
---- a/cinder/tests/test_netapp_nfs.py
-+++ b/cinder/tests/test_netapp_nfs.py
-@@ -469,7 +469,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
- drv._post_clone_image(volume)
-
- mox.ReplayAll()
-- drv. clone_image(volume, ('image_location', None), 'image_id')
-+ drv.clone_image(volume, ('image_location', None), 'image_id', {})
- mox.VerifyAll()
-
- def get_img_info(self, format):
-@@ -493,7 +493,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
-
- mox.ReplayAll()
- (prop, cloned) = drv. clone_image(
-- volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id')
-+ volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id', {})
- mox.VerifyAll()
- if not cloned and not prop['provider_location']:
- pass
-@@ -529,7 +529,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
-
- mox.ReplayAll()
- drv. clone_image(
-- volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id')
-+ volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id', {})
- mox.VerifyAll()
-
- def test_clone_image_cloneableshare_notraw(self):
-@@ -566,7 +566,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
-
- mox.ReplayAll()
- drv. clone_image(
-- volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
-+ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id', {})
- mox.VerifyAll()
-
- def test_clone_image_file_not_discovered(self):
-@@ -605,7 +605,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
-
- mox.ReplayAll()
- vol_dict, result = drv. clone_image(
-- volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
-+ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id', {})
- mox.VerifyAll()
- self.assertFalse(result)
- self.assertFalse(vol_dict['bootable'])
-@@ -652,7 +652,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
-
- mox.ReplayAll()
- vol_dict, result = drv. clone_image(
-- volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
-+ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id', {})
- mox.VerifyAll()
- self.assertFalse(result)
- self.assertFalse(vol_dict['bootable'])
-diff --git a/cinder/tests/test_rbd.py b/cinder/tests/test_rbd.py
-index 60de09b..054bbb5 100644
---- a/cinder/tests/test_rbd.py
-+++ b/cinder/tests/test_rbd.py
-@@ -34,6 +34,7 @@ from cinder.tests.test_volume import DriverTestCase
- from cinder import units
- from cinder.volume import configuration as conf
- import cinder.volume.drivers.rbd as driver
-+from cinder.volume.flows import create_volume
-
-
- LOG = logging.getLogger(__name__)
-@@ -247,7 +248,8 @@ class RBDTestCase(test.TestCase):
- self.assertRaises(exception.ImageUnacceptable,
- self.driver._parse_location,
- loc)
-- self.assertFalse(self.driver._is_cloneable(loc))
-+ self.assertFalse(
-+ self.driver._is_cloneable(loc, {'disk_format': 'raw'}))
-
- def test_cloneable(self):
- self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
-@@ -264,12 +266,14 @@ class RBDTestCase(test.TestCase):
-
- self.mox.ReplayAll()
-
-- self.assertTrue(self.driver._is_cloneable(location))
-+ self.assertTrue(
-+ self.driver._is_cloneable(location, {'disk_format': 'raw'}))
-
- def test_uncloneable_different_fsid(self):
- self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
- location = 'rbd://def/pool/image/snap'
-- self.assertFalse(self.driver._is_cloneable(location))
-+ self.assertFalse(
-+ self.driver._is_cloneable(location, {'disk_format': 'raw'}))
-
- def test_uncloneable_unreadable(self):
- self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
-@@ -284,7 +288,16 @@ class RBDTestCase(test.TestCase):
-
- self.mox.ReplayAll()
-
-- self.assertFalse(self.driver._is_cloneable(location))
-+ self.assertFalse(
-+ self.driver._is_cloneable(location, {'disk_format': 'raw'}))
-+
-+ def test_uncloneable_bad_format(self):
-+ self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
-+ location = 'rbd://abc/pool/image/snap'
-+ formats = ['qcow2', 'vmdk', 'vdi']
-+ for f in formats:
-+ self.assertFalse(
-+ self.driver._is_cloneable(location, {'disk_format': f}))
-
- def _copy_image(self):
- @contextlib.contextmanager
-@@ -504,26 +517,37 @@ class ManagedRBDTestCase(DriverTestCase):
- super(ManagedRBDTestCase, self).setUp()
- fake_image.stub_out_image_service(self.stubs)
- self.volume.driver.set_initialized()
-+ self.called = []
-
-- def _clone_volume_from_image(self, expected_status,
-- clone_works=True):
-+ def _create_volume_from_image(self, expected_status, raw=False,
-+ clone_error=False):
- """Try to clone a volume from an image, and check the status
- afterwards.
-+
-+ NOTE: if clone_error is True we force the image type to raw otherwise
-+ clone_image is not called
- """
-- def fake_clone_image(volume, image_location, image_id):
-- return {'provider_location': None}, True
-+ def mock_clone_image(volume, image_location, image_id, image_meta):
-+ self.called.append('clone_image')
-+ if clone_error:
-+ raise exception.CinderException()
-+ else:
-+ return {'provider_location': None}, True
-
-- def fake_clone_error(volume, image_location, image_id):
-- raise exception.CinderException()
-+ if clone_error:
-+ raw = True
-
-- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
-- if clone_works:
-- self.stubs.Set(self.volume.driver, 'clone_image', fake_clone_image)
-+ # See tests.image.fake for image types.
-+ if raw:
-+ expected_calls = ['clone_image']
-+ image_id = '155d900f-4e14-4e4c-a73d-069cbf4541e6'
- else:
-- self.stubs.Set(self.volume.driver, 'clone_image', fake_clone_error)
-+ expected_calls = ['clone_image', 'create_volume',
-+ 'copy_image_to_volume']
-+ image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
-
-- image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
- volume_id = 1
-+
- # creating volume testdata
- db.volume_create(self.context,
- {'id': volume_id,
-@@ -533,58 +557,88 @@ class ManagedRBDTestCase(DriverTestCase):
- 'status': 'creating',
- 'instance_uuid': None,
- 'host': 'dummy'})
-- try:
-- if clone_works:
-- self.volume.create_volume(self.context,
-- volume_id,
-- image_id=image_id)
-- else:
-- self.assertRaises(exception.CinderException,
-- self.volume.create_volume,
-- self.context,
-- volume_id,
-- image_id=image_id)
--
-- volume = db.volume_get(self.context, volume_id)
-- self.assertEqual(volume['status'], expected_status)
-- finally:
-- # cleanup
-- db.volume_destroy(self.context, volume_id)
-+
-+ with mock.patch.object(self.volume.driver, 'create_volume') as \
-+ mock_create_volume:
-+ with mock.patch.object(self.volume.driver, 'clone_image',
-+ mock_clone_image):
-+ with mock.patch.object(create_volume.CreateVolumeFromSpecTask,
-+ '_copy_image_to_volume') as \
-+ mock_copy_image_to_volume:
-+ self.volume.driver._is_cloneable = mock.Mock()
-+ self.volume.driver._is_cloneable.return_value = True
-+
-+ try:
-+ if not clone_error:
-+ self.volume.create_volume(self.context,
-+ volume_id,
-+ image_id=image_id)
-+ else:
-+ self.assertRaises(exception.CinderException,
-+ self.volume.create_volume,
-+ self.context,
-+ volume_id,
-+ image_id=image_id)
-+
-+ volume = db.volume_get(self.context, volume_id)
-+ self.assertEqual(volume['status'], expected_status)
-+ finally:
-+ # cleanup
-+ db.volume_destroy(self.context, volume_id)
-+
-+ if raw:
-+ self.assertEquals(self.called, ['clone_image'])
-+
-+ mock_create_volume.assert_called()
-+ mock_copy_image_to_volume.assert_called()
-
- def test_create_vol_from_image_status_available(self):
-- """Verify that before cloning, an image is in the available state."""
-- self._clone_volume_from_image('available', True)
-+ """Clone raw image then verify volume is in available state."""
-+ self._create_volume_from_image('available', raw=True)
-+
-+ def test_create_vol_from_non_raw_image_status_available(self):
-+ """Clone non-raw image then verify volume is in available state."""
-+ self._create_volume_from_image('available', raw=False)
-
- def test_create_vol_from_image_status_error(self):
-- """Verify that before cloning, an image is in the available state."""
-- self._clone_volume_from_image('error', False)
-+ """Clone raw image with failure then verify volume is in error
-+ state.
-+ """
-+ self._create_volume_from_image('error', raw=True, clone_error=True)
-
- def test_clone_image(self):
-- # Test Failure Case(s)
-- expected = ({}, False)
-+ driver = self.volume.driver
-
-- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: False)
-- image_loc = (object(), object())
-- actual = self.volume.driver.clone_image(object(), image_loc, object())
-- self.assertEqual(expected, actual)
-+ # Test uncloneable case(s)
-+ with mock.patch.object(driver, '_is_cloneable',
-+ lambda *args: False) as mock_is_cloneable:
-+ image_loc = (mock.Mock(), mock.Mock())
-+ actual = driver.clone_image(mock.Mock(), image_loc,
-+ mock.Mock(), {})
-+ self.assertEqual(({}, False), actual)
-
-- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
-- self.assertEqual(expected,
-- self.volume.driver.clone_image(object(), None, None))
-+ self.assertEqual(({}, False),
-+ driver.clone_image(object(), None, None, {}))
-
-- # Test Success Case(s)
-+ # Test success case(s)
- expected = ({'provider_location': None}, True)
--
-- self.stubs.Set(self.volume.driver, '_parse_location',
-- lambda x: ('a', 'b', 'c', 'd'))
--
-- self.stubs.Set(self.volume.driver, '_clone', lambda *args: None)
-- self.stubs.Set(self.volume.driver, '_resize', lambda *args: None)
-- actual = self.volume.driver.clone_image(object(), image_loc, object())
-- self.assertEqual(expected, actual)
-+ mpo = mock.patch.object
-+ with mpo(driver, '_is_cloneable', lambda *args: True):
-+ with mpo(driver, '_parse_location',
-+ lambda x: ('a', 'b', 'c', 'd')):
-+ with mpo(driver, '_clone') as mock_clone:
-+ with mpo(driver, '_resize') as mock_resize:
-+ actual = driver.clone_image(mock.Mock(), image_loc,
-+ mock.Mock(),
-+ {'disk_format': 'raw'})
-+ self.assertEqual(expected, actual)
-+ mock_clone.assert_called()
-+ mock_resize.assert_called()
-
- def test_clone_success(self):
-- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
-- self.stubs.Set(self.volume.driver, 'clone_image', lambda a, b, c: True)
-+ self.stubs.Set(self.volume.driver, '_is_cloneable', lambda *args: True)
-+ self.stubs.Set(self.volume.driver, 'clone_image',
-+ lambda a, b, c, d: True)
- image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
-- self.assertTrue(self.volume.driver.clone_image({}, image_id, image_id))
-+ self.assertTrue(self.volume.driver.clone_image({},
-+ image_id, image_id, {}))
-diff --git a/cinder/tests/test_volume.py b/cinder/tests/test_volume.py
-index 4a0d475..928799f 100644
---- a/cinder/tests/test_volume.py
-+++ b/cinder/tests/test_volume.py
-@@ -1216,7 +1216,7 @@ class VolumeTestCase(BaseVolumeTestCase):
- def fake_fetch_to_raw(ctx, image_service, image_id, path, size=None):
- pass
-
-- def fake_clone_image(volume_ref, image_location, image_id):
-+ def fake_clone_image(volume_ref, image_location, image_id, image_meta):
- return {'provider_location': None}, True
-
- dst_fd, dst_path = tempfile.mkstemp()
-diff --git a/cinder/volume/driver.py b/cinder/volume/driver.py
-index 21dd12b..dd2be3c 100644
---- a/cinder/volume/driver.py
-+++ b/cinder/volume/driver.py
-@@ -396,7 +396,7 @@ class VolumeDriver(object):
- connector.disconnect_volume(attach_info['conn']['data'],
- attach_info['device'])
-
-- def clone_image(self, volume, image_location, image_id):
-+ def clone_image(self, volume, image_location, image_id, image_meta):
- """Create a volume efficiently from an existing image.
-
- image_location is a string whose format depends on the
-@@ -407,6 +407,11 @@ class VolumeDriver(object):
- It can be used by the driver to introspect internal
- stores or registry to do an efficient image clone.
-
-+ image_meta is a dictionary that includes 'disk_format' (e.g.
-+ raw, qcow2) and other image attributes that allow drivers to
-+ decide whether they can clone the image without first requiring
-+ conversion.
-+
- Returns a dict of volume properties eg. provider_location,
- boolean indicating whether cloning occurred
- """
-diff --git a/cinder/volume/drivers/gpfs.py b/cinder/volume/drivers/gpfs.py
-index 9a1a397..8792ad8 100644
---- a/cinder/volume/drivers/gpfs.py
-+++ b/cinder/volume/drivers/gpfs.py
-@@ -463,7 +463,7 @@ class GPFSDriver(driver.VolumeDriver):
- return '100M'
- return '%sG' % size_in_g
-
-- def clone_image(self, volume, image_location, image_id):
-+ def clone_image(self, volume, image_location, image_id, image_meta):
- return self._clone_image(volume, image_location, image_id)
-
- def _is_cloneable(self, image_id):
-diff --git a/cinder/volume/drivers/lvm.py b/cinder/volume/drivers/lvm.py
-index 094436f..c6023eb 100644
---- a/cinder/volume/drivers/lvm.py
-+++ b/cinder/volume/drivers/lvm.py
-@@ -322,7 +322,7 @@ class LVMVolumeDriver(driver.VolumeDriver):
- finally:
- self.delete_snapshot(temp_snapshot)
-
-- def clone_image(self, volume, image_location, image_id):
-+ def clone_image(self, volume, image_location, image_id, image_meta):
- return None, False
-
- def backup_volume(self, context, backup, backup_service):
-diff --git a/cinder/volume/drivers/netapp/nfs.py b/cinder/volume/drivers/netapp/nfs.py
-index 602a1dc..9463137 100644
---- a/cinder/volume/drivers/netapp/nfs.py
-+++ b/cinder/volume/drivers/netapp/nfs.py
-@@ -374,7 +374,7 @@ class NetAppNFSDriver(nfs.NfsDriver):
- LOG.warning(_('Exception during deleting %s'), ex.__str__())
- return False
-
-- def clone_image(self, volume, image_location, image_id):
-+ def clone_image(self, volume, image_location, image_id, image_meta):
- """Create a volume efficiently from an existing image.
-
- image_location is a string whose format depends on the
-diff --git a/cinder/volume/drivers/rbd.py b/cinder/volume/drivers/rbd.py
-index 775ab16..7ed59d0 100644
---- a/cinder/volume/drivers/rbd.py
-+++ b/cinder/volume/drivers/rbd.py
-@@ -706,7 +706,7 @@ class RBDDriver(driver.VolumeDriver):
- with RADOSClient(self) as client:
- return client.cluster.get_fsid()
-
-- def _is_cloneable(self, image_location):
-+ def _is_cloneable(self, image_location, image_meta):
- try:
- fsid, pool, image, snapshot = self._parse_location(image_location)
- except exception.ImageUnacceptable as e:
-@@ -718,6 +718,13 @@ class RBDDriver(driver.VolumeDriver):
- LOG.debug(reason)
- return False
-
-+ if image_meta['disk_format'] != 'raw':
-+ reason = _("rbd image clone requires image format to be "
-+ "'raw' but image {0} is '{1}'").format(
-+ image_location, image_meta['disk_format'])
-+ LOG.debug(reason)
-+ return False
-+
- # check that we can read the image
- try:
- with RBDVolumeProxy(self, image,
-@@ -730,9 +737,10 @@ class RBDDriver(driver.VolumeDriver):
- dict(loc=image_location, err=e))
- return False
-
-- def clone_image(self, volume, image_location, image_id):
-+ def clone_image(self, volume, image_location, image_id, image_meta):
- image_location = image_location[0] if image_location else None
-- if image_location is None or not self._is_cloneable(image_location):
-+ if image_location is None or not self._is_cloneable(
-+ image_location, image_meta):
- return ({}, False)
- prefix, pool, image, snapshot = self._parse_location(image_location)
- self._clone(volume, pool, image, snapshot)
-diff --git a/cinder/volume/drivers/scality.py b/cinder/volume/drivers/scality.py
-index 4cf49c6..abd6c29 100644
---- a/cinder/volume/drivers/scality.py
-+++ b/cinder/volume/drivers/scality.py
-@@ -250,7 +250,7 @@ class ScalityDriver(driver.VolumeDriver):
- image_meta,
- self.local_path(volume))
-
-- def clone_image(self, volume, image_location, image_id):
-+ def clone_image(self, volume, image_location, image_id, image_meta):
- """Create a volume efficiently from an existing image.
-
- image_location is a string whose format depends on the
-diff --git a/cinder/volume/flows/create_volume/__init__.py b/cinder/volume/flows/create_volume/__init__.py
-index bb7acd3..e5aa371 100644
---- a/cinder/volume/flows/create_volume/__init__.py
-+++ b/cinder/volume/flows/create_volume/__init__.py
-@@ -1438,7 +1438,7 @@ class CreateVolumeFromSpecTask(base.CinderTask):
- # dict containing provider_location for cloned volume
- # and clone status.
- model_update, cloned = self.driver.clone_image(
-- volume_ref, image_location, image_id)
-+ volume_ref, image_location, image_id, image_meta)
- if not cloned:
- # TODO(harlowja): what needs to be rolled back in the clone if this
- # volume create fails?? Likely this should be a subflow or broken
---
-1.8.5.1
-
+++ /dev/null
-Author: Revert new babel requirements.
-Author: Chuck Short <zulcss@ubuntu.com>
-Forwarded: No, https://review.openstack.org/#/c/48739/
-diff --git a/requirements.txt b/requirements.txt
-index 219962d..01ec2e6 100644
---- a/requirements.txt
-+++ b/requirements.txt
-@@ -2,7 +2,7 @@ pbr>=0.5.21,<1.0
- amqplib>=0.6.1
- anyjson>=0.3.3
- argparse
--Babel>=1.3
-+Babel>=0.9.6
- eventlet>=0.13.0
- greenlet>=0.3.2
- iso8601>=0.1.8
-
+++ /dev/null
-diff -Naurp cinder-2013.2.rc1.orig/requirements.txt cinder-2013.2.rc1/requirements.txt
---- cinder-2013.2.rc1.orig/requirements.txt 2013-10-04 05:11:50.000000000 -0400
-+++ cinder-2013.2.rc1/requirements.txt 2013-10-04 08:33:32.072164528 -0400
-@@ -20,7 +20,7 @@ python-novaclient>=2.15.0
- python-swiftclient>=1.5
- Routes>=1.12.3
- six>=1.4.1
--SQLAlchemy>=0.7.8,<=0.7.99
-+SQLAlchemy>=0.7.8,<=0.8.99
- sqlalchemy-migrate>=0.7.2
- stevedore>=0.10
- suds>=0.4
+++ /dev/null
-fix-sqlalchemy-requirements.patch
-fix-babel-requirements.patch
-MIRA-Do-not-clone-non-raw-images-in-rbd-backend.patch
ifeq (,$(findstring nocheck, $(DEB_BUILD_OPTIONS)))
override_dh_auto_test:
- testr init && testr run
+ ./run_tests.sh -N -P
endif
override_dh_install:
+++ /dev/null
-From 9904118c6d3d130624eaec31878649e41efd1bc2 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?P=C3=A1draig=20Brady?= <P@draigBrady.com>
-Date: Fri, 21 Sep 2012 13:33:26 +0100
-Subject: [PATCH] Ensure we don't access the net when building docs
-
-(Note, this has not been sent upstream)
----
- doc/source/conf.py | 1 -
- 1 file changed, 1 deletion(-)
-
-diff --git a/doc/source/conf.py b/doc/source/conf.py
-index e27d458..7a2bf1a 100644
---- a/doc/source/conf.py
-+++ b/doc/source/conf.py
-@@ -29,7 +29,6 @@ sys.path.insert(0, os.path.abspath('./'))
- # or your custom ones.
-
- extensions = ['sphinx.ext.autodoc',
-- 'sphinx.ext.intersphinx',
- 'ext.cinder_todo',
- 'sphinx.ext.coverage',
- 'sphinx.ext.pngmath',
-From 6ed6db76f6e6b1a579d07c3db63283d444385c14 Mon Sep 17 00:00:00 2001
+From 302c285b0193bcae47aaaaec49f51340b4fbe6e7 Mon Sep 17 00:00:00 2001
From: Eric Harney <eharney@redhat.com>
Date: Thu, 13 Jun 2013 17:50:12 -0400
Subject: [PATCH] Remove runtime dep on python-pbr, python-d2to1
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/cinder/version.py b/cinder/version.py
-index ad1ebc0..b4f6fd8 100644
+index e3dbd70..ab8f888 100644
--- a/cinder/version.py
+++ b/cinder/version.py
-@@ -14,12 +14,22 @@
+@@ -12,12 +12,22 @@
# License for the specific language governing permissions and limitations
# under the License.
--- /dev/null
+From 15cc208adf811d9a83e8f820fb4be775c5db9a00 Mon Sep 17 00:00:00 2001
+From: Eric Harney <eharney@redhat.com>
+Date: Thu, 27 Mar 2014 14:24:57 -0400
+Subject: [PATCH] Revert "Switch over to oslosphinx"
+
+This reverts commit ea7d4a599224b5d0c7674d03993bbe72c49f0d51.
+---
+ doc/source/conf.py | 2 +-
+ test-requirements.txt | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/doc/source/conf.py b/doc/source/conf.py
+index ac2eadc..238a922 100644
+--- a/doc/source/conf.py
++++ b/doc/source/conf.py
+@@ -34,7 +34,7 @@ extensions = ['sphinx.ext.autodoc',
+ 'sphinx.ext.pngmath',
+ 'sphinx.ext.ifconfig',
+ 'sphinx.ext.graphviz',
+- 'oslosphinx',
++ 'oslo.sphinx',
+ ]
+
+ # autodoc generation is a bit aggressive and a nuisance
+diff --git a/test-requirements.txt b/test-requirements.txt
+index 56746da..15308b9 100644
+--- a/test-requirements.txt
++++ b/test-requirements.txt
+@@ -13,4 +13,4 @@ sphinx>=1.1.2,<1.2
+ python-subunit>=0.0.18
+ testtools>=0.9.34
+ testrepository>=0.0.18
+-oslosphinx
++oslo.sphinx
+++ /dev/null
-From c8b9384ccf0e9c69f35978f3abbb5e5d5638d859 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?P=C3=A1draig=20Brady?= <P@draigBrady.com>
-Date: Wed, 24 Oct 2012 13:44:37 +0100
-Subject: [PATCH] Use updated parallel install versions of epel package
-
-Use sqlalchemy >= 0.6.3 WebOb >= 1.2 Routes >= 1.12.3 PasteDeploy >= 1.5.0
-and depend on the parallel installable
-versions of these packages to satisfy those requirements.
-
-Delve into pkg_resources a little to get it to modify sys.path,
-so that our parallel installed egg takes precedence over the
-system default module versions.
----
- bin/cinder-manage | 4 +++-
- cinder/__init__.py | 30 ++++++++++++++++++++++++++++++
- cinder/db/sqlalchemy/migration.py | 7 ++++++-
- 3 files changed, 39 insertions(+), 2 deletions(-)
-
-diff --git a/bin/cinder-manage b/bin/cinder-manage
-index 3c05d77..9474e2e 100755
---- a/bin/cinder-manage
-+++ b/bin/cinder-manage
-@@ -62,7 +62,6 @@ import sys
-
- from oslo.config import cfg
-
--
- # If ../cinder/__init__.py exists, add ../ to Python search path, so that
- # it will override what happens to be installed in /usr/(local/)lib/python...
- POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
-@@ -84,6 +83,9 @@ from cinder.openstack.common import uuidutils
- from cinder import utils
- from cinder import version
-
-+from sqlalchemy import create_engine, MetaData, Table
-+from sqlalchemy.ext.declarative import declarative_base
-+from sqlalchemy.orm import sessionmaker
-
- CONF = cfg.CONF
-
-diff --git a/cinder/__init__.py b/cinder/__init__.py
-index d765b08..720320e 100644
---- a/cinder/__init__.py
-+++ b/cinder/__init__.py
-@@ -30,3 +30,33 @@
- .. moduleauthor:: Manish Singh <yosh@gimp.org>
- .. moduleauthor:: Andy Smith <andy@anarkystic.com>
- """
-+
-+import sys
-+import pkg_resources
-+
-+# If there is a conflicting non egg module,
-+# i.e. an older standard system module installed,
-+# then replace it with this requirement
-+def replace_dist(requirement):
-+ try:
-+ return pkg_resources.require(requirement)
-+ except pkg_resources.VersionConflict:
-+ e = sys.exc_info()[1]
-+ dist=e.args[0]
-+ req=e.args[1]
-+ if dist.key == req.key and not dist.location.endswith('.egg'):
-+ del pkg_resources.working_set.by_key[dist.key]
-+ # We assume there is no need to adjust sys.path
-+ # and the associated pkg_resources.working_set.entries
-+ return pkg_resources.require(requirement)
-+
-+replace_dist("WebOb >= 1.2")
-+replace_dist("SQLAlchemy >= 0.6.3")
-+replace_dist("Routes >= 1.12.3")
-+
-+replace_dist("PasteDeploy >= 1.5.0")
-+# This hack is needed because replace_dist() results in
-+# the standard paste module path being at the start of __path__.
-+# TODO: See can we get pkg_resources to do the right thing directly
-+import paste
-+paste.__path__.insert(0, paste.__path__.pop(-1))
-diff --git a/cinder/db/sqlalchemy/migration.py b/cinder/db/sqlalchemy/migration.py
-index e2463bc..9ba7b73 100644
---- a/cinder/db/sqlalchemy/migration.py
-+++ b/cinder/db/sqlalchemy/migration.py
-@@ -56,7 +56,12 @@ if (not hasattr(migrate, '__version__') or
-
-
- # NOTE(jkoelker) Delay importing migrate until we are patched
--from migrate import exceptions as versioning_exceptions
-+try:
-+ # Try the more specific path first (migrate <= 0.6)
-+ from migrate.versioning import exceptions as versioning_exceptions
-+except ImportError:
-+ # Use the newer path (migrate >= 0.7)
-+ from migrate import exceptions as versioning_exceptions
- from migrate.versioning import api as versioning_api
- from migrate.versioning.repository import Repository
-
+++ /dev/null
-From bd1bfd86fbeacd893ff5d9abb969c7b0c994fbf4 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?P=C3=A1draig=20Brady?= <P@draigBrady.com>
-Date: Mon, 22 Jul 2013 03:14:27 +0100
-Subject: [PATCH] Revert "Use oslo.sphinx and remove local copy of doc theme"
-
-This reverts commit 8b1ae18ba95f04fedc04592b419401d75d448543.
-
-Conflicts:
-
- test-requirements.txt
----
- doc/source/_static/.gitignore | 0
- doc/source/_static/.placeholder | 0
- doc/source/_static/basic.css | 416 +++++++++++++++++++++++++++++++++++++
- doc/source/_static/default.css | 230 ++++++++++++++++++++
- doc/source/_static/jquery.tweet.js | 154 ++++++++++++++
- doc/source/_static/tweaks.css | 218 +++++++++++++++++++
- doc/source/_templates/.gitignore | 0
- doc/source/_templates/.placeholder | 0
- doc/source/_theme/layout.html | 95 +++++++++
- doc/source/_theme/theme.conf | 5 +
- doc/source/conf.py | 8 +-
- test-requirements.txt | 1 -
- 12 files changed, 1121 insertions(+), 6 deletions(-)
- create mode 100644 doc/source/_static/.gitignore
- create mode 100644 doc/source/_static/.placeholder
- create mode 100644 doc/source/_static/basic.css
- create mode 100644 doc/source/_static/default.css
- create mode 100644 doc/source/_static/jquery.tweet.js
- create mode 100644 doc/source/_static/tweaks.css
- create mode 100644 doc/source/_templates/.gitignore
- create mode 100644 doc/source/_templates/.placeholder
- create mode 100644 doc/source/_theme/layout.html
- create mode 100644 doc/source/_theme/theme.conf
-
-diff --git a/doc/source/_static/.gitignore b/doc/source/_static/.gitignore
-new file mode 100644
-index 0000000..e69de29
-diff --git a/doc/source/_static/.placeholder b/doc/source/_static/.placeholder
-new file mode 100644
-index 0000000..e69de29
-diff --git a/doc/source/_static/basic.css b/doc/source/_static/basic.css
-new file mode 100644
-index 0000000..d909ce3
---- /dev/null
-+++ b/doc/source/_static/basic.css
-@@ -0,0 +1,416 @@
-+/**
-+ * Sphinx stylesheet -- basic theme
-+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-+ */
-+
-+/* -- main layout ----------------------------------------------------------- */
-+
-+div.clearer {
-+ clear: both;
-+}
-+
-+/* -- relbar ---------------------------------------------------------------- */
-+
-+div.related {
-+ width: 100%;
-+ font-size: 90%;
-+}
-+
-+div.related h3 {
-+ display: none;
-+}
-+
-+div.related ul {
-+ margin: 0;
-+ padding: 0 0 0 10px;
-+ list-style: none;
-+}
-+
-+div.related li {
-+ display: inline;
-+}
-+
-+div.related li.right {
-+ float: right;
-+ margin-right: 5px;
-+}
-+
-+/* -- sidebar --------------------------------------------------------------- */
-+
-+div.sphinxsidebarwrapper {
-+ padding: 10px 5px 0 10px;
-+}
-+
-+div.sphinxsidebar {
-+ float: left;
-+ width: 230px;
-+ margin-left: -100%;
-+ font-size: 90%;
-+}
-+
-+div.sphinxsidebar ul {
-+ list-style: none;
-+}
-+
-+div.sphinxsidebar ul ul,
-+div.sphinxsidebar ul.want-points {
-+ margin-left: 20px;
-+ list-style: square;
-+}
-+
-+div.sphinxsidebar ul ul {
-+ margin-top: 0;
-+ margin-bottom: 0;
-+}
-+
-+div.sphinxsidebar form {
-+ margin-top: 10px;
-+}
-+
-+div.sphinxsidebar input {
-+ border: 1px solid #98dbcc;
-+ font-family: sans-serif;
-+ font-size: 1em;
-+}
-+
-+img {
-+ border: 0;
-+}
-+
-+/* -- search page ----------------------------------------------------------- */
-+
-+ul.search {
-+ margin: 10px 0 0 20px;
-+ padding: 0;
-+}
-+
-+ul.search li {
-+ padding: 5px 0 5px 20px;
-+ background-image: url(file.png);
-+ background-repeat: no-repeat;
-+ background-position: 0 7px;
-+}
-+
-+ul.search li a {
-+ font-weight: bold;
-+}
-+
-+ul.search li div.context {
-+ color: #888;
-+ margin: 2px 0 0 30px;
-+ text-align: left;
-+}
-+
-+ul.keywordmatches li.goodmatch a {
-+ font-weight: bold;
-+}
-+
-+/* -- index page ------------------------------------------------------------ */
-+
-+table.contentstable {
-+ width: 90%;
-+}
-+
-+table.contentstable p.biglink {
-+ line-height: 150%;
-+}
-+
-+a.biglink {
-+ font-size: 1.3em;
-+}
-+
-+span.linkdescr {
-+ font-style: italic;
-+ padding-top: 5px;
-+ font-size: 90%;
-+}
-+
-+/* -- general index --------------------------------------------------------- */
-+
-+table.indextable td {
-+ text-align: left;
-+ vertical-align: top;
-+}
-+
-+table.indextable dl, table.indextable dd {
-+ margin-top: 0;
-+ margin-bottom: 0;
-+}
-+
-+table.indextable tr.pcap {
-+ height: 10px;
-+}
-+
-+table.indextable tr.cap {
-+ margin-top: 10px;
-+ background-color: #f2f2f2;
-+}
-+
-+img.toggler {
-+ margin-right: 3px;
-+ margin-top: 3px;
-+ cursor: pointer;
-+}
-+
-+/* -- general body styles --------------------------------------------------- */
-+
-+a.headerlink {
-+ visibility: hidden;
-+}
-+
-+h1:hover > a.headerlink,
-+h2:hover > a.headerlink,
-+h3:hover > a.headerlink,
-+h4:hover > a.headerlink,
-+h5:hover > a.headerlink,
-+h6:hover > a.headerlink,
-+dt:hover > a.headerlink {
-+ visibility: visible;
-+}
-+
-+div.body p.caption {
-+ text-align: inherit;
-+}
-+
-+div.body td {
-+ text-align: left;
-+}
-+
-+.field-list ul {
-+ padding-left: 1em;
-+}
-+
-+.first {
-+}
-+
-+p.rubric {
-+ margin-top: 30px;
-+ font-weight: bold;
-+}
-+
-+/* -- sidebars -------------------------------------------------------------- */
-+
-+div.sidebar {
-+ margin: 0 0 0.5em 1em;
-+ border: 1px solid #ddb;
-+ padding: 7px 7px 0 7px;
-+ background-color: #ffe;
-+ width: 40%;
-+ float: right;
-+}
-+
-+p.sidebar-title {
-+ font-weight: bold;
-+}
-+
-+/* -- topics ---------------------------------------------------------------- */
-+
-+div.topic {
-+ border: 1px solid #ccc;
-+ padding: 7px 7px 0 7px;
-+ margin: 10px 0 10px 0;
-+}
-+
-+p.topic-title {
-+ font-size: 1.1em;
-+ font-weight: bold;
-+ margin-top: 10px;
-+}
-+
-+/* -- admonitions ----------------------------------------------------------- */
-+
-+div.admonition {
-+ margin-top: 10px;
-+ margin-bottom: 10px;
-+ padding: 7px;
-+}
-+
-+div.admonition dt {
-+ font-weight: bold;
-+}
-+
-+div.admonition dl {
-+ margin-bottom: 0;
-+}
-+
-+p.admonition-title {
-+ margin: 0px 10px 5px 0px;
-+ font-weight: bold;
-+}
-+
-+div.body p.centered {
-+ text-align: center;
-+ margin-top: 25px;
-+}
-+
-+/* -- tables ---------------------------------------------------------------- */
-+
-+table.docutils {
-+ border: 0;
-+ border-collapse: collapse;
-+}
-+
-+table.docutils td, table.docutils th {
-+ padding: 1px 8px 1px 0;
-+ border-top: 0;
-+ border-left: 0;
-+ border-right: 0;
-+ border-bottom: 1px solid #aaa;
-+}
-+
-+table.field-list td, table.field-list th {
-+ border: 0 !important;
-+}
-+
-+table.footnote td, table.footnote th {
-+ border: 0 !important;
-+}
-+
-+th {
-+ text-align: left;
-+ padding-right: 5px;
-+}
-+
-+/* -- other body styles ----------------------------------------------------- */
-+
-+dl {
-+ margin-bottom: 15px;
-+}
-+
-+dd p {
-+ margin-top: 0px;
-+}
-+
-+dd ul, dd table {
-+ margin-bottom: 10px;
-+}
-+
-+dd {
-+ margin-top: 3px;
-+ margin-bottom: 10px;
-+ margin-left: 30px;
-+}
-+
-+dt:target, .highlight {
-+ background-color: #fbe54e;
-+}
-+
-+dl.glossary dt {
-+ font-weight: bold;
-+ font-size: 1.1em;
-+}
-+
-+.field-list ul {
-+ margin: 0;
-+ padding-left: 1em;
-+}
-+
-+.field-list p {
-+ margin: 0;
-+}
-+
-+.refcount {
-+ color: #060;
-+}
-+
-+.optional {
-+ font-size: 1.3em;
-+}
-+
-+.versionmodified {
-+ font-style: italic;
-+}
-+
-+.system-message {
-+ background-color: #fda;
-+ padding: 5px;
-+ border: 3px solid red;
-+}
-+
-+.footnote:target {
-+ background-color: #ffa
-+}
-+
-+.line-block {
-+ display: block;
-+ margin-top: 1em;
-+ margin-bottom: 1em;
-+}
-+
-+.line-block .line-block {
-+ margin-top: 0;
-+ margin-bottom: 0;
-+ margin-left: 1.5em;
-+}
-+
-+/* -- code displays --------------------------------------------------------- */
-+
-+pre {
-+ overflow: auto;
-+}
-+
-+td.linenos pre {
-+ padding: 5px 0px;
-+ border: 0;
-+ background-color: transparent;
-+ color: #aaa;
-+}
-+
-+table.highlighttable {
-+ margin-left: 0.5em;
-+}
-+
-+table.highlighttable td {
-+ padding: 0 0.5em 0 0.5em;
-+}
-+
-+tt.descname {
-+ background-color: transparent;
-+ font-weight: bold;
-+ font-size: 1.2em;
-+}
-+
-+tt.descclassname {
-+ background-color: transparent;
-+}
-+
-+tt.xref, a tt {
-+ background-color: transparent;
-+ font-weight: bold;
-+}
-+
-+h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
-+ background-color: transparent;
-+}
-+
-+/* -- math display ---------------------------------------------------------- */
-+
-+img.math {
-+ vertical-align: middle;
-+}
-+
-+div.body div.math p {
-+ text-align: center;
-+}
-+
-+span.eqno {
-+ float: right;
-+}
-+
-+/* -- printout stylesheet --------------------------------------------------- */
-+
-+@media print {
-+ div.document,
-+ div.documentwrapper,
-+ div.bodywrapper {
-+ margin: 0 !important;
-+ width: 100%;
-+ }
-+
-+ div.sphinxsidebar,
-+ div.related,
-+ div.footer,
-+ #top-link {
-+ display: none;
-+ }
-+}
-diff --git a/doc/source/_static/default.css b/doc/source/_static/default.css
-new file mode 100644
-index 0000000..c8091ec
---- /dev/null
-+++ b/doc/source/_static/default.css
-@@ -0,0 +1,230 @@
-+/**
-+ * Sphinx stylesheet -- default theme
-+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-+ */
-+
-+@import url("basic.css");
-+
-+/* -- page layout ----------------------------------------------------------- */
-+
-+body {
-+ font-family: sans-serif;
-+ font-size: 100%;
-+ background-color: #11303d;
-+ color: #000;
-+ margin: 0;
-+ padding: 0;
-+}
-+
-+div.document {
-+ background-color: #1c4e63;
-+}
-+
-+div.documentwrapper {
-+ float: left;
-+ width: 100%;
-+}
-+
-+div.bodywrapper {
-+ margin: 0 0 0 230px;
-+}
-+
-+div.body {
-+ background-color: #ffffff;
-+ color: #000000;
-+ padding: 0 20px 30px 20px;
-+}
-+
-+div.footer {
-+ color: #ffffff;
-+ width: 100%;
-+ padding: 9px 0 9px 0;
-+ text-align: center;
-+ font-size: 75%;
-+}
-+
-+div.footer a {
-+ color: #ffffff;
-+ text-decoration: underline;
-+}
-+
-+div.related {
-+ background-color: #133f52;
-+ line-height: 30px;
-+ color: #ffffff;
-+}
-+
-+div.related a {
-+ color: #ffffff;
-+}
-+
-+div.sphinxsidebar {
-+}
-+
-+div.sphinxsidebar h3 {
-+ font-family: 'Trebuchet MS', sans-serif;
-+ color: #ffffff;
-+ font-size: 1.4em;
-+ font-weight: normal;
-+ margin: 0;
-+ padding: 0;
-+}
-+
-+div.sphinxsidebar h3 a {
-+ color: #ffffff;
-+}
-+
-+div.sphinxsidebar h4 {
-+ font-family: 'Trebuchet MS', sans-serif;
-+ color: #ffffff;
-+ font-size: 1.3em;
-+ font-weight: normal;
-+ margin: 5px 0 0 0;
-+ padding: 0;
-+}
-+
-+div.sphinxsidebar p {
-+ color: #ffffff;
-+}
-+
-+div.sphinxsidebar p.topless {
-+ margin: 5px 10px 10px 10px;
-+}
-+
-+div.sphinxsidebar ul {
-+ margin: 10px;
-+ padding: 0;
-+ color: #ffffff;
-+}
-+
-+div.sphinxsidebar a {
-+ color: #98dbcc;
-+}
-+
-+div.sphinxsidebar input {
-+ border: 1px solid #98dbcc;
-+ font-family: sans-serif;
-+ font-size: 1em;
-+}
-+
-+/* -- body styles ----------------------------------------------------------- */
-+
-+a {
-+ color: #355f7c;
-+ text-decoration: none;
-+}
-+
-+a:hover {
-+ text-decoration: underline;
-+}
-+
-+div.body p, div.body dd, div.body li {
-+ text-align: left;
-+ line-height: 130%;
-+}
-+
-+div.body h1,
-+div.body h2,
-+div.body h3,
-+div.body h4,
-+div.body h5,
-+div.body h6 {
-+ font-family: 'Trebuchet MS', sans-serif;
-+ background-color: #f2f2f2;
-+ font-weight: normal;
-+ color: #20435c;
-+ border-bottom: 1px solid #ccc;
-+ margin: 20px -20px 10px -20px;
-+ padding: 3px 0 3px 10px;
-+}
-+
-+div.body h1 { margin-top: 0; font-size: 200%; }
-+div.body h2 { font-size: 160%; }
-+div.body h3 { font-size: 140%; }
-+div.body h4 { font-size: 120%; }
-+div.body h5 { font-size: 110%; }
-+div.body h6 { font-size: 100%; }
-+
-+a.headerlink {
-+ color: #c60f0f;
-+ font-size: 0.8em;
-+ padding: 0 4px 0 4px;
-+ text-decoration: none;
-+}
-+
-+a.headerlink:hover {
-+ background-color: #c60f0f;
-+ color: white;
-+}
-+
-+div.body p, div.body dd, div.body li {
-+ text-align: left;
-+ line-height: 130%;
-+}
-+
-+div.admonition p.admonition-title + p {
-+ display: inline;
-+}
-+
-+div.admonition p {
-+ margin-bottom: 5px;
-+}
-+
-+div.admonition pre {
-+ margin-bottom: 5px;
-+}
-+
-+div.admonition ul, div.admonition ol {
-+ margin-bottom: 5px;
-+}
-+
-+div.note {
-+ background-color: #eee;
-+ border: 1px solid #ccc;
-+}
-+
-+div.seealso {
-+ background-color: #ffc;
-+ border: 1px solid #ff6;
-+}
-+
-+div.topic {
-+ background-color: #eee;
-+}
-+
-+div.warning {
-+ background-color: #ffe4e4;
-+ border: 1px solid #f66;
-+}
-+
-+p.admonition-title {
-+ display: inline;
-+}
-+
-+p.admonition-title:after {
-+ content: ":";
-+}
-+
-+pre {
-+ padding: 5px;
-+ background-color: #eeffcc;
-+ color: #333333;
-+ line-height: 120%;
-+ border: 1px solid #ac9;
-+ border-left: none;
-+ border-right: none;
-+}
-+
-+tt {
-+ background-color: #ecf0f3;
-+ padding: 0 1px 0 1px;
-+ font-size: 0.95em;
-+}
-+
-+.warning tt {
-+ background: #efc2c2;
-+}
-+
-+.note tt {
-+ background: #d6d6d6;
-+}
-diff --git a/doc/source/_static/jquery.tweet.js b/doc/source/_static/jquery.tweet.js
-new file mode 100644
-index 0000000..79bf0bd
---- /dev/null
-+++ b/doc/source/_static/jquery.tweet.js
-@@ -0,0 +1,154 @@
-+(function($) {
-+
-+ $.fn.tweet = function(o){
-+ var s = {
-+ username: ["seaofclouds"], // [string] required, unless you want to display our tweets. :) it can be an array, just do ["username1","username2","etc"]
-+ list: null, //[string] optional name of list belonging to username
-+ avatar_size: null, // [integer] height and width of avatar if displayed (48px max)
-+ count: 3, // [integer] how many tweets to display?
-+ intro_text: null, // [string] do you want text BEFORE your your tweets?
-+ outro_text: null, // [string] do you want text AFTER your tweets?
-+ join_text: null, // [string] optional text in between date and tweet, try setting to "auto"
-+ auto_join_text_default: "i said,", // [string] auto text for non verb: "i said" bullocks
-+ auto_join_text_ed: "i", // [string] auto text for past tense: "i" surfed
-+ auto_join_text_ing: "i am", // [string] auto tense for present tense: "i was" surfing
-+ auto_join_text_reply: "i replied to", // [string] auto tense for replies: "i replied to" @someone "with"
-+ auto_join_text_url: "i was looking at", // [string] auto tense for urls: "i was looking at" http:...
-+ loading_text: null, // [string] optional loading text, displayed while tweets load
-+ query: null // [string] optional search query
-+ };
-+
-+ if(o) $.extend(s, o);
-+
-+ $.fn.extend({
-+ linkUrl: function() {
-+ var returning = [];
-+ var regexp = /((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi;
-+ this.each(function() {
-+ returning.push(this.replace(regexp,"<a href=\"$1\">$1</a>"));
-+ });
-+ return $(returning);
-+ },
-+ linkUser: function() {
-+ var returning = [];
-+ var regexp = /[\@]+([A-Za-z0-9-_]+)/gi;
-+ this.each(function() {
-+ returning.push(this.replace(regexp,"<a href=\"http://twitter.com/$1\">@$1</a>"));
-+ });
-+ return $(returning);
-+ },
-+ linkHash: function() {
-+ var returning = [];
-+ var regexp = / [\#]+([A-Za-z0-9-_]+)/gi;
-+ this.each(function() {
-+ returning.push(this.replace(regexp, ' <a href="http://search.twitter.com/search?q=&tag=$1&lang=all&from='+s.username.join("%2BOR%2B")+'">#$1</a>'));
-+ });
-+ return $(returning);
-+ },
-+ capAwesome: function() {
-+ var returning = [];
-+ this.each(function() {
-+ returning.push(this.replace(/\b(awesome)\b/gi, '<span class="awesome">$1</span>'));
-+ });
-+ return $(returning);
-+ },
-+ capEpic: function() {
-+ var returning = [];
-+ this.each(function() {
-+ returning.push(this.replace(/\b(epic)\b/gi, '<span class="epic">$1</span>'));
-+ });
-+ return $(returning);
-+ },
-+ makeHeart: function() {
-+ var returning = [];
-+ this.each(function() {
-+ returning.push(this.replace(/(<)+[3]/gi, "<tt class='heart'>♥</tt>"));
-+ });
-+ return $(returning);
-+ }
-+ });
-+
-+ function relative_time(time_value) {
-+ var parsed_date = Date.parse(time_value);
-+ var relative_to = (arguments.length > 1) ? arguments[1] : new Date();
-+ var delta = parseInt((relative_to.getTime() - parsed_date) / 1000);
-+ var pluralize = function (singular, n) {
-+ return '' + n + ' ' + singular + (n == 1 ? '' : 's');
-+ };
-+ if(delta < 60) {
-+ return 'less than a minute ago';
-+ } else if(delta < (45*60)) {
-+ return 'about ' + pluralize("minute", parseInt(delta / 60)) + ' ago';
-+ } else if(delta < (24*60*60)) {
-+ return 'about ' + pluralize("hour", parseInt(delta / 3600)) + ' ago';
-+ } else {
-+ return 'about ' + pluralize("day", parseInt(delta / 86400)) + ' ago';
-+ }
-+ }
-+
-+ function build_url() {
-+ var proto = ('https:' == document.location.protocol ? 'https:' : 'http:');
-+ if (s.list) {
-+ return proto+"//api.twitter.com/1/"+s.username[0]+"/lists/"+s.list+"/statuses.json?per_page="+s.count+"&callback=?";
-+ } else if (s.query == null && s.username.length == 1) {
-+ return proto+'//twitter.com/status/user_timeline/'+s.username[0]+'.json?count='+s.count+'&callback=?';
-+ } else {
-+ var query = (s.query || 'from:'+s.username.join('%20OR%20from:'));
-+ return proto+'//search.twitter.com/search.json?&q='+query+'&rpp='+s.count+'&callback=?';
-+ }
-+ }
-+
-+ return this.each(function(){
-+ var list = $('<ul class="tweet_list">').appendTo(this);
-+ var intro = '<p class="tweet_intro">'+s.intro_text+'</p>';
-+ var outro = '<p class="tweet_outro">'+s.outro_text+'</p>';
-+ var loading = $('<p class="loading">'+s.loading_text+'</p>');
-+
-+ if(typeof(s.username) == "string"){
-+ s.username = [s.username];
-+ }
-+
-+ if (s.loading_text) $(this).append(loading);
-+ $.getJSON(build_url(), function(data){
-+ if (s.loading_text) loading.remove();
-+ if (s.intro_text) list.before(intro);
-+ $.each((data.results || data), function(i,item){
-+ // auto join text based on verb tense and content
-+ if (s.join_text == "auto") {
-+ if (item.text.match(/^(@([A-Za-z0-9-_]+)) .*/i)) {
-+ var join_text = s.auto_join_text_reply;
-+ } else if (item.text.match(/(^\w+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+) .*/i)) {
-+ var join_text = s.auto_join_text_url;
-+ } else if (item.text.match(/^((\w+ed)|just) .*/im)) {
-+ var join_text = s.auto_join_text_ed;
-+ } else if (item.text.match(/^(\w*ing) .*/i)) {
-+ var join_text = s.auto_join_text_ing;
-+ } else {
-+ var join_text = s.auto_join_text_default;
-+ }
-+ } else {
-+ var join_text = s.join_text;
-+ };
-+
-+ var from_user = item.from_user || item.user.screen_name;
-+ var profile_image_url = item.profile_image_url || item.user.profile_image_url;
-+ var join_template = '<span class="tweet_join"> '+join_text+' </span>';
-+ var join = ((s.join_text) ? join_template : ' ');
-+ var avatar_template = '<a class="tweet_avatar" href="http://twitter.com/'+from_user+'"><img src="'+profile_image_url+'" height="'+s.avatar_size+'" width="'+s.avatar_size+'" alt="'+from_user+'\'s avatar" title="'+from_user+'\'s avatar" border="0"/></a>';
-+ var avatar = (s.avatar_size ? avatar_template : '');
-+ var date = '<a href="http://twitter.com/'+from_user+'/statuses/'+item.id+'" title="view tweet on twitter">'+relative_time(item.created_at)+'</a>';
-+ var text = '<span class="tweet_text">' +$([item.text]).linkUrl().linkUser().linkHash().makeHeart().capAwesome().capEpic()[0]+ '</span>';
-+
-+ // until we create a template option, arrange the items below to alter a tweet's display.
-+ list.append('<li>' + avatar + date + join + text + '</li>');
-+
-+ list.children('li:first').addClass('tweet_first');
-+ list.children('li:odd').addClass('tweet_even');
-+ list.children('li:even').addClass('tweet_odd');
-+ });
-+ if (s.outro_text) list.after(outro);
-+ });
-+
-+ });
-+ };
-+})(jQuery);
-\ No newline at end of file
-diff --git a/doc/source/_static/tweaks.css b/doc/source/_static/tweaks.css
-new file mode 100644
-index 0000000..046ead8
---- /dev/null
-+++ b/doc/source/_static/tweaks.css
-@@ -0,0 +1,218 @@
-+ul.todo_list {
-+ list-style-type: none;
-+ margin: 0;
-+ padding: 0;
-+}
-+
-+ul.todo_list li {
-+ display: block;
-+ margin: 0;
-+ padding: 7px 0;
-+ border-top: 1px solid #eee;
-+}
-+
-+ul.todo_list li p {
-+ display: inline;
-+}
-+
-+ul.todo_list li p.link {
-+ font-weight: bold;
-+}
-+
-+ul.todo_list li p.details {
-+ font-style: italic;
-+}
-+
-+ul.todo_list li {
-+}
-+
-+div.admonition {
-+ border: 1px solid #8F1000;
-+}
-+
-+div.admonition p.admonition-title {
-+ background-color: #8F1000;
-+ border-bottom: 1px solid #8E8E8E;
-+}
-+
-+a {
-+ color: #CF2F19;
-+}
-+
-+div.related ul li a {
-+ color: #CF2F19;
-+}
-+
-+div.sphinxsidebar h4 {
-+ background-color:#8E8E8E;
-+ border:1px solid #255E6E;
-+ color:white;
-+ font-size:1em;
-+ margin:1em 0 0.5em;
-+ padding:0.1em 0 0.1em 0.5em;
-+}
-+
-+em {
-+ font-style: normal;
-+}
-+
-+table.docutils {
-+ font-size: 11px;
-+}
-+
-+.tweet_list li {
-+ font-size: 0.9em;
-+ border-bottom: 1px solid #eee;
-+ padding: 5px 0;
-+}
-+
-+.tweet_list li .tweet_avatar {
-+ float: left;
-+}
-+
-+/* ------------------------------------------
-+PURE CSS SPEECH BUBBLES
-+by Nicolas Gallagher
-+- http://nicolasgallagher.com/pure-css-speech-bubbles/
-+
-+http://nicolasgallagher.com
-+http://twitter.com/necolas
-+
-+Created: 02 March 2010
-+Version: 1.1 (21 October 2010)
-+
-+Dual licensed under MIT and GNU GPLv2 © Nicolas Gallagher
-+------------------------------------------ */
-+/* THE SPEECH BUBBLE
-+------------------------------------------------------------------------------------------------------------------------------- */
-+
-+/* THE SPEECH BUBBLE
-+------------------------------------------------------------------------------------------------------------------------------- */
-+
-+.triangle-border {
-+ position:relative;
-+ padding:15px;
-+ margin:1em 0 3em;
-+ border:5px solid #BC1518;
-+ color:#333;
-+ background:#fff;
-+
-+ /* css3 */
-+ -moz-border-radius:10px;
-+ -webkit-border-radius:10px;
-+ border-radius:10px;
-+}
-+
-+/* Variant : for left positioned triangle
-+------------------------------------------ */
-+
-+.triangle-border.left {
-+ margin-left:30px;
-+}
-+
-+/* Variant : for right positioned triangle
-+------------------------------------------ */
-+
-+.triangle-border.right {
-+ margin-right:30px;
-+}
-+
-+/* THE TRIANGLE
-+------------------------------------------------------------------------------------------------------------------------------- */
-+
-+.triangle-border:before {
-+ content:"";
-+ display:block; /* reduce the damage in FF3.0 */
-+ position:absolute;
-+ bottom:-40px; /* value = - border-top-width - border-bottom-width */
-+ left:40px; /* controls horizontal position */
-+ width:0;
-+ height:0;
-+ border:20px solid transparent;
-+ border-top-color:#BC1518;
-+}
-+
-+/* creates the smaller triangle */
-+.triangle-border:after {
-+ content:"";
-+ display:block; /* reduce the damage in FF3.0 */
-+ position:absolute;
-+ bottom:-26px; /* value = - border-top-width - border-bottom-width */
-+ left:47px; /* value = (:before left) + (:before border-left) - (:after border-left) */
-+ width:0;
-+ height:0;
-+ border:13px solid transparent;
-+ border-top-color:#fff;
-+}
-+
-+/* Variant : top
-+------------------------------------------ */
-+
-+/* creates the larger triangle */
-+.triangle-border.top:before {
-+ top:-40px; /* value = - border-top-width - border-bottom-width */
-+ right:40px; /* controls horizontal position */
-+ bottom:auto;
-+ left:auto;
-+ border:20px solid transparent;
-+ border-bottom-color:#BC1518;
-+}
-+
-+/* creates the smaller triangle */
-+.triangle-border.top:after {
-+ top:-26px; /* value = - border-top-width - border-bottom-width */
-+ right:47px; /* value = (:before right) + (:before border-right) - (:after border-right) */
-+ bottom:auto;
-+ left:auto;
-+ border:13px solid transparent;
-+ border-bottom-color:#fff;
-+}
-+
-+/* Variant : left
-+------------------------------------------ */
-+
-+/* creates the larger triangle */
-+.triangle-border.left:before {
-+ top:10px; /* controls vertical position */
-+ left:-30px; /* value = - border-left-width - border-right-width */
-+ bottom:auto;
-+ border-width:15px 30px 15px 0;
-+ border-style:solid;
-+ border-color:transparent #BC1518;
-+}
-+
-+/* creates the smaller triangle */
-+.triangle-border.left:after {
-+ top:16px; /* value = (:before top) + (:before border-top) - (:after border-top) */
-+ left:-21px; /* value = - border-left-width - border-right-width */
-+ bottom:auto;
-+ border-width:9px 21px 9px 0;
-+ border-style:solid;
-+ border-color:transparent #fff;
-+}
-+
-+/* Variant : right
-+------------------------------------------ */
-+
-+/* creates the larger triangle */
-+.triangle-border.right:before {
-+ top:10px; /* controls vertical position */
-+ right:-30px; /* value = - border-left-width - border-right-width */
-+ bottom:auto;
-+ left:auto;
-+ border-width:15px 0 15px 30px;
-+ border-style:solid;
-+ border-color:transparent #BC1518;
-+}
-+
-+/* creates the smaller triangle */
-+.triangle-border.right:after {
-+ top:16px; /* value = (:before top) + (:before border-top) - (:after border-top) */
-+ right:-21px; /* value = - border-left-width - border-right-width */
-+ bottom:auto;
-+ left:auto;
-+ border-width:9px 0 9px 21px;
-+ border-style:solid;
-+ border-color:transparent #fff;
-+}
-+
-diff --git a/doc/source/_templates/.gitignore b/doc/source/_templates/.gitignore
-new file mode 100644
-index 0000000..e69de29
-diff --git a/doc/source/_templates/.placeholder b/doc/source/_templates/.placeholder
-new file mode 100644
-index 0000000..e69de29
-diff --git a/doc/source/_theme/layout.html b/doc/source/_theme/layout.html
-new file mode 100644
-index 0000000..f5b388c
---- /dev/null
-+++ b/doc/source/_theme/layout.html
-@@ -0,0 +1,95 @@
-+{% extends "sphinxdoc/layout.html" %}
-+{% set css_files = css_files + ['_static/tweaks.css'] %}
-+{% set script_files = script_files + ['_static/jquery.tweet.js'] %}
-+{% block extrahead %}
-+ <script type='text/javascript'>
-+ $(document).ready(function(){
-+ $("#twitter_feed").tweet({
-+ username: "openstack",
-+ query: "from:openstack",
-+ avatar_size: 32,
-+ count: 10,
-+ loading_text: "loading tweets..."
-+ });
-+ });
-+ </script>
-+{% endblock %}
-+
-+{%- macro sidebar() %}
-+ {%- if not embedded %}{% if not theme_nosidebar|tobool %}
-+ <div class="sphinxsidebar">
-+ <div class="sphinxsidebarwrapper">
-+ {%- block sidebarlogo %}
-+ {%- if logo %}
-+ <p class="logo"><a href="{{ pathto(master_doc) }}">
-+ <img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
-+ </a></p>
-+ {%- endif %}
-+ {%- endblock %}
-+ {%- block sidebartoc %}
-+ {%- if display_toc %}
-+ <h3><a href="{{ pathto(master_doc) }}">{{ _('Table Of Contents') }}</a></h3>
-+ {{ toc }}
-+ {%- endif %}
-+ {%- endblock %}
-+ {%- block sidebarrel %}
-+ {%- if prev %}
-+ <h4>{{ _('Previous topic') }}</h4>
-+ <p class="topless"><a href="{{ prev.link|e }}"
-+ title="{{ _('previous chapter') }}">{{ prev.title }}</a></p>
-+ {%- endif %}
-+ {%- if next %}
-+ <h4>{{ _('Next topic') }}</h4>
-+ <p class="topless"><a href="{{ next.link|e }}"
-+ title="{{ _('next chapter') }}">{{ next.title }}</a></p>
-+ {%- endif %}
-+ {%- endblock %}
-+ {%- block sidebarsourcelink %}
-+ {%- if show_source and has_source and sourcename %}
-+ <h3>{{ _('This Page') }}</h3>
-+ <ul class="this-page-menu">
-+ <li><a href="{{ pathto('_sources/' + sourcename, true)|e }}"
-+ rel="nofollow">{{ _('Show Source') }}</a></li>
-+ </ul>
-+ {%- endif %}
-+ {%- endblock %}
-+ {%- if customsidebar %}
-+ {% include customsidebar %}
-+ {%- endif %}
-+ {%- block sidebarsearch %}
-+ {%- if pagename != "search" %}
-+ <div id="searchbox" style="display: none">
-+ <h3>{{ _('Quick search') }}</h3>
-+ <form class="search" action="{{ pathto('search') }}" method="get">
-+ <input type="text" name="q" size="18" />
-+ <input type="submit" value="{{ _('Go') }}" />
-+ <input type="hidden" name="check_keywords" value="yes" />
-+ <input type="hidden" name="area" value="default" />
-+ </form>
-+ <p class="searchtip" style="font-size: 90%">
-+ {{ _('Enter search terms or a module, class or function name.') }}
-+ </p>
-+ </div>
-+ <script type="text/javascript">$('#searchbox').show(0);</script>
-+
-+ <p class="triangle-border right">
-+ Psst... hey. You're reading the latest content, but it's for the Block Storage project only. You can read <a href="http://docs.openstack.org">all OpenStack docs</a> too.
-+ </p>
-+
-+ {%- endif %}
-+
-+ {%- if pagename == "index" %}
-+
-+
-+ <h3>{{ _('Twitter Feed') }}</h3>
-+ <div id="twitter_feed" class='twitter_feed'></div>
-+ {%- endif %}
-+
-+
-+
-+
-+ {%- endblock %}
-+ </div>
-+ </div>
-+ {%- endif %}{% endif %}
-+{%- endmacro %}
-diff --git a/doc/source/_theme/theme.conf b/doc/source/_theme/theme.conf
-new file mode 100644
-index 0000000..e039fe0
---- /dev/null
-+++ b/doc/source/_theme/theme.conf
-@@ -0,0 +1,5 @@
-+[theme]
-+inherit = sphinxdoc
-+stylesheet = sphinxdoc.css
-+pygments_style = friendly
-+
-diff --git a/doc/source/conf.py b/doc/source/conf.py
-index 7a2bf1a..01bd3b4 100644
---- a/doc/source/conf.py
-+++ b/doc/source/conf.py
-@@ -33,9 +33,7 @@ extensions = ['sphinx.ext.autodoc',
- 'sphinx.ext.coverage',
- 'sphinx.ext.pngmath',
- 'sphinx.ext.ifconfig',
-- 'sphinx.ext.graphviz',
-- 'oslo.sphinx',
-- ]
-+ 'sphinx.ext.graphviz']
-
- # autodoc generation is a bit aggressive and a nuisance
- # when doing heavy text edit cycles. Execute "export SPHINX_DEBUG=1"
-@@ -133,8 +131,8 @@ man_pages = [
-
- # The theme to use for HTML and HTML Help pages. Major themes that come with
- # Sphinx are currently 'default' and 'sphinxdoc'.
--# html_theme_path = ["."]
--# html_theme = '_theme'
-+html_theme_path = ["."]
-+html_theme = '_theme'
-
- # Theme options are theme-specific and customize the look and feel of a theme
- # further. For a list of options available for each theme, see the
-diff --git a/test-requirements.txt b/test-requirements.txt
-index 3d8e9bf..f83b20b 100644
---- a/test-requirements.txt
-+++ b/test-requirements.txt
-@@ -12,4 +12,3 @@ sphinx>=1.1.2
- python-subunit
- testtools>=0.9.32
- testrepository>=0.0.17
--oslo.sphinx
+++ /dev/null
-From fd7e9dd59ffa346bed5a11e5312f4bb1bf114ab4 Mon Sep 17 00:00:00 2001
-From: Dmitry Borodaenko <angdraug@gmail.com>
-Date: Wed, 27 Nov 2013 14:33:00 -0800
-Subject: [PATCH] Do not clone non-raw images in rbd backend
-
-RBD backend only supports booting from images in raw format. A volume
-that was cloned from an image in any other format is not bootable. The
-RBD driver will consider non-raw images to be uncloneable to trigger
-automatic conversion to raw format.
-
-Includes conversion of the corresponding unit test to use mock (instead
-of mox) and expanded comments and error messages from patchset #58893 by
-Edward Hope-Morley.
-
-Change-Id: I5725d2f7576bc1b3e9b874ba944ad17d33a6e2cb
-Closes-Bug: #1246219
-Closes-Bug: #1247998
----
- cinder/tests/test_gpfs.py | 6 +-
- cinder/tests/test_netapp_nfs.py | 12 +-
- cinder/tests/test_rbd.py | 168 +++++++++++++++++---------
- cinder/tests/test_volume.py | 2 +-
- cinder/volume/driver.py | 7 +-
- cinder/volume/drivers/gpfs.py | 2 +-
- cinder/volume/drivers/lvm.py | 2 +-
- cinder/volume/drivers/netapp/nfs.py | 2 +-
- cinder/volume/drivers/rbd.py | 14 ++-
- cinder/volume/drivers/scality.py | 2 +-
- cinder/volume/flows/create_volume/__init__.py | 2 +-
- 11 files changed, 144 insertions(+), 75 deletions(-)
-
-diff --git a/cinder/tests/test_gpfs.py b/cinder/tests/test_gpfs.py
-index 4fdb788..1f47c6b 100644
---- a/cinder/tests/test_gpfs.py
-+++ b/cinder/tests/test_gpfs.py
-@@ -288,7 +288,8 @@ class GPFSDriverTestCase(test.TestCase):
- CONF.gpfs_images_share_mode = 'copy_on_write'
- self.driver.clone_image(volume,
- None,
-- self.image_id)
-+ self.image_id,
-+ {})
-
- self.assertTrue(os.path.exists(volumepath))
- self.volume.delete_volume(self.context, volume['id'])
-@@ -309,7 +310,8 @@ class GPFSDriverTestCase(test.TestCase):
- CONF.gpfs_images_share_mode = 'copy'
- self.driver.clone_image(volume,
- None,
-- self.image_id)
-+ self.image_id,
-+ {})
-
- self.assertTrue(os.path.exists(volumepath))
- self.volume.delete_volume(self.context, volume['id'])
-diff --git a/cinder/tests/test_netapp_nfs.py b/cinder/tests/test_netapp_nfs.py
-index 950efc8..042280e 100644
---- a/cinder/tests/test_netapp_nfs.py
-+++ b/cinder/tests/test_netapp_nfs.py
-@@ -469,7 +469,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
- drv._post_clone_image(volume)
-
- mox.ReplayAll()
-- drv. clone_image(volume, ('image_location', None), 'image_id')
-+ drv.clone_image(volume, ('image_location', None), 'image_id', {})
- mox.VerifyAll()
-
- def get_img_info(self, format):
-@@ -493,7 +493,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
-
- mox.ReplayAll()
- (prop, cloned) = drv. clone_image(
-- volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id')
-+ volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id', {})
- mox.VerifyAll()
- if not cloned and not prop['provider_location']:
- pass
-@@ -529,7 +529,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
-
- mox.ReplayAll()
- drv. clone_image(
-- volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id')
-+ volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id', {})
- mox.VerifyAll()
-
- def test_clone_image_cloneableshare_notraw(self):
-@@ -566,7 +566,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
-
- mox.ReplayAll()
- drv. clone_image(
-- volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
-+ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id', {})
- mox.VerifyAll()
-
- def test_clone_image_file_not_discovered(self):
-@@ -605,7 +605,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
-
- mox.ReplayAll()
- vol_dict, result = drv. clone_image(
-- volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
-+ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id', {})
- mox.VerifyAll()
- self.assertFalse(result)
- self.assertFalse(vol_dict['bootable'])
-@@ -652,7 +652,7 @@ class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
-
- mox.ReplayAll()
- vol_dict, result = drv. clone_image(
-- volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
-+ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id', {})
- mox.VerifyAll()
- self.assertFalse(result)
- self.assertFalse(vol_dict['bootable'])
-diff --git a/cinder/tests/test_rbd.py b/cinder/tests/test_rbd.py
-index 60de09b..054bbb5 100644
---- a/cinder/tests/test_rbd.py
-+++ b/cinder/tests/test_rbd.py
-@@ -34,6 +34,7 @@ from cinder.tests.test_volume import DriverTestCase
- from cinder import units
- from cinder.volume import configuration as conf
- import cinder.volume.drivers.rbd as driver
-+from cinder.volume.flows import create_volume
-
-
- LOG = logging.getLogger(__name__)
-@@ -247,7 +248,8 @@ class RBDTestCase(test.TestCase):
- self.assertRaises(exception.ImageUnacceptable,
- self.driver._parse_location,
- loc)
-- self.assertFalse(self.driver._is_cloneable(loc))
-+ self.assertFalse(
-+ self.driver._is_cloneable(loc, {'disk_format': 'raw'}))
-
- def test_cloneable(self):
- self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
-@@ -264,12 +266,14 @@ class RBDTestCase(test.TestCase):
-
- self.mox.ReplayAll()
-
-- self.assertTrue(self.driver._is_cloneable(location))
-+ self.assertTrue(
-+ self.driver._is_cloneable(location, {'disk_format': 'raw'}))
-
- def test_uncloneable_different_fsid(self):
- self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
- location = 'rbd://def/pool/image/snap'
-- self.assertFalse(self.driver._is_cloneable(location))
-+ self.assertFalse(
-+ self.driver._is_cloneable(location, {'disk_format': 'raw'}))
-
- def test_uncloneable_unreadable(self):
- self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
-@@ -284,7 +288,16 @@ class RBDTestCase(test.TestCase):
-
- self.mox.ReplayAll()
-
-- self.assertFalse(self.driver._is_cloneable(location))
-+ self.assertFalse(
-+ self.driver._is_cloneable(location, {'disk_format': 'raw'}))
-+
-+ def test_uncloneable_bad_format(self):
-+ self.stubs.Set(self.driver, '_get_fsid', lambda: 'abc')
-+ location = 'rbd://abc/pool/image/snap'
-+ formats = ['qcow2', 'vmdk', 'vdi']
-+ for f in formats:
-+ self.assertFalse(
-+ self.driver._is_cloneable(location, {'disk_format': f}))
-
- def _copy_image(self):
- @contextlib.contextmanager
-@@ -504,26 +517,37 @@ class ManagedRBDTestCase(DriverTestCase):
- super(ManagedRBDTestCase, self).setUp()
- fake_image.stub_out_image_service(self.stubs)
- self.volume.driver.set_initialized()
-+ self.called = []
-
-- def _clone_volume_from_image(self, expected_status,
-- clone_works=True):
-+ def _create_volume_from_image(self, expected_status, raw=False,
-+ clone_error=False):
- """Try to clone a volume from an image, and check the status
- afterwards.
-+
-+ NOTE: if clone_error is True we force the image type to raw otherwise
-+ clone_image is not called
- """
-- def fake_clone_image(volume, image_location, image_id):
-- return {'provider_location': None}, True
-+ def mock_clone_image(volume, image_location, image_id, image_meta):
-+ self.called.append('clone_image')
-+ if clone_error:
-+ raise exception.CinderException()
-+ else:
-+ return {'provider_location': None}, True
-
-- def fake_clone_error(volume, image_location, image_id):
-- raise exception.CinderException()
-+ if clone_error:
-+ raw = True
-
-- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
-- if clone_works:
-- self.stubs.Set(self.volume.driver, 'clone_image', fake_clone_image)
-+ # See tests.image.fake for image types.
-+ if raw:
-+ expected_calls = ['clone_image']
-+ image_id = '155d900f-4e14-4e4c-a73d-069cbf4541e6'
- else:
-- self.stubs.Set(self.volume.driver, 'clone_image', fake_clone_error)
-+ expected_calls = ['clone_image', 'create_volume',
-+ 'copy_image_to_volume']
-+ image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
-
-- image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
- volume_id = 1
-+
- # creating volume testdata
- db.volume_create(self.context,
- {'id': volume_id,
-@@ -533,58 +557,88 @@ class ManagedRBDTestCase(DriverTestCase):
- 'status': 'creating',
- 'instance_uuid': None,
- 'host': 'dummy'})
-- try:
-- if clone_works:
-- self.volume.create_volume(self.context,
-- volume_id,
-- image_id=image_id)
-- else:
-- self.assertRaises(exception.CinderException,
-- self.volume.create_volume,
-- self.context,
-- volume_id,
-- image_id=image_id)
--
-- volume = db.volume_get(self.context, volume_id)
-- self.assertEqual(volume['status'], expected_status)
-- finally:
-- # cleanup
-- db.volume_destroy(self.context, volume_id)
-+
-+ with mock.patch.object(self.volume.driver, 'create_volume') as \
-+ mock_create_volume:
-+ with mock.patch.object(self.volume.driver, 'clone_image',
-+ mock_clone_image):
-+ with mock.patch.object(create_volume.CreateVolumeFromSpecTask,
-+ '_copy_image_to_volume') as \
-+ mock_copy_image_to_volume:
-+ self.volume.driver._is_cloneable = mock.Mock()
-+ self.volume.driver._is_cloneable.return_value = True
-+
-+ try:
-+ if not clone_error:
-+ self.volume.create_volume(self.context,
-+ volume_id,
-+ image_id=image_id)
-+ else:
-+ self.assertRaises(exception.CinderException,
-+ self.volume.create_volume,
-+ self.context,
-+ volume_id,
-+ image_id=image_id)
-+
-+ volume = db.volume_get(self.context, volume_id)
-+ self.assertEqual(volume['status'], expected_status)
-+ finally:
-+ # cleanup
-+ db.volume_destroy(self.context, volume_id)
-+
-+ if raw:
-+ self.assertEquals(self.called, ['clone_image'])
-+
-+ mock_create_volume.assert_called()
-+ mock_copy_image_to_volume.assert_called()
-
- def test_create_vol_from_image_status_available(self):
-- """Verify that before cloning, an image is in the available state."""
-- self._clone_volume_from_image('available', True)
-+ """Clone raw image then verify volume is in available state."""
-+ self._create_volume_from_image('available', raw=True)
-+
-+ def test_create_vol_from_non_raw_image_status_available(self):
-+ """Clone non-raw image then verify volume is in available state."""
-+ self._create_volume_from_image('available', raw=False)
-
- def test_create_vol_from_image_status_error(self):
-- """Verify that before cloning, an image is in the available state."""
-- self._clone_volume_from_image('error', False)
-+ """Clone raw image with failure then verify volume is in error
-+ state.
-+ """
-+ self._create_volume_from_image('error', raw=True, clone_error=True)
-
- def test_clone_image(self):
-- # Test Failure Case(s)
-- expected = ({}, False)
-+ driver = self.volume.driver
-
-- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: False)
-- image_loc = (object(), object())
-- actual = self.volume.driver.clone_image(object(), image_loc, object())
-- self.assertEqual(expected, actual)
-+ # Test uncloneable case(s)
-+ with mock.patch.object(driver, '_is_cloneable',
-+ lambda *args: False) as mock_is_cloneable:
-+ image_loc = (mock.Mock(), mock.Mock())
-+ actual = driver.clone_image(mock.Mock(), image_loc,
-+ mock.Mock(), {})
-+ self.assertEqual(({}, False), actual)
-
-- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
-- self.assertEqual(expected,
-- self.volume.driver.clone_image(object(), None, None))
-+ self.assertEqual(({}, False),
-+ driver.clone_image(object(), None, None, {}))
-
-- # Test Success Case(s)
-+ # Test success case(s)
- expected = ({'provider_location': None}, True)
--
-- self.stubs.Set(self.volume.driver, '_parse_location',
-- lambda x: ('a', 'b', 'c', 'd'))
--
-- self.stubs.Set(self.volume.driver, '_clone', lambda *args: None)
-- self.stubs.Set(self.volume.driver, '_resize', lambda *args: None)
-- actual = self.volume.driver.clone_image(object(), image_loc, object())
-- self.assertEqual(expected, actual)
-+ mpo = mock.patch.object
-+ with mpo(driver, '_is_cloneable', lambda *args: True):
-+ with mpo(driver, '_parse_location',
-+ lambda x: ('a', 'b', 'c', 'd')):
-+ with mpo(driver, '_clone') as mock_clone:
-+ with mpo(driver, '_resize') as mock_resize:
-+ actual = driver.clone_image(mock.Mock(), image_loc,
-+ mock.Mock(),
-+ {'disk_format': 'raw'})
-+ self.assertEqual(expected, actual)
-+ mock_clone.assert_called()
-+ mock_resize.assert_called()
-
- def test_clone_success(self):
-- self.stubs.Set(self.volume.driver, '_is_cloneable', lambda x: True)
-- self.stubs.Set(self.volume.driver, 'clone_image', lambda a, b, c: True)
-+ self.stubs.Set(self.volume.driver, '_is_cloneable', lambda *args: True)
-+ self.stubs.Set(self.volume.driver, 'clone_image',
-+ lambda a, b, c, d: True)
- image_id = 'c905cedb-7281-47e4-8a62-f26bc5fc4c77'
-- self.assertTrue(self.volume.driver.clone_image({}, image_id, image_id))
-+ self.assertTrue(self.volume.driver.clone_image({},
-+ image_id, image_id, {}))
-diff --git a/cinder/tests/test_volume.py b/cinder/tests/test_volume.py
-index 4a0d475..928799f 100644
---- a/cinder/tests/test_volume.py
-+++ b/cinder/tests/test_volume.py
-@@ -1216,7 +1216,7 @@ class VolumeTestCase(BaseVolumeTestCase):
- def fake_fetch_to_raw(ctx, image_service, image_id, path, size=None):
- pass
-
-- def fake_clone_image(volume_ref, image_location, image_id):
-+ def fake_clone_image(volume_ref, image_location, image_id, image_meta):
- return {'provider_location': None}, True
-
- dst_fd, dst_path = tempfile.mkstemp()
-diff --git a/cinder/volume/driver.py b/cinder/volume/driver.py
-index 21dd12b..dd2be3c 100644
---- a/cinder/volume/driver.py
-+++ b/cinder/volume/driver.py
-@@ -396,7 +396,7 @@ class VolumeDriver(object):
- connector.disconnect_volume(attach_info['conn']['data'],
- attach_info['device'])
-
-- def clone_image(self, volume, image_location, image_id):
-+ def clone_image(self, volume, image_location, image_id, image_meta):
- """Create a volume efficiently from an existing image.
-
- image_location is a string whose format depends on the
-@@ -407,6 +407,11 @@ class VolumeDriver(object):
- It can be used by the driver to introspect internal
- stores or registry to do an efficient image clone.
-
-+ image_meta is a dictionary that includes 'disk_format' (e.g.
-+ raw, qcow2) and other image attributes that allow drivers to
-+ decide whether they can clone the image without first requiring
-+ conversion.
-+
- Returns a dict of volume properties eg. provider_location,
- boolean indicating whether cloning occurred
- """
-diff --git a/cinder/volume/drivers/gpfs.py b/cinder/volume/drivers/gpfs.py
-index 9a1a397..8792ad8 100644
---- a/cinder/volume/drivers/gpfs.py
-+++ b/cinder/volume/drivers/gpfs.py
-@@ -463,7 +463,7 @@ class GPFSDriver(driver.VolumeDriver):
- return '100M'
- return '%sG' % size_in_g
-
-- def clone_image(self, volume, image_location, image_id):
-+ def clone_image(self, volume, image_location, image_id, image_meta):
- return self._clone_image(volume, image_location, image_id)
-
- def _is_cloneable(self, image_id):
-diff --git a/cinder/volume/drivers/lvm.py b/cinder/volume/drivers/lvm.py
-index 094436f..c6023eb 100644
---- a/cinder/volume/drivers/lvm.py
-+++ b/cinder/volume/drivers/lvm.py
-@@ -322,7 +322,7 @@ class LVMVolumeDriver(driver.VolumeDriver):
- finally:
- self.delete_snapshot(temp_snapshot)
-
-- def clone_image(self, volume, image_location, image_id):
-+ def clone_image(self, volume, image_location, image_id, image_meta):
- return None, False
-
- def backup_volume(self, context, backup, backup_service):
-diff --git a/cinder/volume/drivers/netapp/nfs.py b/cinder/volume/drivers/netapp/nfs.py
-index 602a1dc..9463137 100644
---- a/cinder/volume/drivers/netapp/nfs.py
-+++ b/cinder/volume/drivers/netapp/nfs.py
-@@ -374,7 +374,7 @@ class NetAppNFSDriver(nfs.NfsDriver):
- LOG.warning(_('Exception during deleting %s'), ex.__str__())
- return False
-
-- def clone_image(self, volume, image_location, image_id):
-+ def clone_image(self, volume, image_location, image_id, image_meta):
- """Create a volume efficiently from an existing image.
-
- image_location is a string whose format depends on the
-diff --git a/cinder/volume/drivers/rbd.py b/cinder/volume/drivers/rbd.py
-index 775ab16..7ed59d0 100644
---- a/cinder/volume/drivers/rbd.py
-+++ b/cinder/volume/drivers/rbd.py
-@@ -706,7 +706,7 @@ class RBDDriver(driver.VolumeDriver):
- with RADOSClient(self) as client:
- return client.cluster.get_fsid()
-
-- def _is_cloneable(self, image_location):
-+ def _is_cloneable(self, image_location, image_meta):
- try:
- fsid, pool, image, snapshot = self._parse_location(image_location)
- except exception.ImageUnacceptable as e:
-@@ -718,6 +718,13 @@ class RBDDriver(driver.VolumeDriver):
- LOG.debug(reason)
- return False
-
-+ if image_meta['disk_format'] != 'raw':
-+ reason = _("rbd image clone requires image format to be "
-+ "'raw' but image {0} is '{1}'").format(
-+ image_location, image_meta['disk_format'])
-+ LOG.debug(reason)
-+ return False
-+
- # check that we can read the image
- try:
- with RBDVolumeProxy(self, image,
-@@ -730,9 +737,10 @@ class RBDDriver(driver.VolumeDriver):
- dict(loc=image_location, err=e))
- return False
-
-- def clone_image(self, volume, image_location, image_id):
-+ def clone_image(self, volume, image_location, image_id, image_meta):
- image_location = image_location[0] if image_location else None
-- if image_location is None or not self._is_cloneable(image_location):
-+ if image_location is None or not self._is_cloneable(
-+ image_location, image_meta):
- return ({}, False)
- prefix, pool, image, snapshot = self._parse_location(image_location)
- self._clone(volume, pool, image, snapshot)
-diff --git a/cinder/volume/drivers/scality.py b/cinder/volume/drivers/scality.py
-index 4cf49c6..abd6c29 100644
---- a/cinder/volume/drivers/scality.py
-+++ b/cinder/volume/drivers/scality.py
-@@ -250,7 +250,7 @@ class ScalityDriver(driver.VolumeDriver):
- image_meta,
- self.local_path(volume))
-
-- def clone_image(self, volume, image_location, image_id):
-+ def clone_image(self, volume, image_location, image_id, image_meta):
- """Create a volume efficiently from an existing image.
-
- image_location is a string whose format depends on the
-diff --git a/cinder/volume/flows/create_volume/__init__.py b/cinder/volume/flows/create_volume/__init__.py
-index bb7acd3..e5aa371 100644
---- a/cinder/volume/flows/create_volume/__init__.py
-+++ b/cinder/volume/flows/create_volume/__init__.py
-@@ -1438,7 +1438,7 @@ class CreateVolumeFromSpecTask(base.CinderTask):
- # dict containing provider_location for cloned volume
- # and clone status.
- model_update, cloned = self.driver.clone_image(
-- volume_ref, image_location, image_id)
-+ volume_ref, image_location, image_id, image_meta)
- if not cloned:
- # TODO(harlowja): what needs to be rolled back in the clone if this
- # volume create fails?? Likely this should be a subflow or broken
---
-1.8.5.1
-
state_path = /var/lib/cinder
lock_path = /var/lib/cinder/tmp
volumes_dir = /etc/cinder/volumes
-iscsi_helper = tgtadm
+iscsi_helper = lioadm
sql_connection = mysql://cinder:cinder@localhost/cinder
-rpc_backend = cinder.openstack.common.rpc.impl_qpid
rootwrap_config = /etc/cinder/rootwrap.conf
auth_strategy = keystone
-# Note this config mode is not supported by scsi-target-utils in RHEL <= 6.4
-# include /etc/cinder/volumes/
-# So instead please add the following line (without the leading comment char)
-# to the top of /etc/tgt/targets.conf
-# include /etc/cinder/volumes/*
+include /etc/cinder/volumes/
+++ /dev/null
-description "OpenStack Cinder API Server"
-
-start on stopped rc RUNLEVEL=[2345]
-stop on runlevel [S016]
-
-respawn
-
-exec su -s /bin/sh -c "exec /usr/bin/cinder-api --config-file /usr/share/cinder/cinder-dist.conf --config-file /etc/cinder/cinder.conf --logfile /var/log/cinder/api.log" cinder
+++ /dev/null
-description "OpenStack Cinder Backup Server"
-
-start on stopped rc RUNLEVEL=[2345]
-stop on runlevel [S016]
-
-respawn
-
-exec su -s /bin/sh -c "exec /usr/bin/cinder-backup --config-file /usr/share/cinder/cinder-dist.conf --config-file /etc/cinder/cinder.conf --logfile /var/log/cinder/backup.log" cinder
+++ /dev/null
-description "OpenStack Cinder Scheduler Server"
-
-start on stopped rc RUNLEVEL=[2345]
-stop on runlevel [S016]
-
-respawn
-
-exec su -s /bin/sh -c "exec /usr/bin/cinder-scheduler --config-file /usr/share/cinder/cinder-dist.conf --config-file /etc/cinder/cinder.conf --logfile /var/log/cinder/scheduler.log" cinder
+++ /dev/null
-description "OpenStack Cinder Volume Server"
-
-start on stopped rc RUNLEVEL=[2345]
-stop on runlevel [S016]
-
-respawn
-
-exec su -s /bin/sh -c "exec /usr/bin/cinder-volume --config-file /usr/share/cinder/cinder-dist.conf --config-file /etc/cinder/cinder.conf --logfile /var/log/cinder/volume.log" cinder
%global with_doc %{!?_without_doc:1}%{?_without_doc:0}
Name: openstack-cinder
-Version: 2013.2
-Release: 0.9.b3%{?dist}
+Version: 2014.2
+Release: 2%{?dist}
Summary: OpenStack Volume service
Group: Applications/System
License: ASL 2.0
URL: http://www.openstack.org/software/openstack-storage/
-Source0: https://launchpad.net/cinder/havana/havana-3/+download/cinder-%{version}.b3.tar.gz
+Source0: https://launchpad.net/cinder/icehouse/2014.1/+download/cinder-%{version}.tar.gz
Source1: cinder-dist.conf
Source2: cinder.logrotate
Source3: cinder-tgt.conf
Source10: openstack-cinder-api.init
-Source100: openstack-cinder-api.upstart
Source11: openstack-cinder-scheduler.init
-Source110: openstack-cinder-scheduler.upstart
Source12: openstack-cinder-volume.init
-Source120: openstack-cinder-volume.upstart
Source13: openstack-cinder-backup.init
-Source130: openstack-cinder-backup.upstart
Source20: cinder-sudoers
#
-# patches_base=2013.2.b3
+# patches_base=2014.1.1
#
-Patch0001: 0001-Ensure-we-don-t-access-the-net-when-building-docs.patch
-Patch0002: 0002-Use-updated-parallel-install-versions-of-epel-packag.patch
-Patch0003: 0003-Remove-runtime-dep-on-python-pbr-python-d2to1.patch
-Patch0004: 0004-Revert-Use-oslo.sphinx-and-remove-local-copy-of-doc-.patch
-Patch0005: MIRA-Do-not-clone-non-raw-images-in-rbd-backend.patch
+Patch0001: 0001-Remove-runtime-dep-on-python-pbr-python-d2to1.patch
+Patch0002: 0002-Revert-Switch-over-to-oslosphinx.patch
BuildArch: noarch
BuildRequires: intltool
BuildRequires: python-d2to1
+BuildRequires: python-oslo-sphinx
BuildRequires: python-pbr
-BuildRequires: python-sphinx10
+BuildRequires: python-sphinx
BuildRequires: python-setuptools
BuildRequires: python-netaddr
-BuildRequires: openstack-utils
-BuildRequires: python-paste-deploy1.5
-BuildRequires: python-routes1.12
-BuildRequires: python-sqlalchemy0.7
-BuildRequires: python-webob1.2
Requires: openstack-utils
Requires: python-cinder = %{version}-%{release}
Requires: qemu-img
Requires: sysfsutils
-Requires: python-paramiko
+Requires: python-paramiko >= 1.13.0
Requires: python-qpid
Requires: python-kombu
Requires: python-amqplib
-Requires: python-importlib
-
-Requires: python-eventlet
-Requires: python-greenlet
-Requires: python-iso8601
-Requires: python-netaddr
-Requires: python-lxml
-Requires: python-anyjson
-Requires: python-cheetah
-Requires: python-stevedore
+
+Requires: python-eventlet >= 0.13.0
+Requires: python-greenlet >= 0.3.2
+Requires: python-iso8601 >= 0.1.9
+Requires: python-netaddr >= 0.7.6
+Requires: python-lxml >= 2.3
+Requires: python-anyjson >= 0.3.3
+#Requires: python-cheetah
+Requires: python-stevedore >= 0.14
Requires: python-suds
-Requires: python-sqlalchemy0.7
-Requires: python-migrate
+Requires: python-sqlalchemy >= 0.8.4, python-sqlalchemy < 0.95, python-sqlalchemy > 0.9.5, python-sqlalchemy <= 0.9.99
+Requires: python-migrate >= 0.9.1
-Requires: python-paste-deploy1.5
-Requires: python-routes1.12
-Requires: python-webob1.2
+Requires: python-paste-deploy >= 1.5.0
+Requires: python-routes >= 1.12.3, python-routes < 2.0, python-routes > 2.0
+Requires: python-webob >= 1.2.3
Requires: python-glanceclient >= 1:0
-Requires: python-swiftclient >= 1.2
-Requires: python-keystoneclient
-Requires: python-novaclient >= 2.14
+Requires: python-swiftclient >= 2.0.2
+Requires: python-keystoneclient
+Requires: python-keystonemiddleware >= 1.0.0
+Requires: python-novaclient >= 1:2.17
Requires: python-oslo-config >= 1:1.2.0
-Requires: python-six
+Requires: python-six >= 1.7.0
-Requires: python-babel
+Requires: python-babel >= 1.3
Requires: python-lockfile
+Requires: python-oslo-rootwrap
+Requires: python-rtslib-fb
+Requires: python-taskflow
+Requires: python-oslo-messaging >= 1.3.0-0.1.a9
+
+Requires: python-ceph
+#Requires: iscsi-initiator-utils
+
%description -n python-cinder
OpenStack Volume (codename Cinder) provides services to manage and
access block storage volumes for use by Virtual Machine instances.
# Required to build module documents
BuildRequires: python-eventlet
-BuildRequires: python-routes1.12
-BuildRequires: python-sqlalchemy0.7
-BuildRequires: python-webob1.2
+BuildRequires: python-routes
+BuildRequires: python-sqlalchemy
+BuildRequires: python-webob
# while not strictly required, quiets the build down when building docs.
BuildRequires: python-migrate, python-iso8601
%patch0001 -p1
%patch0002 -p1
-%patch0003 -p1
-%patch0004 -p1
-%patch0005 -p1
find . \( -name .gitignore -o -name .placeholder \) -delete
sed -i s/REDHATCINDERRELEASE/%{release}/ cinder/version.py
%build
-
-# Move authtoken configuration out of paste.ini
-openstack-config --del etc/cinder/api-paste.ini filter:authtoken admin_tenant_name
-openstack-config --del etc/cinder/api-paste.ini filter:authtoken admin_user
-openstack-config --del etc/cinder/api-paste.ini filter:authtoken admin_password
-openstack-config --del etc/cinder/api-paste.ini filter:authtoken auth_host
-openstack-config --del etc/cinder/api-paste.ini filter:authtoken auth_port
-openstack-config --del etc/cinder/api-paste.ini filter:authtoken auth_protocol
-
%{__python} setup.py build
%install
pushd doc
%if 0%{?with_doc}
-SPHINX_DEBUG=1 sphinx-1.0-build -b html source build/html
+SPHINX_DEBUG=1 sphinx-build -b html source build/html
# Fix hidden-file-or-dir warnings
rm -fr build/html/.doctrees build/html/.buildinfo
%endif
# Create dir link to avoid a sphinx-build exception
mkdir -p build/man/.doctrees/
ln -s . build/man/.doctrees/man
-SPHINX_DEBUG=1 sphinx-1.0-build -b man -c source source/man build/man
+SPHINX_DEBUG=1 sphinx-build -b man -c source source/man build/man
mkdir -p %{buildroot}%{_mandir}/man1
install -p -D -m 644 build/man/*.1 %{buildroot}%{_mandir}/man1/
# Install pid directory
install -d -m 755 %{buildroot}%{_localstatedir}/run/cinder
-# Install upstart jobs examples
-install -d -m 755 %{buildroot}%{_datadir}/cinder
-install -p -m 644 %{SOURCE100} %{buildroot}%{_datadir}/cinder/
-install -p -m 644 %{SOURCE110} %{buildroot}%{_datadir}/cinder/
-install -p -m 644 %{SOURCE120} %{buildroot}%{_datadir}/cinder/
-install -p -m 644 %{SOURCE130} %{buildroot}%{_datadir}/cinder/
-
# Install rootwrap files in /usr/share/cinder/rootwrap
mkdir -p %{buildroot}%{_datarootdir}/cinder/rootwrap/
install -p -D -m 644 etc/cinder/rootwrap.d/* %{buildroot}%{_datarootdir}/cinder/rootwrap/
exit 0
%post
-if [ $1 -eq 1 ] ; then
- # Initial installation
- for svc in volume api scheduler; do
- /sbin/chkconfig --add openstack-cinder-$svc
- done
-fi
+for svc in volume api scheduler backup; do
+ /sbin/chkconfig --add openstack-cinder-$svc
+done
%preun
if [ $1 -eq 0 ] ; then
- for svc in volume api scheduler; do
+ for svc in volume api scheduler backup; do
/sbin/service openstack-cinder-${svc} stop > /dev/null 2>&1
/sbin/chkconfig --del openstack-cinder-${svc}
done
%postun
if [ $1 -ge 1 ] ; then
# Package upgrade, not uninstall
- for svc in volume api scheduler; do
+ for svc in volume api scheduler backup; do
/sbin/service openstack-cinder-${svc} condrestart > /dev/null 2>&1 || :
done
fi
%endif
%changelog
+* Wed Jun 11 2014 Eric Harney <eharney@redhat.com> - 2014.1.1-2
+- Add dependency on iscsi-initiator-utils
+
+* Mon Jun 09 2014 Eric Harney <eharney@redhat.com> - 2014.1.1-1
+- Update to Icehouse stable release 1
+
+* Sat Jun 07 2014 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2014.1-4
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild
+
+* Tue Apr 29 2014 Alan Pevec <apevec@redhat.com> - 2014.1-3
+- drop crudini build dependency
+
+* Mon Apr 21 2014 Eric Harney <eharney@redhat.com> - 2014.1-2
+- Remove qpid settings from cinder-dist.conf
+
+* Thu Apr 17 2014 Eric Harney <eharney@redhat.com> - 2014.1-1
+- Update to 2014.1 (Icehouse)
+
+* Tue Apr 15 2014 Eric Harney <eharney@redhat.com> - 2014.1-0.10.rc3
+- Add python-oslo-messaging requirement
+- Add GlusterFS delete patch
+- Add systemd patches (not used yet)
+
+* Tue Apr 15 2014 Eric Harney <eharney@redhat.com> - 2014.1-0.9.rc3
+- Update to Icehouse RC3
+
+* Mon Apr 07 2014 Eric Harney <eharney@redhat.com> - 2014.1-0.8.rc2
+- Update to Icehouse RC2
+- Icehouse requires newer version of python-six
+
+* Thu Mar 27 2014 Eric Harney <eharney@redhat.com> - 2014.1-0.7.rc1
+- Update to Icehouse RC1
+
+* Tue Mar 25 2014 Pádraig Brady <pbrady@redhat.com> - 2014.1-0.6.b3
+- Depend on python-rtslib and targetcli rather than scsi-target-utils
+
+* Fri Mar 21 2014 Pádraig Brady <pbrady@redhat.com> - 2014.1-0.5.b3
+- Use lioadm iSCSI helper rather than tgtadm
+
+* Sun Mar 16 2014 Eric Harney <eharney@redhat.com> - 2014.1-0.4.b3
+- Update to Icehouse milestone 3
+- Add deps on python-oslo-rootwrap, python-taskflow
+
+* Mon Jan 27 2014 Eric Harney <eharney@redhat.com> - 2014.1-0.3.b2
+- Update to Icehouse milestone 2
+
+* Mon Jan 06 2014 Pádraig Brady <pbrady@redhat.com> - 2014.1-0.2.b1
+- Set python-six min version to ensure updated
+
+* Thu Dec 19 2013 Eric Harney <eharney@redhat.com> - 2014.1-0.1.b1
+- Update to Icehouse milestone 1
+
+* Mon Oct 28 2013 Eric Harney <eharney@redhat.com> - 2013.2-2
+- Fix GlusterFS volume driver clone operations
+
+* Thu Oct 17 2013 Eric Harney <eharney@redhat.com> - 2013.2-1
+- Update to 2013.2 (Havana)
+- Restart/remove cinder-backup service during upgrade/uninstallation
+
+* Wed Oct 16 2013 Eric Harney <eharney@redhat.com> - 2013.2-0.13.rc3
+- Update to Havana RC3
+
+* Fri Oct 11 2013 Eric Harney <eharney@redhat.com> - 2013.2-0.12.rc2
+- Update to Havana RC2
+
+* Tue Oct 08 2013 Eric Harney <eharney@redhat.com> - 2013.2-0.11.rc1
+- Update to Havana RC1
+- Fix python-novaclient req epoch
+
+* Mon Sep 23 2013 Eric Harney <eharney@redhat.com> - 2013.2-0.10.b3
+- Depend on python-novaclient 2.15
+
* Wed Sep 18 2013 Eric Harney <eharney@redhat.com> - 2013.2-0.9.b3
- Add cinder-dist.conf
- Tighten permissions on /var/log/cinder
- Update to Havana milestone 3
- Add dependency on python-novaclient
-* Thu Aug 29 2013 Pádraig Brady <pbrady@redhat.com> - 2013.2-0.7.b2
+* Thu Aug 29 2013 PÃ\83¡draig Brady <pbrady@redhat.com> - 2013.2-0.7.b2
- Add dependency on sysfsutils to support the fiber channel driver
-* Mon Aug 26 2013 Eric Harney <eharney@redhat.com> - 2013.2-0.5.b2
+* Mon Aug 26 2013 Eric Harney <eharney@redhat.com> - 2013.2-0.6.b2
- Add cinder-backup service init script
-* Mon Jul 22 2013 Pádraig Brady <pbrady@redhat.com> - 2013.2-0.4.b2
+* Sat Aug 03 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2013.2-0.5.b2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild
+
+* Mon Jul 22 2013 Pádraig Brady <pbrady@redhat.com> - 2013.2-0.4.b2
- Add dependency on python-suds to support the netapp driver
- Add dependency on python-keystoneclient for auth token middleware
- Add dependency on qemu-img for volume creation from Glance images
-* Sun Jul 21 2013 Pádraig Brady <pbrady@redhat.com> - 2013.2-0.3.b2
+* Sun Jul 21 2013 PÃ\83¡draig Brady <pbrady@redhat.com> - 2013.2-0.3.b2
- Update to Havana milestone 2
* Thu Jun 13 2013 Eric Harney <eharney@redhat.com> - 2013.2-0.2.b1
- Update to Havana milestone 1
-* Fri May 10 2013 Eric Harney <eharney@redhat.com> - 2013.1.1-1
-- Update to Grizzly stable release 1
+* Mon May 13 2013 Eric Harney <eharney@redhat.com> - 2013.1.1-1
+- Update to Grizzly stable release 1, 2013.1.1
* Mon Apr 08 2013 Eric Harney <eharney@redhat.com> - 2013.1-2
- Backport fix for GlusterFS driver get_volume_stats
-- Adjust to support sqlalchemy-0.8.0
* Thu Apr 04 2013 Eric Harney <eharney@redhat.com> - 2013.1-1
- Update to Grizzly final release
+* Tue Apr 2 2013 Pádraig Brady <pbrady@redhat.com> - 2013.1-0.6.rc3
+- Adjust to support sqlalchemy-0.8.0
+
* Wed Mar 27 2013 Eric Harney <eharney@redhat.com> - 2013.1-0.5.rc3
- Update to Grizzly RC3 release
* Mon Mar 18 2013 Eric Harney <eharney@redhat.com> - 2013.1-0.5.rc1
- Update to Grizzly RC1 release
-* Tue Mar 05 2013 Pádraig Brady <P@draigBrady.com> - 2013.1-0.4.g3
+* Tue Mar 05 2013 PÃ\83¡draig Brady <P@draigBrady.com> - 2013.1-0.4.g3
- Add dependency on python-stevedore
-* Wed Feb 27 2013 Eric Harney <eharney@redhat.com> - 2013.1-0.2.g3
+* Mon Feb 25 2013 Eric Harney <eharney@redhat.com> - 2013.1-0.3.g3
+- Fix build issues with G-3 update
+
+* Mon Feb 25 2013 Eric Harney <eharney@redhat.com> - 2013.1-0.2.g3
- Update to Grizzly milestone 3
+* Thu Feb 14 2013 Fedora Release Engineering <rel-eng@lists.fedoraproject.org> - 2013.1-0.2.g2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_19_Mass_Rebuild
+
* Thu Jan 10 2013 Eric Harney <eharney@redhat.com> - 2013.1-0.1.g2
- Update to Grizzly milestone 2
-* Thu Dec 20 2012 Eric Harney <eharney@redhat.com> - 2013.1-0.1.g1
+* Thu Nov 29 2012 Eric Harney <eharney@redhat.com> - 2013.1-0.1.g1
- Update to Grizzly milestone 1
-* Mon Dec 03 2012 Eric Harney <eharney@redhat.com> - 2012.2.1-1
-- Update to Folsom stable release 1
-
-* Wed Nov 14 2012 Eric Harney <eharney@redhat.com> - 2012.2-4
+* Wed Nov 14 2012 Eric Harney <eharney@redhat.com> - 2012.2-2
- Remove unused dependency on python-daemon
-* Wed Oct 31 2012 Pádraig Brady <P@draigBrady.com> - 2012.2-3
-- Adjust to be compatible with python-migrate-0.6
+* Thu Sep 27 2012 Pádraig Brady <P@draigBrady.com> - 2012.2-1
+- Update to Folsom final
+
+* Fri Sep 21 2012 Pádraig Brady <P@draigBrady.com> - 2012.2-0.5.rc1
+- Update to Folsom RC1
+
+* Fri Sep 21 2012 Pádraig Brady <P@draigBrady.com> - 2012.2-0.4.f3
+- Fix to ensure that tgt configuration is honored
+
+* Mon Sep 17 2012 Pádraig Brady <P@draigBrady.com> - 2012.2-0.3.f3
+- Move user config out of /etc/cinder/api-paste.ini
+- Require python-cinderclient
+
+* Mon Sep 3 2012 Pádraig Brady <P@draigBrady.com> - 2012.2-0.2.f3
+- Initial release
-* Wed Oct 24 2012 Pádraig Brady <P@draigBrady.com> - 2012.2-2
-- Initial Folsom release