def setUp(self):
     """Establish a clean test environment"""
     self.stubs = stubout.StubOutForTesting()
     stubs.stub_out_filesystem_backend()
     self.orig_chunksize = ChunkedFile.CHUNKSIZE
     ChunkedFile.CHUNKSIZE = 10
     self.store = Store(FILESYSTEM_OPTIONS)
 def setUp(self):
     """Establish a clean test environment"""
     super(TestStore, self).setUp()
     self.orig_read_chunksize = Store.READ_CHUNKSIZE
     self.orig_write_chunksize = Store.WRITE_CHUNKSIZE
     Store.READ_CHUNKSIZE = Store.WRITE_CHUNKSIZE = 10
     self.store = Store()
 def setUp(self):
     """Establish a clean test environment"""
     self.stubs = stubout.StubOutForTesting()
     stubs.stub_out_filesystem_backend()
     self.orig_chunksize = ChunkedFile.CHUNKSIZE
     ChunkedFile.CHUNKSIZE = 10
     self.store = Store(test_utils.TestConfigOpts(FILESYSTEM_CONF))
Exemple #4
0
 def setUp(self):
     """Establish a clean test environment"""
     super(TestStore, self).setUp()
     self.orig_read_chunksize = Store.READ_CHUNKSIZE
     self.orig_write_chunksize = Store.WRITE_CHUNKSIZE
     Store.READ_CHUNKSIZE = Store.WRITE_CHUNKSIZE = 10
     self.store = Store()
 def setUp(self):
     """Establish a clean test environment"""
     super(TestStore, self).setUp()
     self.orig_chunksize = ChunkedFile.CHUNKSIZE
     ChunkedFile.CHUNKSIZE = 10
     self.store = Store()
class TestStore(base.IsolatedUnitTest):
    def setUp(self):
        """Establish a clean test environment"""
        super(TestStore, self).setUp()
        self.orig_chunksize = ChunkedFile.CHUNKSIZE
        ChunkedFile.CHUNKSIZE = 10
        self.store = Store()

    def tearDown(self):
        """Clear the test environment"""
        super(TestStore, self).tearDown()
        ChunkedFile.CHUNKSIZE = self.orig_chunksize

    def test_get(self):
        """Test a "normal" retrieval of an image in chunks"""
        # First add an image...
        image_id = uuidutils.generate_uuid()
        file_contents = "chunk00000remainder"
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum = self.store.add(image_id, image_file,
                                                  len(file_contents))

        # Now read it back...
        uri = "file:///%s/%s" % (self.test_dir, image_id)
        loc = get_location_from_uri(uri)
        (image_file, image_size) = self.store.get(loc)

        expected_data = "chunk00000remainder"
        expected_num_chunks = 2
        data = ""
        num_chunks = 0

        for chunk in image_file:
            num_chunks += 1
            data += chunk
        self.assertEqual(expected_data, data)
        self.assertEqual(expected_num_chunks, num_chunks)

    def test_get_non_existing(self):
        """
        Test that trying to retrieve a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///%s/non-existing" % self.test_dir)
        self.assertRaises(exception.NotFound, self.store.get, loc)

    def test_add(self):
        """Test that we can add an image via the filesystem backend"""
        ChunkedFile.CHUNKSIZE = 1024
        expected_image_id = uuidutils.generate_uuid()
        expected_file_size = 1024 * 5  # 5K
        expected_file_contents = "*" * expected_file_size
        expected_checksum = hashlib.md5(expected_file_contents).hexdigest()
        expected_location = "file://%s/%s" % (self.test_dir, expected_image_id)
        image_file = StringIO.StringIO(expected_file_contents)

        location, size, checksum = self.store.add(expected_image_id,
                                                  image_file,
                                                  expected_file_size)

        self.assertEquals(expected_location, location)
        self.assertEquals(expected_file_size, size)
        self.assertEquals(expected_checksum, checksum)

        uri = "file:///%s/%s" % (self.test_dir, expected_image_id)
        loc = get_location_from_uri(uri)
        (new_image_file, new_image_size) = self.store.get(loc)
        new_image_contents = ""
        new_image_file_size = 0

        for chunk in new_image_file:
            new_image_file_size += len(chunk)
            new_image_contents += chunk

        self.assertEquals(expected_file_contents, new_image_contents)
        self.assertEquals(expected_file_size, new_image_file_size)

    def test_add_already_existing(self):
        """
        Tests that adding an image with an existing identifier
        raises an appropriate exception
        """
        ChunkedFile.CHUNKSIZE = 1024
        image_id = uuidutils.generate_uuid()
        file_size = 1024 * 5  # 5K
        file_contents = "*" * file_size
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum = self.store.add(image_id, image_file,
                                                  file_size)
        image_file = StringIO.StringIO("nevergonnamakeit")
        self.assertRaises(exception.Duplicate, self.store.add, image_id,
                          image_file, 0)

    def _do_test_add_write_failure(self, errno, exception):
        ChunkedFile.CHUNKSIZE = 1024
        image_id = uuidutils.generate_uuid()
        file_size = 1024 * 5  # 5K
        file_contents = "*" * file_size
        path = os.path.join(self.test_dir, image_id)
        image_file = StringIO.StringIO(file_contents)

        m = mox.Mox()
        m.StubOutWithMock(__builtin__, 'open')
        e = IOError()
        e.errno = errno
        open(path, 'wb').AndRaise(e)
        m.ReplayAll()

        try:
            self.assertRaises(exception, self.store.add, image_id, image_file,
                              0)
            self.assertFalse(os.path.exists(path))
        finally:
            m.VerifyAll()
            m.UnsetStubs()

    def test_add_storage_full(self):
        """
        Tests that adding an image without enough space on disk
        raises an appropriate exception
        """
        self._do_test_add_write_failure(errno.ENOSPC, exception.StorageFull)

    def test_add_file_too_big(self):
        """
        Tests that adding an excessively large image file
        raises an appropriate exception
        """
        self._do_test_add_write_failure(errno.EFBIG, exception.StorageFull)

    def test_add_storage_write_denied(self):
        """
        Tests that adding an image with insufficient filestore permissions
        raises an appropriate exception
        """
        self._do_test_add_write_failure(errno.EACCES,
                                        exception.StorageWriteDenied)

    def test_add_other_failure(self):
        """
        Tests that a non-space-related IOError does not raise a
        StorageFull exception.
        """
        self._do_test_add_write_failure(errno.ENOTDIR, IOError)

    def test_add_cleanup_on_read_failure(self):
        """
        Tests the partial image file is cleaned up after a read
        failure.
        """
        ChunkedFile.CHUNKSIZE = 1024
        image_id = uuidutils.generate_uuid()
        file_size = 1024 * 5  # 5K
        file_contents = "*" * file_size
        path = os.path.join(self.test_dir, image_id)
        image_file = StringIO.StringIO(file_contents)

        def fake_Error(size):
            raise AttributeError()

        self.stubs.Set(image_file, 'read', fake_Error)

        self.assertRaises(AttributeError, self.store.add, image_id, image_file,
                          0)
        self.assertFalse(os.path.exists(path))

    def test_delete(self):
        """
        Test we can delete an existing image in the filesystem store
        """
        # First add an image
        image_id = uuidutils.generate_uuid()
        file_size = 1024 * 5  # 5K
        file_contents = "*" * file_size
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum = self.store.add(image_id, image_file,
                                                  file_size)

        # Now check that we can delete it
        uri = "file:///%s/%s" % (self.test_dir, image_id)
        loc = get_location_from_uri(uri)
        self.store.delete(loc)

        self.assertRaises(exception.NotFound, self.store.get, loc)

    def test_delete_non_existing(self):
        """
        Test that trying to delete a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///tmp/glance-tests/non-existing")
        self.assertRaises(exception.NotFound, self.store.delete, loc)
 def setUp(self):
     """Establish a clean test environment"""
     super(TestStore, self).setUp()
     self.orig_chunksize = ChunkedFile.CHUNKSIZE
     ChunkedFile.CHUNKSIZE = 10
     self.store = Store(self.conf)
class TestStore(base.IsolatedUnitTest):

    def setUp(self):
        """Establish a clean test environment"""
        super(TestStore, self).setUp()
        self.orig_chunksize = ChunkedFile.CHUNKSIZE
        ChunkedFile.CHUNKSIZE = 10
        self.store = Store(self.conf)

    def tearDown(self):
        """Clear the test environment"""
        super(TestStore, self).tearDown()
        ChunkedFile.CHUNKSIZE = self.orig_chunksize

    def test_get(self):
        """Test a "normal" retrieval of an image in chunks"""
        # First add an image...
        image_id = utils.generate_uuid()
        file_contents = "chunk00000remainder"
        location = "file://%s/%s" % (self.test_dir, image_id)
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum = self.store.add(image_id,
                                                  image_file,
                                                  len(file_contents))

        # Now read it back...
        uri = "file:///%s/%s" % (self.test_dir, image_id)
        loc = get_location_from_uri(uri)
        (image_file, image_size) = self.store.get(loc)

        expected_data = "chunk00000remainder"
        expected_num_chunks = 2
        data = ""
        num_chunks = 0

        for chunk in image_file:
            num_chunks += 1
            data += chunk
        self.assertEqual(expected_data, data)
        self.assertEqual(expected_num_chunks, num_chunks)

    def test_get_non_existing(self):
        """
        Test that trying to retrieve a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///%s/non-existing" % self.test_dir)
        self.assertRaises(exception.NotFound,
                          self.store.get,
                          loc)

    def test_add(self):
        """Test that we can add an image via the filesystem backend"""
        ChunkedFile.CHUNKSIZE = 1024
        expected_image_id = utils.generate_uuid()
        expected_file_size = 1024 * 5  # 5K
        expected_file_contents = "*" * expected_file_size
        expected_checksum = hashlib.md5(expected_file_contents).hexdigest()
        expected_location = "file://%s/%s" % (self.test_dir,
                                              expected_image_id)
        image_file = StringIO.StringIO(expected_file_contents)

        location, size, checksum = self.store.add(expected_image_id,
                                                  image_file,
                                                  expected_file_size)

        self.assertEquals(expected_location, location)
        self.assertEquals(expected_file_size, size)
        self.assertEquals(expected_checksum, checksum)

        uri = "file:///%s/%s" % (self.test_dir, expected_image_id)
        loc = get_location_from_uri(uri)
        (new_image_file, new_image_size) = self.store.get(loc)
        new_image_contents = ""
        new_image_file_size = 0

        for chunk in new_image_file:
            new_image_file_size += len(chunk)
            new_image_contents += chunk

        self.assertEquals(expected_file_contents, new_image_contents)
        self.assertEquals(expected_file_size, new_image_file_size)

    def test_add_already_existing(self):
        """
        Tests that adding an image with an existing identifier
        raises an appropriate exception
        """
        ChunkedFile.CHUNKSIZE = 1024
        image_id = utils.generate_uuid()
        file_size = 1024 * 5  # 5K
        file_contents = "*" * file_size
        location = "file://%s/%s" % (self.test_dir, image_id)
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum = self.store.add(image_id,
                                                  image_file,
                                                  file_size)
        image_file = StringIO.StringIO("nevergonnamakeit")
        self.assertRaises(exception.Duplicate,
                          self.store.add,
                          image_id, image_file, 0)

    def test_add_storage_full(self):
        """
        Tests that adding an image without enough space on disk
        raises an appropriate exception
        """
        ChunkedFile.CHUNKSIZE = 1024
        image_id = utils.generate_uuid()
        file_size = 1024 * 5  # 5K
        file_contents = "*" * file_size
        location = "file://%s/%s" % (self.test_dir, image_id)
        image_file = StringIO.StringIO(file_contents)

        def fake_IO_Error(size):
            raise IOError

        self.stubs.Set(image_file, 'read', fake_IO_Error)
        self.assertRaises(exception.StorageFull,
                          self.store.add,
                          image_id, image_file, 0)

    def test_delete(self):
        """
        Test we can delete an existing image in the filesystem store
        """
        # First add an image
        image_id = utils.generate_uuid()
        file_size = 1024 * 5  # 5K
        file_contents = "*" * file_size
        location = "file://%s/%s" % (self.test_dir, image_id)
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum = self.store.add(image_id,
                                                  image_file,
                                                  file_size)

        # Now check that we can delete it
        uri = "file:///%s/%s" % (self.test_dir, image_id)
        loc = get_location_from_uri(uri)
        self.store.delete(loc)

        self.assertRaises(exception.NotFound, self.store.get, loc)

    def test_delete_non_existing(self):
        """
        Test that trying to delete a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///tmp/glance-tests/non-existing")
        self.assertRaises(exception.NotFound,
                          self.store.delete,
                          loc)
class TestStore(base.IsolatedUnitTest):

    def setUp(self):
        """Establish a clean test environment"""
        super(TestStore, self).setUp()
        self.orig_chunksize = ChunkedFile.CHUNKSIZE
        ChunkedFile.CHUNKSIZE = 10
        self.store = Store()

    def tearDown(self):
        """Clear the test environment"""
        super(TestStore, self).tearDown()
        ChunkedFile.CHUNKSIZE = self.orig_chunksize

    def test_configure_add_single_datadir(self):
        """
        Tests filesystem specified by filesystem_store_datadir
        are parsed correctly.
        """
        store = self.useFixture(fixtures.TempDir()).path
        CONF.set_override('filesystem_store_datadir', store)
        self.store.configure_add()
        self.assertEqual(self.store.datadir, store)

    def test_configure_add_with_single_and_multi_datadirs(self):
        """
        Tests BadStoreConfiguration exception is raised if both
        filesystem_store_datadir and filesystem_store_datadirs are specified.
        """
        store_map = [self.useFixture(fixtures.TempDir()).path,
                     self.useFixture(fixtures.TempDir()).path]
        CONF.set_override('filesystem_store_datadirs',
                          [store_map[0] + ":100",
                           store_map[1] + ":200"])
        self.assertRaises(exception.BadStoreConfiguration,
                          self.store.configure_add)

    def test_configure_add_without_single_and_multi_datadirs(self):
        """
        Tests BadStoreConfiguration exception is raised if neither
        filesystem_store_datadir nor filesystem_store_datadirs are specified.
        """
        CONF.clear_override('filesystem_store_datadir')
        self.assertRaises(exception.BadStoreConfiguration,
                          self.store.configure_add)

    def test_configure_add_with_multi_datadirs(self):
        """
        Tests multiple filesystem specified by filesystem_store_datadirs
        are parsed correctly.
        """
        store_map = [self.useFixture(fixtures.TempDir()).path,
                     self.useFixture(fixtures.TempDir()).path]
        CONF.clear_override('filesystem_store_datadir')
        CONF.set_override('filesystem_store_datadirs',
                          [store_map[0] + ":100",
                           store_map[1] + ":200"])
        self.store.configure_add()

        expected_priority_map = {100: [store_map[0]], 200: [store_map[1]]}
        expected_priority_list = [200, 100]
        self.assertEqual(self.store.priority_data_map, expected_priority_map)
        self.assertEqual(self.store.priority_list, expected_priority_list)

    def test_configure_add_invalid_priority(self):
        """
        Tests invalid priority specified by filesystem_store_datadirs
        param raises BadStoreConfiguration exception.
        """
        CONF.clear_override('filesystem_store_datadir')
        CONF.set_override('filesystem_store_datadirs',
                          [self.useFixture(fixtures.TempDir()).path + ":100",
                           self.useFixture(fixtures.TempDir()).path +
                           ":invalid"])
        self.assertRaises(exception.BadStoreConfiguration,
                          self.store.configure_add)

    def test_configure_add_same_dir_multiple_times(self):
        """
        Tests BadStoreConfiguration exception is raised if same directory
        is specified multiple times in filesystem_store_datadirs.
        """
        store_map = [self.useFixture(fixtures.TempDir()).path,
                     self.useFixture(fixtures.TempDir()).path]
        CONF.clear_override('filesystem_store_datadir')
        CONF.set_override('filesystem_store_datadirs',
                          [store_map[0] + ":100",
                           store_map[1] + ":200",
                           store_map[0] + ":300"])
        self.assertRaises(exception.BadStoreConfiguration,
                          self.store.configure_add)

    def test_configure_add_with_empty_datadir_path(self):
        """
        Tests BadStoreConfiguration exception is raised if empty directory
        path is specified in filesystem_store_datadirs.
        """
        CONF.clear_override('filesystem_store_datadir')
        CONF.set_override('filesystem_store_datadirs', [''])
        self.assertRaises(exception.BadStoreConfiguration,
                          self.store.configure_add)

    def test_configure_add_with_readonly_datadir_path(self):
        """
        Tests BadStoreConfiguration exception is raised if directory
        path specified in filesystem_store_datadirs is readonly.
        """
        readonly_dir = self.useFixture(fixtures.TempDir()).path
        os.chmod(readonly_dir, 0o444)
        CONF.clear_override('filesystem_store_datadir')
        CONF.set_override('filesystem_store_datadirs', [readonly_dir])
        self.assertRaises(exception.BadStoreConfiguration,
                          self.store.configure_add)

    def test_get(self):
        """Test a "normal" retrieval of an image in chunks"""
        # First add an image...
        image_id = str(uuid.uuid4())
        file_contents = "chunk00000remainder"
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum, _ = self.store.add(image_id,
                                                     image_file,
                                                     len(file_contents))

        # Now read it back...
        uri = "file:///%s/%s" % (self.test_dir, image_id)
        loc = get_location_from_uri(uri)
        (image_file, image_size) = self.store.get(loc)

        expected_data = "chunk00000remainder"
        expected_num_chunks = 2
        data = ""
        num_chunks = 0

        for chunk in image_file:
            num_chunks += 1
            data += chunk
        self.assertEqual(expected_data, data)
        self.assertEqual(expected_num_chunks, num_chunks)

    def test_get_non_existing(self):
        """
        Test that trying to retrieve a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///%s/non-existing" % self.test_dir)
        self.assertRaises(exception.NotFound,
                          self.store.get,
                          loc)

    def test_add(self):
        """Test that we can add an image via the filesystem backend"""
        ChunkedFile.CHUNKSIZE = 1024
        expected_image_id = str(uuid.uuid4())
        expected_file_size = 5 * units.Ki  # 5K
        expected_file_contents = "*" * expected_file_size
        expected_checksum = hashlib.md5(expected_file_contents).hexdigest()
        expected_location = "file://%s/%s" % (self.test_dir,
                                              expected_image_id)
        image_file = StringIO.StringIO(expected_file_contents)

        location, size, checksum, _ = self.store.add(expected_image_id,
                                                     image_file,
                                                     expected_file_size)

        self.assertEqual(expected_location, location)
        self.assertEqual(expected_file_size, size)
        self.assertEqual(expected_checksum, checksum)

        uri = "file:///%s/%s" % (self.test_dir, expected_image_id)
        loc = get_location_from_uri(uri)
        (new_image_file, new_image_size) = self.store.get(loc)
        new_image_contents = ""
        new_image_file_size = 0

        for chunk in new_image_file:
            new_image_file_size += len(chunk)
            new_image_contents += chunk

        self.assertEqual(expected_file_contents, new_image_contents)
        self.assertEqual(expected_file_size, new_image_file_size)

    def test_add_with_multiple_dirs(self):
        """Test adding multiple filesystem directories."""
        store_map = [self.useFixture(fixtures.TempDir()).path,
                     self.useFixture(fixtures.TempDir()).path]
        CONF.clear_override('filesystem_store_datadir')
        CONF.set_override('filesystem_store_datadirs',
                          [store_map[0] + ":100",
                           store_map[1] + ":200"])
        self.store.configure_add()

        """Test that we can add an image via the filesystem backend"""
        ChunkedFile.CHUNKSIZE = 1024
        expected_image_id = str(uuid.uuid4())
        expected_file_size = 5 * units.Ki  # 5K
        expected_file_contents = "*" * expected_file_size
        expected_checksum = hashlib.md5(expected_file_contents).hexdigest()
        expected_location = "file://%s/%s" % (store_map[1],
                                              expected_image_id)
        image_file = StringIO.StringIO(expected_file_contents)

        location, size, checksum, _ = self.store.add(expected_image_id,
                                                     image_file,
                                                     expected_file_size)

        self.assertEqual(expected_location, location)
        self.assertEqual(expected_file_size, size)
        self.assertEqual(expected_checksum, checksum)

        loc = get_location_from_uri(expected_location)
        (new_image_file, new_image_size) = self.store.get(loc)
        new_image_contents = ""
        new_image_file_size = 0

        for chunk in new_image_file:
            new_image_file_size += len(chunk)
            new_image_contents += chunk

        self.assertEqual(expected_file_contents, new_image_contents)
        self.assertEqual(expected_file_size, new_image_file_size)

    def test_add_with_multiple_dirs_storage_full(self):
        """
        Test StorageFull exception is raised if no filesystem directory
        is found that can store an image.
        """
        store_map = [self.useFixture(fixtures.TempDir()).path,
                     self.useFixture(fixtures.TempDir()).path]
        CONF.clear_override('filesystem_store_datadir')
        CONF.set_override('filesystem_store_datadirs',
                          [store_map[0] + ":100",
                           store_map[1] + ":200"])
        self.store.configure_add()

        def fake_get_capacity_info(mount_point):
            return 0

        self.stubs.Set(self.store, '_get_capacity_info',
                       fake_get_capacity_info)
        ChunkedFile.CHUNKSIZE = 1024
        expected_image_id = str(uuid.uuid4())
        expected_file_size = 5 * units.Ki  # 5K
        expected_file_contents = "*" * expected_file_size
        image_file = StringIO.StringIO(expected_file_contents)

        self.assertRaises(exception.StorageFull, self.store.add,
                          expected_image_id, image_file, expected_file_size)

    def test_add_check_metadata_success(self):
        expected_image_id = str(uuid.uuid4())
        in_metadata = {'akey': u'some value', 'list': [u'1', u'2', u'3']}
        jsonfilename = os.path.join(self.test_dir,
                                    "storage_metadata.%s" % expected_image_id)

        self.config(filesystem_store_metadata_file=jsonfilename)
        with open(jsonfilename, 'w') as fptr:
            json.dump(in_metadata, fptr)
        expected_file_size = 10
        expected_file_contents = "*" * expected_file_size
        image_file = StringIO.StringIO(expected_file_contents)

        location, size, checksum, metadata = self.store.add(expected_image_id,
                                                            image_file,
                                                            expected_file_size)

        self.assertEqual(metadata, in_metadata)

    def test_add_check_metadata_bad_data(self):
        expected_image_id = str(uuid.uuid4())
        in_metadata = {'akey': 10}  # only unicode is allowed
        jsonfilename = os.path.join(self.test_dir,
                                    "storage_metadata.%s" % expected_image_id)

        self.config(filesystem_store_metadata_file=jsonfilename)
        with open(jsonfilename, 'w') as fptr:
            json.dump(in_metadata, fptr)
        expected_file_size = 10
        expected_file_contents = "*" * expected_file_size
        image_file = StringIO.StringIO(expected_file_contents)

        location, size, checksum, metadata = self.store.add(expected_image_id,
                                                            image_file,
                                                            expected_file_size)

        self.assertEqual(metadata, {})

    def test_add_check_metadata_bad_nosuch_file(self):
        expected_image_id = str(uuid.uuid4())
        jsonfilename = os.path.join(self.test_dir,
                                    "storage_metadata.%s" % expected_image_id)

        self.config(filesystem_store_metadata_file=jsonfilename)
        expected_file_size = 10
        expected_file_contents = "*" * expected_file_size
        image_file = StringIO.StringIO(expected_file_contents)

        location, size, checksum, metadata = self.store.add(expected_image_id,
                                                            image_file,
                                                            expected_file_size)

        self.assertEqual(metadata, {})

    def test_add_already_existing(self):
        """
        Tests that adding an image with an existing identifier
        raises an appropriate exception
        """
        ChunkedFile.CHUNKSIZE = 1024
        image_id = str(uuid.uuid4())
        file_size = 5 * units.Ki  # 5K
        file_contents = "*" * file_size
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum, _ = self.store.add(image_id,
                                                     image_file,
                                                     file_size)
        image_file = StringIO.StringIO("nevergonnamakeit")
        self.assertRaises(exception.Duplicate,
                          self.store.add,
                          image_id, image_file, 0)

    def _do_test_add_write_failure(self, errno, exception):
        ChunkedFile.CHUNKSIZE = 1024
        image_id = str(uuid.uuid4())
        file_size = 5 * units.Ki  # 5K
        file_contents = "*" * file_size
        path = os.path.join(self.test_dir, image_id)
        image_file = StringIO.StringIO(file_contents)

        m = mox.Mox()
        m.StubOutWithMock(__builtin__, 'open')
        e = IOError()
        e.errno = errno
        open(path, 'wb').AndRaise(e)
        m.ReplayAll()

        try:
            self.assertRaises(exception,
                              self.store.add,
                              image_id, image_file, 0)
            self.assertFalse(os.path.exists(path))
        finally:
            m.VerifyAll()
            m.UnsetStubs()

    def test_add_storage_full(self):
        """
        Tests that adding an image without enough space on disk
        raises an appropriate exception
        """
        self._do_test_add_write_failure(errno.ENOSPC, exception.StorageFull)

    def test_add_file_too_big(self):
        """
        Tests that adding an excessively large image file
        raises an appropriate exception
        """
        self._do_test_add_write_failure(errno.EFBIG, exception.StorageFull)

    def test_add_storage_write_denied(self):
        """
        Tests that adding an image with insufficient filestore permissions
        raises an appropriate exception
        """
        self._do_test_add_write_failure(errno.EACCES,
                                        exception.StorageWriteDenied)

    def test_add_other_failure(self):
        """
        Tests that a non-space-related IOError does not raise a
        StorageFull exception.
        """
        self._do_test_add_write_failure(errno.ENOTDIR, IOError)

    def test_add_cleanup_on_read_failure(self):
        """
        Tests the partial image file is cleaned up after a read
        failure.
        """
        ChunkedFile.CHUNKSIZE = 1024
        image_id = str(uuid.uuid4())
        file_size = 5 * units.Ki  # 5K
        file_contents = "*" * file_size
        path = os.path.join(self.test_dir, image_id)
        image_file = StringIO.StringIO(file_contents)

        def fake_Error(size):
            raise AttributeError()

        self.stubs.Set(image_file, 'read', fake_Error)

        self.assertRaises(AttributeError,
                          self.store.add,
                          image_id, image_file, 0)
        self.assertFalse(os.path.exists(path))

    def test_delete(self):
        """
        Test we can delete an existing image in the filesystem store
        """
        # First add an image
        image_id = str(uuid.uuid4())
        file_size = 5 * units.Ki  # 5K
        file_contents = "*" * file_size
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum, _ = self.store.add(image_id,
                                                     image_file,
                                                     file_size)

        # Now check that we can delete it
        uri = "file:///%s/%s" % (self.test_dir, image_id)
        loc = get_location_from_uri(uri)
        self.store.delete(loc)

        self.assertRaises(exception.NotFound, self.store.get, loc)

    def test_delete_non_existing(self):
        """
        Test that trying to delete a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///tmp/glance-tests/non-existing")
        self.assertRaises(exception.NotFound,
                          self.store.delete,
                          loc)
class TestStore(unittest.TestCase):

    def setUp(self):
        """Establish a clean test environment"""
        self.stubs = stubout.StubOutForTesting()
        stubs.stub_out_filesystem_backend()
        self.orig_chunksize = ChunkedFile.CHUNKSIZE
        ChunkedFile.CHUNKSIZE = 10
        self.store = Store(test_utils.TestConfigOpts(FILESYSTEM_CONF))

    def tearDown(self):
        """Clear the test environment"""
        stubs.clean_out_fake_filesystem_backend()
        self.stubs.UnsetAll()
        ChunkedFile.CHUNKSIZE = self.orig_chunksize

    def test_get(self):
        """Test a "normal" retrieval of an image in chunks"""
        uri = "file:///tmp/glance-tests/2"
        loc = get_location_from_uri(uri)
        (image_file, image_size) = self.store.get(loc)

        expected_data = "chunk00000remainder"
        expected_num_chunks = 2
        data = ""
        num_chunks = 0

        for chunk in image_file:
            num_chunks += 1
            data += chunk
        self.assertEqual(expected_data, data)
        self.assertEqual(expected_num_chunks, num_chunks)

    def test_get_non_existing(self):
        """
        Test that trying to retrieve a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///tmp/glance-tests/non-existing")
        self.assertRaises(exception.NotFound,
                          self.store.get,
                          loc)

    def test_add(self):
        """Test that we can add an image via the filesystem backend"""
        ChunkedFile.CHUNKSIZE = 1024
        expected_image_id = utils.generate_uuid()
        expected_file_size = 1024 * 5  # 5K
        expected_file_contents = "*" * expected_file_size
        expected_checksum = hashlib.md5(expected_file_contents).hexdigest()
        expected_location = "file://%s/%s" % (stubs.FAKE_FILESYSTEM_ROOTDIR,
                                              expected_image_id)
        image_file = StringIO.StringIO(expected_file_contents)

        location, size, checksum = self.store.add(expected_image_id,
                                                  image_file,
                                                  expected_file_size)

        self.assertEquals(expected_location, location)
        self.assertEquals(expected_file_size, size)
        self.assertEquals(expected_checksum, checksum)

        uri = "file:///tmp/glance-tests/%s" % expected_image_id
        loc = get_location_from_uri(uri)
        (new_image_file, new_image_size) = self.store.get(loc)
        new_image_contents = ""
        new_image_file_size = 0

        for chunk in new_image_file:
            new_image_file_size += len(chunk)
            new_image_contents += chunk

        self.assertEquals(expected_file_contents, new_image_contents)
        self.assertEquals(expected_file_size, new_image_file_size)

    def test_add_already_existing(self):
        """
        Tests that adding an image with an existing identifier
        raises an appropriate exception
        """
        image_file = StringIO.StringIO("nevergonnamakeit")
        self.assertRaises(exception.Duplicate,
                          self.store.add,
                          '2', image_file, 0)

    def test_delete(self):
        """
        Test we can delete an existing image in the filesystem store
        """
        uri = "file:///tmp/glance-tests/2"
        loc = get_location_from_uri(uri)
        self.store.delete(loc)

        self.assertRaises(exception.NotFound, self.store.get, loc)

    def test_delete_non_existing(self):
        """
        Test that trying to delete a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///tmp/glance-tests/non-existing")
        self.assertRaises(exception.NotFound,
                          self.store.delete,
                          loc)
class TestStore(base.IsolatedUnitTest):

    def setUp(self):
        """Establish a clean test environment"""
        super(TestStore, self).setUp()
        self.orig_chunksize = ChunkedFile.CHUNKSIZE
        ChunkedFile.CHUNKSIZE = 10
        self.store = Store()

    def tearDown(self):
        """Clear the test environment"""
        super(TestStore, self).tearDown()
        ChunkedFile.CHUNKSIZE = self.orig_chunksize

    def test_get(self):
        """Test a "normal" retrieval of an image in chunks"""
        # First add an image...
        image_id = uuidutils.generate_uuid()
        file_contents = "chunk00000remainder"
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum, _ = self.store.add(image_id,
                                                     image_file,
                                                     len(file_contents))

        # Now read it back...
        uri = "file:///%s/%s" % (self.test_dir, image_id)
        loc = get_location_from_uri(uri)
        (image_file, image_size) = self.store.get(loc)

        expected_data = "chunk00000remainder"
        expected_num_chunks = 2
        data = ""
        num_chunks = 0

        for chunk in image_file:
            num_chunks += 1
            data += chunk
        self.assertEqual(expected_data, data)
        self.assertEqual(expected_num_chunks, num_chunks)

    def test_get_non_existing(self):
        """
        Test that trying to retrieve a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///%s/non-existing" % self.test_dir)
        self.assertRaises(exception.NotFound,
                          self.store.get,
                          loc)

    def test_add(self):
        """Test that we can add an image via the filesystem backend"""
        ChunkedFile.CHUNKSIZE = 1024
        expected_image_id = uuidutils.generate_uuid()
        expected_file_size = 1024 * 5  # 5K
        expected_file_contents = "*" * expected_file_size
        expected_checksum = hashlib.md5(expected_file_contents).hexdigest()
        expected_location = "file://%s/%s" % (self.test_dir,
                                              expected_image_id)
        image_file = StringIO.StringIO(expected_file_contents)

        location, size, checksum, _ = self.store.add(expected_image_id,
                                                     image_file,
                                                     expected_file_size)

        self.assertEqual(expected_location, location)
        self.assertEqual(expected_file_size, size)
        self.assertEqual(expected_checksum, checksum)

        uri = "file:///%s/%s" % (self.test_dir, expected_image_id)
        loc = get_location_from_uri(uri)
        (new_image_file, new_image_size) = self.store.get(loc)
        new_image_contents = ""
        new_image_file_size = 0

        for chunk in new_image_file:
            new_image_file_size += len(chunk)
            new_image_contents += chunk

        self.assertEqual(expected_file_contents, new_image_contents)
        self.assertEqual(expected_file_size, new_image_file_size)

    def test_add_check_metadata_success(self):
        expected_image_id = uuidutils.generate_uuid()
        in_metadata = {'akey': u'some value', 'list': [u'1', u'2', u'3']}
        jsonfilename = os.path.join(self.test_dir,
                                    "storage_metadata.%s" % expected_image_id)

        self.config(filesystem_store_metadata_file=jsonfilename)
        with open(jsonfilename, 'w') as fptr:
            json.dump(in_metadata, fptr)
        expected_file_size = 10
        expected_file_contents = "*" * expected_file_size
        image_file = StringIO.StringIO(expected_file_contents)

        location, size, checksum, metadata = self.store.add(expected_image_id,
                                                            image_file,
                                                            expected_file_size)

        self.assertEqual(metadata, in_metadata)

    def test_add_check_metadata_bad_data(self):
        expected_image_id = uuidutils.generate_uuid()
        in_metadata = {'akey': 10}  # only unicode is allowed
        jsonfilename = os.path.join(self.test_dir,
                                    "storage_metadata.%s" % expected_image_id)

        self.config(filesystem_store_metadata_file=jsonfilename)
        with open(jsonfilename, 'w') as fptr:
            json.dump(in_metadata, fptr)
        expected_file_size = 10
        expected_file_contents = "*" * expected_file_size
        image_file = StringIO.StringIO(expected_file_contents)

        location, size, checksum, metadata = self.store.add(expected_image_id,
                                                            image_file,
                                                            expected_file_size)

        self.assertEqual(metadata, {})

    def test_add_check_metadata_bad_nosuch_file(self):
        expected_image_id = uuidutils.generate_uuid()
        jsonfilename = os.path.join(self.test_dir,
                                    "storage_metadata.%s" % expected_image_id)

        self.config(filesystem_store_metadata_file=jsonfilename)
        expected_file_size = 10
        expected_file_contents = "*" * expected_file_size
        image_file = StringIO.StringIO(expected_file_contents)

        location, size, checksum, metadata = self.store.add(expected_image_id,
                                                            image_file,
                                                            expected_file_size)

        self.assertEqual(metadata, {})

    def test_add_already_existing(self):
        """
        Tests that adding an image with an existing identifier
        raises an appropriate exception
        """
        ChunkedFile.CHUNKSIZE = 1024
        image_id = uuidutils.generate_uuid()
        file_size = 1024 * 5  # 5K
        file_contents = "*" * file_size
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum, _ = self.store.add(image_id,
                                                     image_file,
                                                     file_size)
        image_file = StringIO.StringIO("nevergonnamakeit")
        self.assertRaises(exception.Duplicate,
                          self.store.add,
                          image_id, image_file, 0)

    def _do_test_add_write_failure(self, errno, exception):
        ChunkedFile.CHUNKSIZE = 1024
        image_id = uuidutils.generate_uuid()
        file_size = 1024 * 5  # 5K
        file_contents = "*" * file_size
        path = os.path.join(self.test_dir, image_id)
        image_file = StringIO.StringIO(file_contents)

        m = mox.Mox()
        m.StubOutWithMock(__builtin__, 'open')
        e = IOError()
        e.errno = errno
        open(path, 'wb').AndRaise(e)
        m.ReplayAll()

        try:
            self.assertRaises(exception,
                              self.store.add,
                              image_id, image_file, 0)
            self.assertFalse(os.path.exists(path))
        finally:
            m.VerifyAll()
            m.UnsetStubs()

    def test_add_storage_full(self):
        """
        Tests that adding an image without enough space on disk
        raises an appropriate exception
        """
        self._do_test_add_write_failure(errno.ENOSPC, exception.StorageFull)

    def test_add_file_too_big(self):
        """
        Tests that adding an excessively large image file
        raises an appropriate exception
        """
        self._do_test_add_write_failure(errno.EFBIG, exception.StorageFull)

    def test_add_storage_write_denied(self):
        """
        Tests that adding an image with insufficient filestore permissions
        raises an appropriate exception
        """
        self._do_test_add_write_failure(errno.EACCES,
                                        exception.StorageWriteDenied)

    def test_add_other_failure(self):
        """
        Tests that a non-space-related IOError does not raise a
        StorageFull exception.
        """
        self._do_test_add_write_failure(errno.ENOTDIR, IOError)

    def test_add_cleanup_on_read_failure(self):
        """
        Tests the partial image file is cleaned up after a read
        failure.
        """
        ChunkedFile.CHUNKSIZE = 1024
        image_id = uuidutils.generate_uuid()
        file_size = 1024 * 5  # 5K
        file_contents = "*" * file_size
        path = os.path.join(self.test_dir, image_id)
        image_file = StringIO.StringIO(file_contents)

        def fake_Error(size):
            raise AttributeError()

        self.stubs.Set(image_file, 'read', fake_Error)

        self.assertRaises(AttributeError,
                          self.store.add,
                          image_id, image_file, 0)
        self.assertFalse(os.path.exists(path))

    def test_delete(self):
        """
        Test we can delete an existing image in the filesystem store
        """
        # First add an image
        image_id = uuidutils.generate_uuid()
        file_size = 1024 * 5  # 5K
        file_contents = "*" * file_size
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum, _ = self.store.add(image_id,
                                                     image_file,
                                                     file_size)

        # Now check that we can delete it
        uri = "file:///%s/%s" % (self.test_dir, image_id)
        loc = get_location_from_uri(uri)
        self.store.delete(loc)

        self.assertRaises(exception.NotFound, self.store.get, loc)

    def test_delete_non_existing(self):
        """
        Test that trying to delete a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///tmp/glance-tests/non-existing")
        self.assertRaises(exception.NotFound,
                          self.store.delete,
                          loc)
class TestStore(base.IsolatedUnitTest):
    def setUp(self):
        """Establish a clean test environment"""
        super(TestStore, self).setUp()
        self.orig_chunksize = ChunkedFile.CHUNKSIZE
        ChunkedFile.CHUNKSIZE = 10
        self.store = Store()

    def tearDown(self):
        """Clear the test environment"""
        super(TestStore, self).tearDown()
        ChunkedFile.CHUNKSIZE = self.orig_chunksize

    def test_configure_add_single_datadir(self):
        """
        Tests filesystem specified by filesystem_store_datadir
        are parsed correctly.
        """
        store = self.useFixture(fixtures.TempDir()).path
        CONF.set_override('filesystem_store_datadir', store)
        self.store.configure_add()
        self.assertEqual(self.store.datadir, store)

    def test_configure_add_with_single_and_multi_datadirs(self):
        """
        Tests BadStoreConfiguration exception is raised if both
        filesystem_store_datadir and filesystem_store_datadirs are specified.
        """
        store_map = [
            self.useFixture(fixtures.TempDir()).path,
            self.useFixture(fixtures.TempDir()).path
        ]
        CONF.set_override('filesystem_store_datadirs',
                          [store_map[0] + ":100", store_map[1] + ":200"])
        self.assertRaises(exception.BadStoreConfiguration,
                          self.store.configure_add)

    def test_configure_add_without_single_and_multi_datadirs(self):
        """
        Tests BadStoreConfiguration exception is raised if neither
        filesystem_store_datadir nor filesystem_store_datadirs are specified.
        """
        CONF.clear_override('filesystem_store_datadir')
        self.assertRaises(exception.BadStoreConfiguration,
                          self.store.configure_add)

    def test_configure_add_with_multi_datadirs(self):
        """
        Tests multiple filesystem specified by filesystem_store_datadirs
        are parsed correctly.
        """
        store_map = [
            self.useFixture(fixtures.TempDir()).path,
            self.useFixture(fixtures.TempDir()).path
        ]
        CONF.clear_override('filesystem_store_datadir')
        CONF.set_override('filesystem_store_datadirs',
                          [store_map[0] + ":100", store_map[1] + ":200"])
        self.store.configure_add()

        expected_priority_map = {100: [store_map[0]], 200: [store_map[1]]}
        expected_priority_list = [200, 100]
        self.assertEqual(self.store.priority_data_map, expected_priority_map)
        self.assertEqual(self.store.priority_list, expected_priority_list)

    def test_configure_add_invalid_priority(self):
        """
        Tests invalid priority specified by filesystem_store_datadirs
        param raises BadStoreConfiguration exception.
        """
        CONF.clear_override('filesystem_store_datadir')
        CONF.set_override('filesystem_store_datadirs', [
            self.useFixture(fixtures.TempDir()).path + ":100",
            self.useFixture(fixtures.TempDir()).path + ":invalid"
        ])
        self.assertRaises(exception.BadStoreConfiguration,
                          self.store.configure_add)

    def test_configure_add_same_dir_multiple_times(self):
        """
        Tests BadStoreConfiguration exception is raised if same directory
        is specified multiple times in filesystem_store_datadirs.
        """
        store_map = [
            self.useFixture(fixtures.TempDir()).path,
            self.useFixture(fixtures.TempDir()).path
        ]
        CONF.clear_override('filesystem_store_datadir')
        CONF.set_override('filesystem_store_datadirs', [
            store_map[0] + ":100", store_map[1] + ":200", store_map[0] + ":300"
        ])
        self.assertRaises(exception.BadStoreConfiguration,
                          self.store.configure_add)

    def test_configure_add_with_empty_datadir_path(self):
        """
        Tests BadStoreConfiguration exception is raised if empty directory
        path is specified in filesystem_store_datadirs.
        """
        CONF.clear_override('filesystem_store_datadir')
        CONF.set_override('filesystem_store_datadirs', [''])
        self.assertRaises(exception.BadStoreConfiguration,
                          self.store.configure_add)

    def test_configure_add_with_readonly_datadir_path(self):
        """
        Tests BadStoreConfiguration exception is raised if directory
        path specified in filesystem_store_datadirs is readonly.
        """
        readonly_dir = self.useFixture(fixtures.TempDir()).path
        os.chmod(readonly_dir, 0o444)
        CONF.clear_override('filesystem_store_datadir')
        CONF.set_override('filesystem_store_datadirs', [readonly_dir])
        self.assertRaises(exception.BadStoreConfiguration,
                          self.store.configure_add)

    def test_get(self):
        """Test a "normal" retrieval of an image in chunks"""
        # First add an image...
        image_id = str(uuid.uuid4())
        file_contents = "chunk00000remainder"
        image_file = six.StringIO(file_contents)

        location, size, checksum, _ = self.store.add(image_id, image_file,
                                                     len(file_contents))

        # Now read it back...
        uri = "file:///%s/%s" % (self.test_dir, image_id)
        loc = get_location_from_uri(uri)
        (image_file, image_size) = self.store.get(loc)

        expected_data = "chunk00000remainder"
        expected_num_chunks = 2
        data = ""
        num_chunks = 0

        for chunk in image_file:
            num_chunks += 1
            data += chunk
        self.assertEqual(expected_data, data)
        self.assertEqual(expected_num_chunks, num_chunks)

    def test_get_non_existing(self):
        """
        Test that trying to retrieve a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///%s/non-existing" % self.test_dir)
        self.assertRaises(exception.NotFound, self.store.get, loc)

    def test_add(self):
        """Test that we can add an image via the filesystem backend"""
        ChunkedFile.CHUNKSIZE = 1024
        expected_image_id = str(uuid.uuid4())
        expected_file_size = 5 * units.Ki  # 5K
        expected_file_contents = "*" * expected_file_size
        expected_checksum = hashlib.md5(expected_file_contents).hexdigest()
        expected_location = "file://%s/%s" % (self.test_dir, expected_image_id)
        image_file = six.StringIO(expected_file_contents)

        location, size, checksum, _ = self.store.add(expected_image_id,
                                                     image_file,
                                                     expected_file_size)

        self.assertEqual(expected_location, location)
        self.assertEqual(expected_file_size, size)
        self.assertEqual(expected_checksum, checksum)

        uri = "file:///%s/%s" % (self.test_dir, expected_image_id)
        loc = get_location_from_uri(uri)
        (new_image_file, new_image_size) = self.store.get(loc)
        new_image_contents = ""
        new_image_file_size = 0

        for chunk in new_image_file:
            new_image_file_size += len(chunk)
            new_image_contents += chunk

        self.assertEqual(expected_file_contents, new_image_contents)
        self.assertEqual(expected_file_size, new_image_file_size)

    def test_add_with_multiple_dirs(self):
        """Test adding multiple filesystem directories."""
        store_map = [
            self.useFixture(fixtures.TempDir()).path,
            self.useFixture(fixtures.TempDir()).path
        ]
        CONF.clear_override('filesystem_store_datadir')
        CONF.set_override('filesystem_store_datadirs',
                          [store_map[0] + ":100", store_map[1] + ":200"])
        self.store.configure_add()
        """Test that we can add an image via the filesystem backend"""
        ChunkedFile.CHUNKSIZE = 1024
        expected_image_id = str(uuid.uuid4())
        expected_file_size = 5 * units.Ki  # 5K
        expected_file_contents = "*" * expected_file_size
        expected_checksum = hashlib.md5(expected_file_contents).hexdigest()
        expected_location = "file://%s/%s" % (store_map[1], expected_image_id)
        image_file = six.StringIO(expected_file_contents)

        location, size, checksum, _ = self.store.add(expected_image_id,
                                                     image_file,
                                                     expected_file_size)

        self.assertEqual(expected_location, location)
        self.assertEqual(expected_file_size, size)
        self.assertEqual(expected_checksum, checksum)

        loc = get_location_from_uri(expected_location)
        (new_image_file, new_image_size) = self.store.get(loc)
        new_image_contents = ""
        new_image_file_size = 0

        for chunk in new_image_file:
            new_image_file_size += len(chunk)
            new_image_contents += chunk

        self.assertEqual(expected_file_contents, new_image_contents)
        self.assertEqual(expected_file_size, new_image_file_size)

    def test_add_with_multiple_dirs_storage_full(self):
        """
        Test StorageFull exception is raised if no filesystem directory
        is found that can store an image.
        """
        store_map = [
            self.useFixture(fixtures.TempDir()).path,
            self.useFixture(fixtures.TempDir()).path
        ]
        CONF.clear_override('filesystem_store_datadir')
        CONF.set_override('filesystem_store_datadirs',
                          [store_map[0] + ":100", store_map[1] + ":200"])
        self.store.configure_add()

        def fake_get_capacity_info(mount_point):
            return 0

        self.stubs.Set(self.store, '_get_capacity_info',
                       fake_get_capacity_info)
        ChunkedFile.CHUNKSIZE = 1024
        expected_image_id = str(uuid.uuid4())
        expected_file_size = 5 * units.Ki  # 5K
        expected_file_contents = "*" * expected_file_size
        image_file = six.StringIO(expected_file_contents)

        self.assertRaises(exception.StorageFull, self.store.add,
                          expected_image_id, image_file, expected_file_size)

    def test_add_check_metadata_success(self):
        expected_image_id = str(uuid.uuid4())
        in_metadata = {'akey': u'some value', 'list': [u'1', u'2', u'3']}
        jsonfilename = os.path.join(self.test_dir,
                                    "storage_metadata.%s" % expected_image_id)

        self.config(filesystem_store_metadata_file=jsonfilename)
        with open(jsonfilename, 'w') as fptr:
            json.dump(in_metadata, fptr)
        expected_file_size = 10
        expected_file_contents = "*" * expected_file_size
        image_file = six.StringIO(expected_file_contents)

        location, size, checksum, metadata = self.store.add(
            expected_image_id, image_file, expected_file_size)

        self.assertEqual(metadata, in_metadata)

    def test_add_check_metadata_bad_data(self):
        expected_image_id = str(uuid.uuid4())
        in_metadata = {'akey': 10}  # only unicode is allowed
        jsonfilename = os.path.join(self.test_dir,
                                    "storage_metadata.%s" % expected_image_id)

        self.config(filesystem_store_metadata_file=jsonfilename)
        with open(jsonfilename, 'w') as fptr:
            json.dump(in_metadata, fptr)
        expected_file_size = 10
        expected_file_contents = "*" * expected_file_size
        image_file = six.StringIO(expected_file_contents)

        location, size, checksum, metadata = self.store.add(
            expected_image_id, image_file, expected_file_size)

        self.assertEqual(metadata, {})

    def test_add_check_metadata_bad_nosuch_file(self):
        expected_image_id = str(uuid.uuid4())
        jsonfilename = os.path.join(self.test_dir,
                                    "storage_metadata.%s" % expected_image_id)

        self.config(filesystem_store_metadata_file=jsonfilename)
        expected_file_size = 10
        expected_file_contents = "*" * expected_file_size
        image_file = six.StringIO(expected_file_contents)

        location, size, checksum, metadata = self.store.add(
            expected_image_id, image_file, expected_file_size)

        self.assertEqual(metadata, {})

    def test_add_already_existing(self):
        """
        Tests that adding an image with an existing identifier
        raises an appropriate exception
        """
        ChunkedFile.CHUNKSIZE = 1024
        image_id = str(uuid.uuid4())
        file_size = 5 * units.Ki  # 5K
        file_contents = "*" * file_size
        image_file = six.StringIO(file_contents)

        location, size, checksum, _ = self.store.add(image_id, image_file,
                                                     file_size)
        image_file = six.StringIO("nevergonnamakeit")
        self.assertRaises(exception.Duplicate, self.store.add, image_id,
                          image_file, 0)

    def _do_test_add_write_failure(self, errno, exception):
        ChunkedFile.CHUNKSIZE = 1024
        image_id = str(uuid.uuid4())
        file_size = 5 * units.Ki  # 5K
        file_contents = "*" * file_size
        path = os.path.join(self.test_dir, image_id)
        image_file = six.StringIO(file_contents)

        m = mox.Mox()
        m.StubOutWithMock(__builtin__, 'open')
        e = IOError()
        e.errno = errno
        open(path, 'wb').AndRaise(e)
        m.ReplayAll()

        try:
            self.assertRaises(exception, self.store.add, image_id, image_file,
                              0)
            self.assertFalse(os.path.exists(path))
        finally:
            m.VerifyAll()
            m.UnsetStubs()

    def test_add_storage_full(self):
        """
        Tests that adding an image without enough space on disk
        raises an appropriate exception
        """
        self._do_test_add_write_failure(errno.ENOSPC, exception.StorageFull)

    def test_add_file_too_big(self):
        """
        Tests that adding an excessively large image file
        raises an appropriate exception
        """
        self._do_test_add_write_failure(errno.EFBIG, exception.StorageFull)

    def test_add_storage_write_denied(self):
        """
        Tests that adding an image with insufficient filestore permissions
        raises an appropriate exception
        """
        self._do_test_add_write_failure(errno.EACCES,
                                        exception.StorageWriteDenied)

    def test_add_other_failure(self):
        """
        Tests that a non-space-related IOError does not raise a
        StorageFull exception.
        """
        self._do_test_add_write_failure(errno.ENOTDIR, IOError)

    def test_add_cleanup_on_read_failure(self):
        """
        Tests the partial image file is cleaned up after a read
        failure.
        """
        ChunkedFile.CHUNKSIZE = 1024
        image_id = str(uuid.uuid4())
        file_size = 5 * units.Ki  # 5K
        file_contents = "*" * file_size
        path = os.path.join(self.test_dir, image_id)
        image_file = six.StringIO(file_contents)

        def fake_Error(size):
            raise AttributeError()

        self.stubs.Set(image_file, 'read', fake_Error)

        self.assertRaises(AttributeError, self.store.add, image_id, image_file,
                          0)
        self.assertFalse(os.path.exists(path))

    def test_delete(self):
        """
        Test we can delete an existing image in the filesystem store
        """
        # First add an image
        image_id = str(uuid.uuid4())
        file_size = 5 * units.Ki  # 5K
        file_contents = "*" * file_size
        image_file = six.StringIO(file_contents)

        location, size, checksum, _ = self.store.add(image_id, image_file,
                                                     file_size)

        # Now check that we can delete it
        uri = "file:///%s/%s" % (self.test_dir, image_id)
        loc = get_location_from_uri(uri)
        self.store.delete(loc)

        self.assertRaises(exception.NotFound, self.store.get, loc)

    def test_delete_non_existing(self):
        """
        Test that trying to delete a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///tmp/glance-tests/non-existing")
        self.assertRaises(exception.NotFound, self.store.delete, loc)
class TestStore(unittest.TestCase):
    def setUp(self):
        """Establish a clean test environment"""
        self.stubs = stubout.StubOutForTesting()
        stubs.stub_out_filesystem_backend()
        self.orig_chunksize = ChunkedFile.CHUNKSIZE
        ChunkedFile.CHUNKSIZE = 10
        self.store = Store(FILESYSTEM_OPTIONS)

    def tearDown(self):
        """Clear the test environment"""
        stubs.clean_out_fake_filesystem_backend()
        self.stubs.UnsetAll()
        ChunkedFile.CHUNKSIZE = self.orig_chunksize

    def test_get(self):
        """Test a "normal" retrieval of an image in chunks"""
        uri = "file:///tmp/glance-tests/2"
        loc = get_location_from_uri(uri)
        (image_file, image_size) = self.store.get(loc)

        expected_data = "chunk00000remainder"
        expected_num_chunks = 2
        data = ""
        num_chunks = 0

        for chunk in image_file:
            num_chunks += 1
            data += chunk
        self.assertEqual(expected_data, data)
        self.assertEqual(expected_num_chunks, num_chunks)

    def test_get_non_existing(self):
        """
        Test that trying to retrieve a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///tmp/glance-tests/non-existing")
        self.assertRaises(exception.NotFound, self.store.get, loc)

    def test_add(self):
        """Test that we can add an image via the filesystem backend"""
        ChunkedFile.CHUNKSIZE = 1024
        expected_image_id = utils.generate_uuid()
        expected_file_size = 1024 * 5  # 5K
        expected_file_contents = "*" * expected_file_size
        expected_checksum = hashlib.md5(expected_file_contents).hexdigest()
        expected_location = "file://%s/%s" % (stubs.FAKE_FILESYSTEM_ROOTDIR,
                                              expected_image_id)
        image_file = StringIO.StringIO(expected_file_contents)

        location, size, checksum = self.store.add(expected_image_id,
                                                  image_file,
                                                  expected_file_size)

        self.assertEquals(expected_location, location)
        self.assertEquals(expected_file_size, size)
        self.assertEquals(expected_checksum, checksum)

        uri = "file:///tmp/glance-tests/%s" % expected_image_id
        loc = get_location_from_uri(uri)
        (new_image_file, new_image_size) = self.store.get(loc)
        new_image_contents = ""
        new_image_file_size = 0

        for chunk in new_image_file:
            new_image_file_size += len(chunk)
            new_image_contents += chunk

        self.assertEquals(expected_file_contents, new_image_contents)
        self.assertEquals(expected_file_size, new_image_file_size)

    def test_add_already_existing(self):
        """
        Tests that adding an image with an existing identifier
        raises an appropriate exception
        """
        image_file = StringIO.StringIO("nevergonnamakeit")
        options = {
            'verbose': True,
            'debug': True,
            'filesystem_store_datadir': stubs.FAKE_FILESYSTEM_ROOTDIR
        }
        self.assertRaises(exception.Duplicate, self.store.add, '2', image_file,
                          0)

    def test_delete(self):
        """
        Test we can delete an existing image in the filesystem store
        """
        uri = "file:///tmp/glance-tests/2"
        loc = get_location_from_uri(uri)
        self.store.delete(loc)

        self.assertRaises(exception.NotFound, self.store.get, loc)

    def test_delete_non_existing(self):
        """
        Test that trying to delete a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///tmp/glance-tests/non-existing")
        self.assertRaises(exception.NotFound, self.store.delete, loc)
class TestStore(base.IsolatedUnitTest):
    def setUp(self):
        """Establish a clean test environment"""
        super(TestStore, self).setUp()
        self.orig_chunksize = ChunkedFile.CHUNKSIZE
        ChunkedFile.CHUNKSIZE = 10
        self.store = Store(self.conf)

    def tearDown(self):
        """Clear the test environment"""
        super(TestStore, self).tearDown()
        ChunkedFile.CHUNKSIZE = self.orig_chunksize

    def test_get(self):
        """Test a "normal" retrieval of an image in chunks"""
        # First add an image...
        image_id = utils.generate_uuid()
        file_contents = "chunk00000remainder"
        location = "file://%s/%s" % (self.test_dir, image_id)
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum = self.store.add(image_id, image_file,
                                                  len(file_contents))

        # Now read it back...
        uri = "file:///%s/%s" % (self.test_dir, image_id)
        loc = get_location_from_uri(uri)
        (image_file, image_size) = self.store.get(loc)

        expected_data = "chunk00000remainder"
        expected_num_chunks = 2
        data = ""
        num_chunks = 0

        for chunk in image_file:
            num_chunks += 1
            data += chunk
        self.assertEqual(expected_data, data)
        self.assertEqual(expected_num_chunks, num_chunks)

    def test_get_non_existing(self):
        """
        Test that trying to retrieve a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///%s/non-existing" % self.test_dir)
        self.assertRaises(exception.NotFound, self.store.get, loc)

    def test_add(self):
        """Test that we can add an image via the filesystem backend"""
        ChunkedFile.CHUNKSIZE = 1024
        expected_image_id = utils.generate_uuid()
        expected_file_size = 1024 * 5  # 5K
        expected_file_contents = "*" * expected_file_size
        expected_checksum = hashlib.md5(expected_file_contents).hexdigest()
        expected_location = "file://%s/%s" % (self.test_dir, expected_image_id)
        image_file = StringIO.StringIO(expected_file_contents)

        location, size, checksum = self.store.add(expected_image_id,
                                                  image_file,
                                                  expected_file_size)

        self.assertEquals(expected_location, location)
        self.assertEquals(expected_file_size, size)
        self.assertEquals(expected_checksum, checksum)

        uri = "file:///%s/%s" % (self.test_dir, expected_image_id)
        loc = get_location_from_uri(uri)
        (new_image_file, new_image_size) = self.store.get(loc)
        new_image_contents = ""
        new_image_file_size = 0

        for chunk in new_image_file:
            new_image_file_size += len(chunk)
            new_image_contents += chunk

        self.assertEquals(expected_file_contents, new_image_contents)
        self.assertEquals(expected_file_size, new_image_file_size)

    def test_add_already_existing(self):
        """
        Tests that adding an image with an existing identifier
        raises an appropriate exception
        """
        ChunkedFile.CHUNKSIZE = 1024
        image_id = utils.generate_uuid()
        file_size = 1024 * 5  # 5K
        file_contents = "*" * file_size
        location = "file://%s/%s" % (self.test_dir, image_id)
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum = self.store.add(image_id, image_file,
                                                  file_size)
        image_file = StringIO.StringIO("nevergonnamakeit")
        self.assertRaises(exception.Duplicate, self.store.add, image_id,
                          image_file, 0)

    def test_delete(self):
        """
        Test we can delete an existing image in the filesystem store
        """
        # First add an image
        image_id = utils.generate_uuid()
        file_size = 1024 * 5  # 5K
        file_contents = "*" * file_size
        location = "file://%s/%s" % (self.test_dir, image_id)
        image_file = StringIO.StringIO(file_contents)

        location, size, checksum = self.store.add(image_id, image_file,
                                                  file_size)

        # Now check that we can delete it
        uri = "file:///%s/%s" % (self.test_dir, image_id)
        loc = get_location_from_uri(uri)
        self.store.delete(loc)

        self.assertRaises(exception.NotFound, self.store.get, loc)

    def test_delete_non_existing(self):
        """
        Test that trying to delete a file that doesn't exist
        raises an error
        """
        loc = get_location_from_uri("file:///tmp/glance-tests/non-existing")
        self.assertRaises(exception.NotFound, self.store.delete, loc)