Merge "Support file injection on container"
This commit is contained in:
commit
1f7526faa6
|
@ -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.
|
||||
|
|
|
@ -75,6 +75,7 @@ zun.network.driver =
|
|||
|
||||
zun.volume.driver =
|
||||
cinder = zun.volume.driver:Cinder
|
||||
local = zun.volume.driver:Local
|
||||
|
||||
[extras]
|
||||
osprofiler =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -201,6 +201,9 @@ mounts = {
|
|||
'items': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'type': {
|
||||
'type': ['string'],
|
||||
},
|
||||
'source': {
|
||||
'type': ['string'],
|
||||
},
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'.
|
||||
|
|
|
@ -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.'),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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'),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'}]}
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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'),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue