Merge "Support Cinder volume multi-attach"
This commit is contained in:
@@ -179,6 +179,11 @@ class Manager(periodic_task.PeriodicTasks):
|
||||
status = self.driver.get_volume_status(context, volume)
|
||||
if status == 'available':
|
||||
volume = next(volumes)
|
||||
if status == 'in-use':
|
||||
multiattach = self.driver.check_multiattach(context,
|
||||
volume)
|
||||
if multiattach:
|
||||
volume = next(volumes)
|
||||
elif status == 'error':
|
||||
break
|
||||
time.sleep(poll_interval)
|
||||
@@ -354,6 +359,7 @@ class Manager(periodic_task.PeriodicTasks):
|
||||
try:
|
||||
for volume in volumes:
|
||||
volume.container_uuid = container.uuid
|
||||
volume.host = self.host
|
||||
self._attach_volume(context, volume)
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
@@ -386,9 +392,17 @@ class Manager(periodic_task.PeriodicTasks):
|
||||
volumes = objects.VolumeMapping.list_by_container(context,
|
||||
container.uuid)
|
||||
for volume in volumes:
|
||||
self._detach_volume(context, volume, reraise=reraise)
|
||||
if volume.auto_remove:
|
||||
self.driver.delete_volume(context, volume)
|
||||
db_volumes = objects.VolumeMapping.list_by_volume(context,
|
||||
volume.volume_id
|
||||
)
|
||||
volume_hosts = [db_volume.host for db_volume in db_volumes]
|
||||
|
||||
if volume_hosts.count(self.host) == 1:
|
||||
self._detach_volume(context, volume, reraise=reraise)
|
||||
if volume.auto_remove and len(volume_hosts) == 1:
|
||||
self.driver.delete_volume(context, volume)
|
||||
else:
|
||||
volume.destroy()
|
||||
|
||||
def _detach_volume(self, context, volume, reraise=True):
|
||||
context = context.elevated()
|
||||
|
||||
@@ -1000,6 +1000,9 @@ class DockerDriver(driver.ContainerDriver):
|
||||
def get_volume_status(self, context, volume_mapping):
|
||||
return self.volume_driver.get_volume_status(context, volume_mapping)
|
||||
|
||||
def check_multiattach(self, context, volume_mapping):
|
||||
return self.volume_driver.check_multiattach(context, volume_mapping)
|
||||
|
||||
def _get_or_create_docker_network(self, context, network_api,
|
||||
neutron_net_id):
|
||||
docker_net_name = self._get_docker_network_name(context,
|
||||
|
||||
@@ -210,6 +210,9 @@ class ContainerDriver(object):
|
||||
def get_volume_status(self, context, volume_mapping):
|
||||
raise NotImplementedError()
|
||||
|
||||
def check_multiattach(self, context, volume_mapping):
|
||||
raise NotImplementedError()
|
||||
|
||||
def add_security_group(self, context, container, security_group, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# 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 host to volume mapping
|
||||
|
||||
Revision ID: f746cd28bcac
|
||||
Revises: 2fb377a5a519
|
||||
Create Date: 2018-08-03 10:53:45.920787
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f746cd28bcac'
|
||||
down_revision = '2fb377a5a519'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('volume_mapping',
|
||||
sa.Column('host', sa.String(length=255), nullable=True))
|
||||
@@ -197,6 +197,7 @@ class VolumeMapping(Base):
|
||||
foreign_keys=container_uuid,
|
||||
primaryjoin='and_(VolumeMapping.container_uuid==Container.uuid)')
|
||||
auto_remove = Column(Boolean, default=False)
|
||||
host = Column(String(255), nullable=True)
|
||||
|
||||
|
||||
class ExecInstance(Base):
|
||||
|
||||
@@ -35,7 +35,8 @@ def _expected_cols(expected_attrs):
|
||||
class VolumeMapping(base.ZunPersistentObject, base.ZunObject):
|
||||
# Version 1.0: Initial version
|
||||
# Version 1.1: Add field "auto_remove"
|
||||
VERSION = '1.1'
|
||||
# Version 1.2: Add field "host"
|
||||
VERSION = '1.2'
|
||||
|
||||
fields = {
|
||||
'id': fields.IntegerField(),
|
||||
@@ -49,6 +50,7 @@ class VolumeMapping(base.ZunPersistentObject, base.ZunObject):
|
||||
'container': fields.ObjectField('Container', nullable=True),
|
||||
'connection_info': fields.SensitiveStringField(nullable=True),
|
||||
'auto_remove': fields.BooleanField(nullable=True),
|
||||
'host': fields.StringField(nullable=True),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -105,6 +107,12 @@ class VolumeMapping(base.ZunPersistentObject, base.ZunObject):
|
||||
db_volumes = dbapi.list_volume_mappings(context, filters=filters)
|
||||
return VolumeMapping._from_db_object_list(db_volumes, cls, context)
|
||||
|
||||
@base.remotable_classmethod
|
||||
def list_by_volume(cls, context, volume_id):
|
||||
filters = {'volume_id': volume_id}
|
||||
db_volumes = dbapi.list_volume_mappings(context, filters=filters)
|
||||
return VolumeMapping._from_db_object_list(db_volumes, cls, context)
|
||||
|
||||
@base.remotable
|
||||
def create(self, context):
|
||||
"""Create a VolumeMapping record in the DB.
|
||||
|
||||
@@ -69,6 +69,10 @@ class FakeVolumeMapping(object):
|
||||
def list_by_container(cls, context, container_id):
|
||||
return cls.volumes
|
||||
|
||||
@classmethod
|
||||
def list_by_volume(cls, context, volume_id):
|
||||
return cls.volumes
|
||||
|
||||
|
||||
class TestManager(base.TestCase):
|
||||
|
||||
@@ -358,6 +362,8 @@ class TestManager(base.TestCase):
|
||||
@mock.patch.object(ContainerActionEvent, 'event_finish')
|
||||
@mock.patch('zun.common.utils.spawn_n')
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch.object(VolumeMapping, 'list_by_volume',
|
||||
side_effect=FakeVolumeMapping.list_by_volume)
|
||||
@mock.patch.object(VolumeMapping, 'list_by_container',
|
||||
side_effect=FakeVolumeMapping.list_by_container)
|
||||
@mock.patch.object(fake_driver, 'pull_image')
|
||||
@@ -369,7 +375,8 @@ class TestManager(base.TestCase):
|
||||
def test_container_run_driver_attach_failed(
|
||||
self, mock_start, mock_create,
|
||||
mock_get_volume_status, mock_attach_volume,
|
||||
mock_detach_volume, mock_pull, mock_list_by_container, mock_save,
|
||||
mock_detach_volume, mock_pull, mock_list_by_container,
|
||||
mock_list_by_volume, mock_save,
|
||||
mock_spawn_n, mock_event_finish, mock_event_start,
|
||||
mock_delete_volume):
|
||||
mock_get_volume_status.return_value = 'available'
|
||||
@@ -409,6 +416,8 @@ class TestManager(base.TestCase):
|
||||
@mock.patch.object(ContainerActionEvent, 'event_finish')
|
||||
@mock.patch('zun.common.utils.spawn_n')
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch.object(VolumeMapping, 'list_by_volume',
|
||||
side_effect=FakeVolumeMapping.list_by_volume)
|
||||
@mock.patch.object(VolumeMapping, 'list_by_container',
|
||||
side_effect=FakeVolumeMapping.list_by_container)
|
||||
@mock.patch.object(fake_driver, 'detach_volume')
|
||||
@@ -418,7 +427,8 @@ class TestManager(base.TestCase):
|
||||
def test_container_run_image_not_found(
|
||||
self, mock_pull, mock_get_volume_status,
|
||||
mock_attach_volume, mock_detach_volume,
|
||||
mock_list_by_container, mock_save, mock_spawn_n, mock_event_finish,
|
||||
mock_list_by_container, mock_list_by_volume,
|
||||
mock_save, mock_spawn_n, mock_event_finish,
|
||||
mock_event_start):
|
||||
container_dict = utils.get_test_container(
|
||||
image='test:latest', image_driver='docker',
|
||||
@@ -452,6 +462,8 @@ class TestManager(base.TestCase):
|
||||
@mock.patch.object(ContainerActionEvent, 'event_finish')
|
||||
@mock.patch('zun.common.utils.spawn_n')
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch.object(VolumeMapping, 'list_by_volume',
|
||||
side_effect=FakeVolumeMapping.list_by_volume)
|
||||
@mock.patch.object(VolumeMapping, 'list_by_container',
|
||||
side_effect=FakeVolumeMapping.list_by_container)
|
||||
@mock.patch.object(fake_driver, 'detach_volume')
|
||||
@@ -461,7 +473,8 @@ class TestManager(base.TestCase):
|
||||
def test_container_run_image_pull_exception_raised(
|
||||
self, mock_pull, mock_get_volume_status,
|
||||
mock_attach_volume, mock_detach_volume,
|
||||
mock_list_by_container, mock_save, mock_spawn_n, mock_event_finish,
|
||||
mock_list_by_container, mock_list_by_volume,
|
||||
mock_save, mock_spawn_n, mock_event_finish,
|
||||
mock_event_start):
|
||||
container_dict = utils.get_test_container(
|
||||
image='test:latest', image_driver='docker',
|
||||
@@ -495,6 +508,8 @@ class TestManager(base.TestCase):
|
||||
@mock.patch.object(ContainerActionEvent, 'event_finish')
|
||||
@mock.patch('zun.common.utils.spawn_n')
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch.object(VolumeMapping, 'list_by_volume',
|
||||
side_effect=FakeVolumeMapping.list_by_volume)
|
||||
@mock.patch.object(VolumeMapping, 'list_by_container',
|
||||
side_effect=FakeVolumeMapping.list_by_container)
|
||||
@mock.patch.object(fake_driver, 'detach_volume')
|
||||
@@ -504,7 +519,8 @@ class TestManager(base.TestCase):
|
||||
def test_container_run_image_pull_docker_error(
|
||||
self, mock_pull, mock_get_volume_status,
|
||||
mock_attach_volume, mock_detach_volume,
|
||||
mock_list_by_container, mock_save, mock_spawn_n, mock_event_finish,
|
||||
mock_list_by_container, mock_list_by_volume,
|
||||
mock_save, mock_spawn_n, mock_event_finish,
|
||||
mock_event_start):
|
||||
container_dict = utils.get_test_container(
|
||||
image='test:latest', image_driver='docker',
|
||||
@@ -538,6 +554,8 @@ class TestManager(base.TestCase):
|
||||
@mock.patch.object(ContainerActionEvent, 'event_finish')
|
||||
@mock.patch('zun.common.utils.spawn_n')
|
||||
@mock.patch.object(Container, 'save')
|
||||
@mock.patch.object(VolumeMapping, 'list_by_volume',
|
||||
side_effect=FakeVolumeMapping.list_by_volume)
|
||||
@mock.patch.object(VolumeMapping, 'list_by_container',
|
||||
side_effect=FakeVolumeMapping.list_by_container)
|
||||
@mock.patch.object(fake_driver, 'detach_volume')
|
||||
@@ -548,7 +566,8 @@ class TestManager(base.TestCase):
|
||||
def test_container_run_create_raises_docker_error(
|
||||
self, mock_create, mock_pull, mock_get_volume_status,
|
||||
mock_attach_volume, mock_detach_volume,
|
||||
mock_list_by_container, mock_save, mock_spawn_n,
|
||||
mock_list_by_container, mock_list_by_volume,
|
||||
mock_save, mock_spawn_n,
|
||||
mock_event_finish, mock_event_start):
|
||||
container = Container(self.context, **utils.get_test_container())
|
||||
image = {'image': 'repo', 'path': 'out_path', 'driver': 'glance',
|
||||
|
||||
@@ -151,6 +151,7 @@ def get_test_volume_mapping(**kwargs):
|
||||
'1aca1705-20f3-4506-8bc3-59685d86a357'),
|
||||
'connection_info': kwargs.get('connection_info', 'fake_info'),
|
||||
'auto_remove': kwargs.get('auto_remove', False),
|
||||
'host': kwargs.get('host', 'fake_host'),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -345,7 +345,7 @@ class TestObject(test_base.TestCase, _TestObject):
|
||||
# https://docs.openstack.org/zun/latest/
|
||||
object_data = {
|
||||
'Container': '1.35-7cadf071bb6865a6da6b7be581ce76f6',
|
||||
'VolumeMapping': '1.1-50df6202f7846a136a91444c38eba841',
|
||||
'VolumeMapping': '1.2-2230102beda09cf5caabd130c600dc92',
|
||||
'Image': '1.1-330e6205c80b99b59717e1cfc6a79935',
|
||||
'MyObj': '1.0-34c4b1aadefd177b13f9a2f894cc23cd',
|
||||
'NUMANode': '1.0-cba878b70b2f8b52f1e031b41ac13b4e',
|
||||
|
||||
@@ -136,3 +136,8 @@ class Cinder(VolumeDriver):
|
||||
def get_volume_status(self, context, volume):
|
||||
ca = cinder_api.CinderAPI(context)
|
||||
return ca.get(volume.volume_id).status
|
||||
|
||||
@validate_volume_provider(supported_providers)
|
||||
def check_multiattach(self, context, volume):
|
||||
ca = cinder_api.CinderAPI(context)
|
||||
return ca.get(volume.volume_id).multiattach
|
||||
|
||||
Reference in New Issue
Block a user