diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index 9fbd427b5..e0809b7ce 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -741,10 +741,16 @@ mounts: description: | A list of dictionary data to specify how volumes are mounted into the container. The container will mount the volumes at create time. + Each item can have an ``type`` attribute that specifies the volume + type. The supported volume types are ``volume`` or ``bind``. If this + attribute is not specified, the default is ``volume``. To provision a container with pre-existing Cinder volumes bind-mounted, specify the UUID or name of the volume in the ``source`` attribute. Alternatively, Cinder volumes can be dynamically created. In this case, the size of the volume needs to be specified in the ``size`` attribute. + Another option is to mount a user-provided file into the container. + In this case, the ``type`` attribute should be 'bind' and + the content of the file is contained in the ``source`` attribute. The volumes will be mounted into the file system of the container and the path to mount the volume needs to be specified in the ``destination`` attribute. diff --git a/setup.cfg b/setup.cfg index 53a6cb96e..e2200bf14 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,6 +75,7 @@ zun.network.driver = zun.volume.driver = cinder = zun.volume.driver:Cinder + local = zun.volume.driver:Local [extras] osprofiler = diff --git a/zun/api/controllers/v1/containers.py b/zun/api/controllers/v1/containers.py index c2dd7810c..b0b54c86f 100644 --- a/zun/api/controllers/v1/containers.py +++ b/zun/api/controllers/v1/containers.py @@ -506,26 +506,38 @@ class ContainersController(base.Controller): return phynet_name def _build_requested_volumes(self, context, mounts): - # NOTE(hongbin): We assume cinder is the only volume provider here. - # The logic needs to be re-visited if a second volume provider - # (i.e. Manila) is introduced. cinder_api = cinder.CinderAPI(context) requested_volumes = [] for mount in mounts: - if mount.get('source'): - volume = cinder_api.search_volume(mount['source']) - auto_remove = False - else: - volume = cinder_api.create_volume(mount['size']) - auto_remove = True - cinder_api.ensure_volume_usable(volume) - volmapp = objects.VolumeMapping( - context, - volume_id=volume.id, volume_provider='cinder', - container_path=mount['destination'], - user_id=context.user_id, - project_id=context.project_id, - auto_remove=auto_remove) + volume_dict = { + 'volume_id': None, + 'container_path': None, + 'auto_remove': False, + 'contents': None, + 'user_id': context.user_id, + 'project_id': context.project_id, + } + volume_type = mount.get('type', 'volume') + if volume_type == 'volume': + if mount.get('source'): + volume = cinder_api.search_volume(mount['source']) + cinder_api.ensure_volume_usable(volume) + volume_dict['volume_id'] = volume.id + volume_dict['container_path'] = mount['destination'] + volume_dict['volume_provider'] = 'cinder' + elif mount.get('size'): + volume = cinder_api.create_volume(mount['size']) + cinder_api.ensure_volume_usable(volume) + volume_dict['volume_id'] = volume.id + volume_dict['container_path'] = mount['destination'] + volume_dict['volume_provider'] = 'cinder' + volume_dict['auto_remove'] = True + elif volume_type == 'bind': + volume_dict['contents'] = mount.pop('source', '') + volume_dict['container_path'] = mount['destination'] + volume_dict['volume_provider'] = 'local' + + volmapp = objects.VolumeMapping(context, **volume_dict) requested_volumes.append(volmapp) return requested_volumes diff --git a/zun/api/controllers/v1/schemas/parameter_types.py b/zun/api/controllers/v1/schemas/parameter_types.py index 8d35c658e..e354f051b 100644 --- a/zun/api/controllers/v1/schemas/parameter_types.py +++ b/zun/api/controllers/v1/schemas/parameter_types.py @@ -201,6 +201,9 @@ mounts = { 'items': { 'type': 'object', 'properties': { + 'type': { + 'type': ['string'], + }, 'source': { 'type': ['string'], }, diff --git a/zun/api/controllers/versions.py b/zun/api/controllers/versions.py index e2b691afc..dfc4ec9fa 100644 --- a/zun/api/controllers/versions.py +++ b/zun/api/controllers/versions.py @@ -55,10 +55,11 @@ REST_API_VERSION_HISTORY = """REST API Version History: * 1.20 - Convert type of 'command' from string to list * 1.21 - Add support privileged * 1.22 - Add healthcheck to container create + * 1.23 - Add attribute 'type' to parameter 'mounts' """ BASE_VER = '1.1' -CURRENT_MAX_VER = '1.22' +CURRENT_MAX_VER = '1.23' class Version(object): diff --git a/zun/api/rest_api_version_history.rst b/zun/api/rest_api_version_history.rst index 69216bdda..5c6d26aeb 100644 --- a/zun/api/rest_api_version_history.rst +++ b/zun/api/rest_api_version_history.rst @@ -183,3 +183,8 @@ user documentation. Add healthcheck to container create +1.23 +---- + + Add support for file injection when creating a container. + The content of the file is sent to Zun server via parameter 'mounts'. diff --git a/zun/conf/volume.py b/zun/conf/volume.py index 2cfd022f3..e099e8451 100644 --- a/zun/conf/volume.py +++ b/zun/conf/volume.py @@ -19,7 +19,19 @@ volume_group = cfg.OptGroup(name='volume', volume_opts = [ cfg.StrOpt('driver', default='cinder', + deprecated_for_removal=True, help='Defines which driver to use for container volume.'), + cfg.ListOpt('driver_list', + default=['cinder', 'local'], + help="""Defines the list of volume driver to use. +Possible values: +* ``cinder`` +* ``local`` +Services which consume this: +* ``zun-compute`` +Interdependencies to other options: +* None +"""), cfg.StrOpt('volume_dir', default='$state_path/mnt', help='At which the docker volume will create.'), diff --git a/zun/container/docker/driver.py b/zun/container/docker/driver.py index 9502efd8b..4a5e3e885 100644 --- a/zun/container/docker/driver.py +++ b/zun/container/docker/driver.py @@ -112,11 +112,14 @@ class DockerDriver(driver.ContainerDriver): super(DockerDriver, self).__init__() self._host = host.Host() self._get_host_storage_info() - self.volume_driver = vol_driver.driver() self.image_drivers = {} for driver_name in CONF.image_driver_list: driver = img_driver.load_image_driver(driver_name) self.image_drivers[driver_name] = driver + self.volume_drivers = {} + for driver_name in CONF.volume.driver_list: + driver = vol_driver.driver(driver_name) + self.volume_drivers[driver_name] = driver def _get_host_storage_info(self): storage_info = self._host.get_storage_info() @@ -380,8 +383,8 @@ class DockerDriver(driver.ContainerDriver): def _get_binds(self, context, requested_volumes): binds = {} for volume in requested_volumes: - source, destination = self.volume_driver.bind_mount(context, - volume) + volume_driver = self._get_volume_driver(volume) + source, destination = volume_driver.bind_mount(context, volume) binds[source] = {'bind': destination} return binds @@ -990,17 +993,30 @@ class DockerDriver(driver.ContainerDriver): docker.start(sandbox['Id']) return sandbox['Id'] + def _get_volume_driver(self, volume_mapping): + driver_name = volume_mapping.volume_provider + driver = self.volume_drivers.get(driver_name) + if not driver: + msg = _("The volume provider '%s' is not supported") % driver_name + raise exception.ZunException(msg) + + return driver + def attach_volume(self, context, volume_mapping): - self.volume_driver.attach(context, volume_mapping) + volume_driver = self._get_volume_driver(volume_mapping) + volume_driver.attach(context, volume_mapping) def detach_volume(self, context, volume_mapping): - self.volume_driver.detach(context, volume_mapping) + volume_driver = self._get_volume_driver(volume_mapping) + volume_driver.detach(context, volume_mapping) def delete_volume(self, context, volume_mapping): - self.volume_driver.delete(context, volume_mapping) + volume_driver = self._get_volume_driver(volume_mapping) + volume_driver.delete(context, volume_mapping) def get_volume_status(self, context, volume_mapping): - return self.volume_driver.get_volume_status(context, volume_mapping) + volume_driver = self._get_volume_driver(volume_mapping) + return volume_driver.get_volume_status(context, volume_mapping) def check_multiattach(self, context, volume_mapping): return self.volume_driver.check_multiattach(context, volume_mapping) diff --git a/zun/db/sqlalchemy/alembic/versions/a9c9fb54274a_add_contents_to_volume_mapping_table.py b/zun/db/sqlalchemy/alembic/versions/a9c9fb54274a_add_contents_to_volume_mapping_table.py new file mode 100644 index 000000000..ee8adde69 --- /dev/null +++ b/zun/db/sqlalchemy/alembic/versions/a9c9fb54274a_add_contents_to_volume_mapping_table.py @@ -0,0 +1,40 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""add_contents_to_volume_mapping_table + +Revision ID: a9c9fb54274a +Revises: bc56b9932dd9 +Create Date: 2018-08-10 02:49:27.524151 + +""" + +# revision identifiers, used by Alembic. +revision = 'a9c9fb54274a' +down_revision = 'bc56b9932dd9' +branch_labels = None +depends_on = None + +from alembic import op +import sqlalchemy as sa + + +def MediumText(): + return sa.Text().with_variant(sa.dialects.mysql.MEDIUMTEXT(), 'mysql') + + +def upgrade(): + op.add_column('volume_mapping', + sa.Column('contents', MediumText(), nullable=True)) + op.alter_column('volume_mapping', 'volume_id', + existing_type=sa.String(36), + nullable=True) diff --git a/zun/db/sqlalchemy/models.py b/zun/db/sqlalchemy/models.py index ac16bc090..f7d6ca99b 100644 --- a/zun/db/sqlalchemy/models.py +++ b/zun/db/sqlalchemy/models.py @@ -186,11 +186,12 @@ class VolumeMapping(Base): id = Column(Integer, primary_key=True, nullable=False) project_id = Column(String(255), nullable=True) user_id = Column(String(255), nullable=True) - volume_id = Column(String(36), nullable=False) + volume_id = Column(String(36), nullable=True) volume_provider = Column(String(36), nullable=False) container_path = Column(String(255), nullable=True) container_uuid = Column(String(36), ForeignKey('container.uuid')) connection_info = Column(MediumText()) + contents = Column(MediumText()) container = orm.relationship( Container, backref=orm.backref('volume'), diff --git a/zun/objects/volume_mapping.py b/zun/objects/volume_mapping.py index 677c960d8..f8ae77cf5 100644 --- a/zun/objects/volume_mapping.py +++ b/zun/objects/volume_mapping.py @@ -36,14 +36,15 @@ class VolumeMapping(base.ZunPersistentObject, base.ZunObject): # Version 1.0: Initial version # Version 1.1: Add field "auto_remove" # Version 1.2: Add field "host" - VERSION = '1.2' + # Version 1.3: Add field "contents" + VERSION = '1.3' fields = { 'id': fields.IntegerField(), 'uuid': fields.UUIDField(nullable=False), 'project_id': fields.StringField(nullable=True), 'user_id': fields.StringField(nullable=True), - 'volume_id': fields.UUIDField(nullable=False), + 'volume_id': fields.UUIDField(nullable=True), 'volume_provider': fields.StringField(nullable=False), 'container_path': fields.StringField(nullable=True), 'container_uuid': fields.UUIDField(nullable=True), @@ -51,6 +52,7 @@ class VolumeMapping(base.ZunPersistentObject, base.ZunObject): 'connection_info': fields.SensitiveStringField(nullable=True), 'auto_remove': fields.BooleanField(nullable=True), 'host': fields.StringField(nullable=True), + 'contents': fields.SensitiveStringField(nullable=True), } @staticmethod diff --git a/zun/tests/unit/api/base.py b/zun/tests/unit/api/base.py index 5c698ec71..0cb9eb0b2 100644 --- a/zun/tests/unit/api/base.py +++ b/zun/tests/unit/api/base.py @@ -26,7 +26,7 @@ from zun.tests.unit.db import base PATH_PREFIX = '/v1' -CURRENT_VERSION = "container 1.22" +CURRENT_VERSION = "container 1.23" class FunctionalTest(base.DbTestCase): diff --git a/zun/tests/unit/api/controllers/test_root.py b/zun/tests/unit/api/controllers/test_root.py index b7c3fecca..a31eb1a44 100644 --- a/zun/tests/unit/api/controllers/test_root.py +++ b/zun/tests/unit/api/controllers/test_root.py @@ -28,7 +28,7 @@ class TestRootController(api_base.FunctionalTest): 'default_version': {'id': 'v1', 'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}], - 'max_version': '1.22', + 'max_version': '1.23', 'min_version': '1.1', 'status': 'CURRENT'}, 'description': 'Zun is an OpenStack project which ' @@ -37,7 +37,7 @@ class TestRootController(api_base.FunctionalTest): 'versions': [{'id': 'v1', 'links': [{'href': 'http://localhost/v1/', 'rel': 'self'}], - 'max_version': '1.22', + 'max_version': '1.23', 'min_version': '1.1', 'status': 'CURRENT'}]} diff --git a/zun/tests/unit/api/controllers/v1/test_containers.py b/zun/tests/unit/api/controllers/v1/test_containers.py index 454692592..717416d72 100644 --- a/zun/tests/unit/api/controllers/v1/test_containers.py +++ b/zun/tests/unit/api/controllers/v1/test_containers.py @@ -684,11 +684,60 @@ class TestContainerController(api_base.FunctionalTest): self.assertEqual(1, len(requested_networks)) self.assertEqual(fake_network['id'], requested_networks[0]['network']) mock_create_volume.assert_called_once() + mock_ensure_volume_usable.assert_called_once() requested_volumes = \ mock_container_create.call_args[1]['requested_volumes'] self.assertEqual(1, len(requested_volumes)) self.assertEqual(fake_volume_id, requested_volumes[0].volume_id) + @patch('zun.network.neutron.NeutronAPI.get_available_network') + @patch('zun.compute.api.API.container_show') + @patch('zun.compute.api.API.container_create') + @patch('zun.common.context.RequestContext.can') + @patch('zun.volume.cinder_api.CinderAPI.create_volume') + @patch('zun.volume.cinder_api.CinderAPI.ensure_volume_usable') + @patch('zun.compute.api.API.image_search') + def test_create_container_with_injected_file( + self, mock_search, mock_ensure_volume_usable, mock_create_volume, + mock_authorize, mock_container_create, mock_container_show, + mock_neutron_get_network): + fake_network = {'id': 'foo'} + mock_neutron_get_network.return_value = fake_network + # Create a container with a command + params = ('{"name": "MyDocker", "image": "ubuntu",' + '"command": ["env"], "memory": "512",' + '"mounts": [{"destination": "d", "source": "hello",' + ' "type": "bind"}]}') + response = self.post('/v1/containers/', + params=params, + content_type='application/json') + self.assertEqual(202, response.status_int) + # get all containers + container = objects.Container.list(self.context)[0] + container.status = 'Creating' + mock_container_show.return_value = container + response = self.app.get('/v1/containers/') + self.assertEqual(200, response.status_int) + self.assertEqual(2, len(response.json)) + c = response.json['containers'][0] + self.assertIsNotNone(c.get('uuid')) + self.assertEqual('MyDocker', c.get('name')) + self.assertEqual(["env"], c.get('command')) + self.assertEqual('Creating', c.get('status')) + self.assertEqual('512', c.get('memory')) + self.assertIn('host', c) + requested_networks = \ + mock_container_create.call_args[1]['requested_networks'] + self.assertEqual(1, len(requested_networks)) + self.assertEqual(fake_network['id'], requested_networks[0]['network']) + mock_create_volume.assert_not_called() + mock_ensure_volume_usable.assert_not_called() + requested_volumes = \ + mock_container_create.call_args[1]['requested_volumes'] + self.assertEqual(1, len(requested_volumes)) + self.assertIsNone(requested_volumes[0].volume_id) + self.assertEqual('local', requested_volumes[0].volume_provider) + @patch('zun.network.neutron.NeutronAPI.get_available_network') @patch('zun.compute.api.API.container_show') @patch('zun.compute.api.API.container_create') diff --git a/zun/tests/unit/db/utils.py b/zun/tests/unit/db/utils.py index b78ce11d6..3faee6ad2 100644 --- a/zun/tests/unit/db/utils.py +++ b/zun/tests/unit/db/utils.py @@ -152,6 +152,7 @@ def get_test_volume_mapping(**kwargs): 'connection_info': kwargs.get('connection_info', 'fake_info'), 'auto_remove': kwargs.get('auto_remove', False), 'host': kwargs.get('host', 'fake_host'), + 'contents': kwargs.get('contents', 'fake-contents'), } diff --git a/zun/tests/unit/objects/test_objects.py b/zun/tests/unit/objects/test_objects.py index 652ee394f..0ffb91032 100644 --- a/zun/tests/unit/objects/test_objects.py +++ b/zun/tests/unit/objects/test_objects.py @@ -345,7 +345,7 @@ class TestObject(test_base.TestCase, _TestObject): # https://docs.openstack.org/zun/latest/ object_data = { 'Container': '1.36-ad2bacdaa51afd0047e96003f93ff181', - 'VolumeMapping': '1.2-2230102beda09cf5caabd130c600dc92', + 'VolumeMapping': '1.3-14e3f9fc64e7afd751727c6ad3f32a94', 'Image': '1.1-330e6205c80b99b59717e1cfc6a79935', 'MyObj': '1.0-34c4b1aadefd177b13f9a2f894cc23cd', 'NUMANode': '1.0-cba878b70b2f8b52f1e031b41ac13b4e', diff --git a/zun/tests/unit/volume/test_driver.py b/zun/tests/unit/volume/test_driver.py index 5faf1a8ba..88fdd34f7 100644 --- a/zun/tests/unit/volume/test_driver.py +++ b/zun/tests/unit/volume/test_driver.py @@ -13,6 +13,7 @@ import mock from oslo_serialization import jsonutils +from oslo_utils import uuidutils from zun.common import exception import zun.conf @@ -23,10 +24,11 @@ from zun.volume import driver CONF = zun.conf.CONF -class VolumeDriverTestCase(base.TestCase): +class CinderVolumeDriverTestCase(base.TestCase): def setUp(self): - super(VolumeDriverTestCase, self).setUp() + super(CinderVolumeDriverTestCase, self).setUp() + self.fake_uuid = uuidutils.generate_uuid() self.fake_volume_id = 'fake-volume-id' self.fake_devpath = '/fake-path' self.fake_mountpoint = '/fake-mountpoint' @@ -35,6 +37,7 @@ class VolumeDriverTestCase(base.TestCase): 'data': {'device_path': self.fake_devpath}, } self.volume = mock.MagicMock() + self.volume.uuid = self.fake_uuid self.volume.volume_provider = 'cinder' self.volume.volume_id = self.fake_volume_id self.volume.container_path = self.fake_container_path @@ -55,7 +58,7 @@ class VolumeDriverTestCase(base.TestCase): volume_driver.attach(self.context, self.volume) mock_cinder_workflow.attach_volume.assert_called_once_with(self.volume) - mock_get_mountpoint.assert_called_once_with(self.fake_volume_id) + mock_get_mountpoint.assert_called_once_with(self.fake_uuid) mock_do_mount.assert_called_once_with( self.fake_devpath, self.fake_mountpoint, CONF.volume.fstype) mock_cinder_workflow.detach_volume.assert_not_called() @@ -112,7 +115,7 @@ class VolumeDriverTestCase(base.TestCase): volume_driver.attach, self.context, self.volume) mock_cinder_workflow.attach_volume.assert_called_once_with(self.volume) - mock_get_mountpoint.assert_called_once_with(self.fake_volume_id) + mock_get_mountpoint.assert_called_once_with(self.fake_uuid) mock_do_mount.assert_called_once_with( self.fake_devpath, self.fake_mountpoint, CONF.volume.fstype) mock_cinder_workflow.detach_volume.assert_called_once_with(self.volume) @@ -143,7 +146,7 @@ class VolumeDriverTestCase(base.TestCase): volume_driver.attach, self.context, self.volume) mock_cinder_workflow.attach_volume.assert_called_once_with(self.volume) - mock_get_mountpoint.assert_called_once_with(self.fake_volume_id) + mock_get_mountpoint.assert_called_once_with(self.fake_uuid) mock_do_mount.assert_called_once_with( self.fake_devpath, self.fake_mountpoint, CONF.volume.fstype) mock_cinder_workflow.detach_volume.assert_called_once_with(self.volume) @@ -162,7 +165,7 @@ class VolumeDriverTestCase(base.TestCase): volume_driver.detach(self.context, self.volume) mock_cinder_workflow.detach_volume.assert_called_once_with(self.volume) - mock_get_mountpoint.assert_called_once_with(self.fake_volume_id) + mock_get_mountpoint.assert_called_once_with(self.fake_uuid) mock_do_unmount.assert_called_once_with(self.fake_mountpoint) mock_rmtree.assert_called_once_with(self.fake_mountpoint) @@ -179,7 +182,7 @@ class VolumeDriverTestCase(base.TestCase): self.assertEqual(self.fake_mountpoint, source) self.assertEqual(self.fake_container_path, destination) - mock_get_mountpoint.assert_called_once_with(self.fake_volume_id) + mock_get_mountpoint.assert_called_once_with(self.fake_uuid) @mock.patch('shutil.rmtree') @mock.patch('zun.common.mount.get_mountpoint') @@ -197,3 +200,55 @@ class VolumeDriverTestCase(base.TestCase): mock_cinder_workflow.delete_volume.assert_called_once_with(self.volume) mock_rmtree.assert_called_once_with(self.fake_mountpoint) + + +class LocalVolumeDriverTestCase(base.TestCase): + + def setUp(self): + super(LocalVolumeDriverTestCase, self).setUp() + self.fake_uuid = uuidutils.generate_uuid() + self.fake_mountpoint = '/fake-mountpoint' + self.fake_container_path = '/fake-container-path' + self.fake_contents = 'fake-contents' + self.volume = mock.MagicMock() + self.volume.uuid = self.fake_uuid + self.volume.volume_provider = 'local' + self.volume.container_path = self.fake_container_path + self.volume.contents = self.fake_contents + + @mock.patch('oslo_utils.fileutils.ensure_tree') + @mock.patch('zun.common.mount.get_mountpoint') + def test_attach(self, mock_get_mountpoint, mock_ensure_tree): + mock_get_mountpoint.return_value = self.fake_mountpoint + volume_driver = driver.Local() + + with mock.patch('zun.volume.driver.open', mock.mock_open() + ) as mock_open: + volume_driver.attach(self.context, self.volume) + + expected_file_path = self.fake_mountpoint + '/' + self.fake_uuid + mock_open.assert_called_once_with(expected_file_path, 'wb') + mock_open().write.assert_called_once_with(self.fake_contents) + mock_get_mountpoint.assert_called_once_with(self.fake_uuid) + + @mock.patch('shutil.rmtree') + @mock.patch('zun.common.mount.get_mountpoint') + def test_detach(self, mock_get_mountpoint, mock_rmtree): + mock_get_mountpoint.return_value = self.fake_mountpoint + volume_driver = driver.Local() + volume_driver.detach(self.context, self.volume) + + mock_get_mountpoint.assert_called_once_with(self.fake_uuid) + mock_rmtree.assert_called_once_with(self.fake_mountpoint) + + @mock.patch('zun.common.mount.get_mountpoint') + def test_bind_mount(self, mock_get_mountpoint): + mock_get_mountpoint.return_value = self.fake_mountpoint + volume_driver = driver.Local() + source, destination = volume_driver.bind_mount( + self.context, self.volume) + + expected_file_path = self.fake_mountpoint + '/' + self.fake_uuid + self.assertEqual(expected_file_path, source) + self.assertEqual(self.fake_container_path, destination) + mock_get_mountpoint.assert_called_once_with(self.fake_uuid) diff --git a/zun/volume/driver.py b/zun/volume/driver.py index 4be343659..3b5e86cd6 100644 --- a/zun/volume/driver.py +++ b/zun/volume/driver.py @@ -33,12 +33,11 @@ LOG = logging.getLogger(__name__) CONF = zun.conf.CONF -def driver(*args, **kwargs): - name = CONF.volume.driver - LOG.info("Loading volume driver '%s'", name) +def driver(driver_name, *args, **kwargs): + LOG.info("Loading volume driver '%s'", driver_name) volume_driver = stevedore_driver.DriverManager( "zun.volume.driver", - name, + driver_name, invoke_on_load=True, invoke_args=args, invoke_kwds=kwargs).driver @@ -84,6 +83,41 @@ class VolumeDriver(object): raise NotImplementedError() +class Local(VolumeDriver): + + supported_providers = ['local'] + + @validate_volume_provider(supported_providers) + def attach(self, context, volume): + mountpoint = mount.get_mountpoint(volume.uuid) + fileutils.ensure_tree(mountpoint) + filename = '/'.join([mountpoint, volume.uuid]) + with open(filename, 'wb') as fd: + fd.write(volume.contents) + + def _remove_local_file(self, volume): + mountpoint = mount.get_mountpoint(volume.uuid) + shutil.rmtree(mountpoint) + + @validate_volume_provider(supported_providers) + def detach(self, context, volume): + self._remove_local_file(volume) + + @validate_volume_provider(supported_providers) + def delete(self, context, volume): + self._remove_local_file(volume) + + @validate_volume_provider(supported_providers) + def bind_mount(self, context, volume): + mountpoint = mount.get_mountpoint(volume.uuid) + filename = '/'.join([mountpoint, volume.uuid]) + return filename, volume.container_path + + @validate_volume_provider(supported_providers) + def get_volume_status(self, context, volume): + return 'available' + + class Cinder(VolumeDriver): supported_providers = [ @@ -105,7 +139,7 @@ class Cinder(VolumeDriver): LOG.exception("Failed to detach volume") def _mount_device(self, volume, devpath): - mountpoint = mount.get_mountpoint(volume.volume_id) + mountpoint = mount.get_mountpoint(volume.uuid) fileutils.ensure_tree(mountpoint) mount.do_mount(devpath, mountpoint, CONF.volume.fstype) @@ -123,13 +157,13 @@ class Cinder(VolumeDriver): def _unmount_device(self, volume): if hasattr(volume, 'connection_info'): - mountpoint = mount.get_mountpoint(volume.volume_id) + mountpoint = mount.get_mountpoint(volume.uuid) mount.do_unmount(mountpoint) shutil.rmtree(mountpoint) @validate_volume_provider(supported_providers) def bind_mount(self, context, volume): - mountpoint = mount.get_mountpoint(volume.volume_id) + mountpoint = mount.get_mountpoint(volume.uuid) return mountpoint, volume.container_path @validate_volume_provider(supported_providers)