Merge "Support Cinder volume multi-attach"

This commit is contained in:
Zuul
2018-08-08 04:03:26 +00:00
committed by Gerrit Code Review
10 changed files with 98 additions and 10 deletions

View File

@@ -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()

View File

@@ -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,

View File

@@ -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()

View File

@@ -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))

View File

@@ -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):

View File

@@ -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.

View File

@@ -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',

View File

@@ -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'),
}

View File

@@ -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',

View File

@@ -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