From a4ec8a272921fe55879ad0dc1b2cb850f1ea993f Mon Sep 17 00:00:00 2001 From: zhangsong Date: Mon, 30 Nov 2015 11:58:16 +0800 Subject: Sheepdog: Change storelocation format This patch changes storelocation format from 'sheepdog://image' to 'sheepdog://addr:port:image'. It add ip and port to the location. This offers the advantages of: 1.The ip and port represents a sheepdog cluster which contains the image. The format like 'sheepdog://addr:port:image' can describe image location accurately. 2.When clone an image in cinder, it need ip and port to decide whether the image is in the same sheepdog cluster with cinder-backend, this is very important for us to determine if the image is cloneable. This patch also change a long data type to int to compatible with python version 3.x. Change-Id: Id79487e05e3af2f7b1287476ddbb174ad9de78c8 --- glance_store/_drivers/sheepdog.py | 36 ++++++++---- glance_store/tests/unit/test_sheepdog_store.py | 79 +++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 13 deletions(-) diff --git a/glance_store/_drivers/sheepdog.py b/glance_store/_drivers/sheepdog.py index 18a5b29..f5031ad 100644 --- a/glance_store/_drivers/sheepdog.py +++ b/glance_store/_drivers/sheepdog.py @@ -81,7 +81,7 @@ class SheepdogImage(object): Sheepdog Usage: collie vdi list -r -a address -p port image """ out = self._run_command("list -r", None) - return long(out.split(' ')[3]) + return int(out.split(' ')[3]) def read(self, offset, count): """ @@ -134,22 +134,34 @@ class StoreLocation(glance_store.location.StoreLocation): """ Class describing a Sheepdog URI. This is of the form: - sheepdog://image + sheepdog://addr:port:image """ def process_specs(self): self.image = self.specs.get('image') + self.addr = self.specs.get('addr') + self.port = self.specs.get('port') def get_uri(self): - return "sheepdog://%s" % self.image + return "sheepdog://%(addr)s:%(port)s:%(image)s" % { + 'addr': self.addr, + 'port': self.port, + 'image': self.image} def parse_uri(self, uri): valid_schema = 'sheepdog://' if not uri.startswith(valid_schema): - reason = _("URI must start with '%s://'") % valid_schema + reason = _("URI must start with '%s'") % valid_schema raise exceptions.BadStoreUri(message=reason) - self.image = uri[11:] + pieces = uri[len(valid_schema):].split(':') + if len(pieces) == 3: + self.addr, self.port, self.image = pieces + # This is used for backwards compatibility. + else: + self.image = pieces[0] + self.port = self.conf.glance_store.sheepdog_store_port + self.addr = self.conf.glance_store.sheepdog_store_address class ImageIterator(object): @@ -177,7 +189,7 @@ class Store(glance_store.driver.Store): _CAPABILITIES = (capabilities.BitMasks.RW_ACCESS | capabilities.BitMasks.DRIVER_REUSABLE) OPTIONS = _SHEEPDOG_OPTS - EXAMPLE_URL = "sheepdog://image" + EXAMPLE_URL = "sheepdog://addr:port:image" def get_schemes(self): return ('sheepdog',) @@ -225,7 +237,7 @@ class Store(glance_store.driver.Store): """ loc = location.store_location - image = SheepdogImage(self.addr, self.port, loc.image, + image = SheepdogImage(loc.addr, loc.port, loc.image, self.READ_CHUNKSIZE) if not image.exist(): raise exceptions.NotFound(_("Sheepdog image %s does not exist") @@ -244,7 +256,7 @@ class Store(glance_store.driver.Store): """ loc = location.store_location - image = SheepdogImage(self.addr, self.port, loc.image, + image = SheepdogImage(loc.addr, loc.port, loc.image, self.READ_CHUNKSIZE) if not image.exist(): raise exceptions.NotFound(_("Sheepdog image %s does not exist") @@ -273,7 +285,11 @@ class Store(glance_store.driver.Store): raise exceptions.Duplicate(_("Sheepdog image %s already exists") % image_id) - location = StoreLocation({'image': image_id}, self.conf) + location = StoreLocation({ + 'image': image_id, + 'addr': self.addr, + 'port': self.port + }, self.conf) checksum = hashlib.md5() image.create(image_size) @@ -307,7 +323,7 @@ class Store(glance_store.driver.Store): """ loc = location.store_location - image = SheepdogImage(self.addr, self.port, loc.image, + image = SheepdogImage(loc.addr, loc.port, loc.image, self.WRITE_CHUNKSIZE) if not image.exist(): raise exceptions.NotFound(_("Sheepdog image %s does not exist") % diff --git a/glance_store/tests/unit/test_sheepdog_store.py b/glance_store/tests/unit/test_sheepdog_store.py index 39f8e05..213b090 100644 --- a/glance_store/tests/unit/test_sheepdog_store.py +++ b/glance_store/tests/unit/test_sheepdog_store.py @@ -42,8 +42,11 @@ class TestSheepdogStore(base.StoreBaseTest, self.addCleanup(execute.stop) self.store = sheepdog.Store(self.conf) self.store.configure() + self.store_specs = {'image': 'fake_image', + 'addr': 'fake_addr', + 'port': 'fake_port'} - def test_cleanup_when_add_image_exception(self): + def test_add_image(self): called_commands = [] def _fake_run_command(command, data, *params): @@ -52,11 +55,81 @@ class TestSheepdogStore(base.StoreBaseTest, with mock.patch.object(sheepdog.SheepdogImage, '_run_command') as cmd: cmd.side_effect = _fake_run_command data = six.BytesIO(b'xx') - self.store.add('fake_image_id', data, 2) + ret = self.store.add('fake_image_id', data, 2) self.assertEqual(called_commands, ['list -r', 'create', 'write']) + self.assertEqual(ret[1], 2) + + def test_cleanup_when_add_image_exception(self): + called_commands = [] + + def _fake_run_command(command, data, *params): + if command == 'write': + raise exceptions.BackendException + else: + called_commands.append(command) + + with mock.patch.object(sheepdog.SheepdogImage, '_run_command') as cmd: + cmd.side_effect = _fake_run_command + data = six.BytesIO(b'xx') + self.assertRaises(exceptions.BackendException, self.store.add, + 'fake_image_id', data, 2) + self.assertTrue('delete' in called_commands) + + def test_add_duplicate_image(self): + def _fake_run_command(command, data, *params): + if command == "list -r": + return "= fake_volume 0 1000" + + with mock.patch.object(sheepdog.SheepdogImage, '_run_command') as cmd: + cmd.side_effect = _fake_run_command + data = six.BytesIO(b'xx') + self.assertRaises(exceptions.Duplicate, self.store.add, + 'fake_image_id', data, 2) + + def test_get(self): + def _fake_run_command(command, data, *params): + if command == "list -r": + return "= fake_volume 0 1000" + + with mock.patch.object(sheepdog.SheepdogImage, '_run_command') as cmd: + cmd.side_effect = _fake_run_command + loc = location.Location('test_sheepdog_store', + sheepdog.StoreLocation, + self.conf, store_specs=self.store_specs) + ret = self.store.get(loc) + self.assertEqual(ret[1], 1000) def test_partial_get(self): loc = location.Location('test_sheepdog_store', sheepdog.StoreLocation, - self.conf, store_specs={'image': 'fake_image'}) + self.conf, store_specs=self.store_specs) self.assertRaises(exceptions.StoreRandomGetNotSupported, self.store.get, loc, chunk_size=1) + + def test_get_size(self): + def _fake_run_command(command, data, *params): + if command == "list -r": + return "= fake_volume 0 1000" + + with mock.patch.object(sheepdog.SheepdogImage, '_run_command') as cmd: + cmd.side_effect = _fake_run_command + loc = location.Location('test_sheepdog_store', + sheepdog.StoreLocation, + self.conf, store_specs=self.store_specs) + ret = self.store.get_size(loc) + self.assertEqual(ret, 1000) + + def test_delete(self): + called_commands = [] + + def _fake_run_command(command, data, *params): + called_commands.append(command) + if command == "list -r": + return "= fake_volume 0 1000" + + with mock.patch.object(sheepdog.SheepdogImage, '_run_command') as cmd: + cmd.side_effect = _fake_run_command + loc = location.Location('test_sheepdog_store', + sheepdog.StoreLocation, + self.conf, store_specs=self.store_specs) + self.store.delete(loc) + self.assertEqual(called_commands, ['list -r', 'delete']) -- cgit v1.2.1