diff --git a/cinder/api/v3/views/attachments.py b/cinder/api/v3/views/attachments.py index ba3e671601b..b24785a8840 100644 --- a/cinder/api/v3/views/attachments.py +++ b/cinder/api/v3/views/attachments.py @@ -32,7 +32,7 @@ class ViewBuilder(object): attached_at=cls._normalize(attachment.attach_time), detached_at=cls._normalize(attachment.detach_time), attach_mode=attachment.attach_mode, - connection_info=getattr(attachment, 'connection_info', None),) + connection_info=attachment.connection_info) if flat: return result return {'attachment': result} diff --git a/cinder/db/sqlalchemy/migrate_repo/versions/099_add_connection_info_to_attachment.py b/cinder/db/sqlalchemy/migrate_repo/versions/099_add_connection_info_to_attachment.py new file mode 100644 index 00000000000..3993a17114b --- /dev/null +++ b/cinder/db/sqlalchemy/migrate_repo/versions/099_add_connection_info_to_attachment.py @@ -0,0 +1,22 @@ +# 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. + +from sqlalchemy import MetaData, Table, Column, Text + + +def upgrade(migrate_engine): + meta = MetaData(migrate_engine) + + # Add connection_info column to attachment table + attachment = Table('volume_attachment', meta, autoload=True) + connection_info = Column('connection_info', Text) + attachment.create_column(connection_info) diff --git a/cinder/db/sqlalchemy/models.py b/cinder/db/sqlalchemy/models.py index 541cfbc5ba2..4324d93c750 100644 --- a/cinder/db/sqlalchemy/models.py +++ b/cinder/db/sqlalchemy/models.py @@ -354,6 +354,7 @@ class VolumeAttachment(BASE, CinderBase): detach_time = Column(DateTime) attach_status = Column(String(255)) attach_mode = Column(String(255)) + connection_info = Column(Text) class VolumeTypes(BASE, CinderBase): diff --git a/cinder/objects/base.py b/cinder/objects/base.py index 73ea9dd768a..6f5dd8f843d 100644 --- a/cinder/objects/base.py +++ b/cinder/objects/base.py @@ -130,6 +130,7 @@ OBJ_VERSIONS.add('1.21', {'ManageableSnapshot': '1.0', 'ManageableVolumeList': '1.0', 'ManageableSnapshotList': '1.0'}) OBJ_VERSIONS.add('1.22', {'Snapshot': '1.4'}) +OBJ_VERSIONS.add('1.23', {'VolumeAttachment': '1.2'}) class CinderObjectRegistry(base.VersionedObjectRegistry): diff --git a/cinder/objects/volume_attachment.py b/cinder/objects/volume_attachment.py index e84a44ea5a6..8883c830de4 100644 --- a/cinder/objects/volume_attachment.py +++ b/cinder/objects/volume_attachment.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_serialization import jsonutils from oslo_versionedobjects import fields from cinder import db @@ -28,7 +29,8 @@ class VolumeAttachment(base.CinderPersistentObject, base.CinderObject, base.CinderComparableObject): # Version 1.0: Initial version # Version 1.1: Added volume relationship - VERSION = '1.1' + # Version 1.2: Added connection_info attribute + VERSION = '1.2' OPTIONAL_FIELDS = ['volume'] obj_extra_fields = ['project_id', 'volume_host'] @@ -47,6 +49,7 @@ class VolumeAttachment(base.CinderPersistentObject, base.CinderObject, 'attach_mode': fields.StringField(nullable=True), 'volume': fields.ObjectField('Volume', nullable=False), + 'connection_info': fields.DictOfNullableStringsField(nullable=True) } @property @@ -73,8 +76,11 @@ class VolumeAttachment(base.CinderPersistentObject, base.CinderObject, value = db_attachment.get(name) if isinstance(field, fields.IntegerField): value = value or 0 - attachment[name] = value - + if name == 'connection_info': + attachment.connection_info = jsonutils.loads( + value) if value else None + else: + attachment[name] = value if 'volume' in expected_attrs: db_volume = db_attachment.get('volume') if db_volume: @@ -100,9 +106,17 @@ class VolumeAttachment(base.CinderPersistentObject, base.CinderObject, self.obj_reset_changes(fields=[attrname]) + @staticmethod + def _convert_connection_info_to_db_format(updates): + properties = updates.pop('connection_info', None) + if properties is not None: + updates['connection_info'] = jsonutils.dumps(properties) + def save(self): updates = self.cinder_obj_get_changes() if updates: + if 'connection_info' in updates: + self._convert_connection_info_to_db_format(updates) if 'volume' in updates: raise exception.ObjectActionError(action='save', reason=_('volume changed')) diff --git a/cinder/tests/unit/attachments/test_attachments_api.py b/cinder/tests/unit/attachments/test_attachments_api.py index 9742d141fd1..a18d41eeecf 100644 --- a/cinder/tests/unit/attachments/test_attachments_api.py +++ b/cinder/tests/unit/attachments/test_attachments_api.py @@ -61,17 +61,22 @@ class AttachmentManagerTestCase(test.TestCase): mock_policy): """Test attachment_create with connector.""" volume_params = {'status': 'available'} + connection_info = {'fake_key': 'fake_value'} + mock_rpc_attachment_update.return_value = connection_info vref = tests_utils.create_volume(self.context, **volume_params) connector = {'fake': 'connector'} - self.volume_api.attachment_create(self.context, - vref, - fake.UUID2, - connector) + attachment = self.volume_api.attachment_create(self.context, + vref, + fake.UUID2, + connector) mock_rpc_attachment_update.assert_called_once_with(self.context, mock.ANY, connector, mock.ANY) + new_attachment = objects.VolumeAttachment.get_by_id(self.context, + attachment.id) + self.assertEqual(connection_info, new_attachment.connection_info) @mock.patch('cinder.volume.api.check_policy') @mock.patch('cinder.volume.rpcapi.VolumeAPI.attachment_delete') @@ -106,6 +111,8 @@ class AttachmentManagerTestCase(test.TestCase): mock_policy): """Test attachment_delete.""" volume_params = {'status': 'available'} + connection_info = {'fake_key': 'fake_value'} + mock_rpc_attachment_update.return_value = connection_info vref = tests_utils.create_volume(self.context, **volume_params) aref = self.volume_api.attachment_create(self.context, @@ -120,6 +127,9 @@ class AttachmentManagerTestCase(test.TestCase): self.volume_api.attachment_update(self.context, aref, connector) + aref = objects.VolumeAttachment.get_by_id(self.context, + aref.id) + self.assertEqual(connection_info, aref.connection_info) # We mock the actual call that updates the status # so force it here values = {'volume_id': vref.id, diff --git a/cinder/tests/unit/objects/test_objects.py b/cinder/tests/unit/objects/test_objects.py index a1656b9fef1..d311427d8dd 100644 --- a/cinder/tests/unit/objects/test_objects.py +++ b/cinder/tests/unit/objects/test_objects.py @@ -47,7 +47,7 @@ object_data = { 'SnapshotList': '1.0-15ecf022a68ddbb8c2a6739cfc9f8f5e', 'Volume': '1.6-7d3bc8577839d5725670d55e480fe95f', 'VolumeList': '1.1-15ecf022a68ddbb8c2a6739cfc9f8f5e', - 'VolumeAttachment': '1.1-ed82a5fdd56655e14d9f86396c130aea', + 'VolumeAttachment': '1.2-b68b357a1756582b706006ea9de40c9a', 'VolumeAttachmentList': '1.1-15ecf022a68ddbb8c2a6739cfc9f8f5e', 'VolumeProperties': '1.1-cadac86b2bdc11eb79d1dcea988ff9e8', 'VolumeType': '1.3-a5d8c3473db9bc3bbcdbab9313acf4d1', diff --git a/releasenotes/notes/add-connection-info-to-attachment-84d4dg45uh41db15.yaml b/releasenotes/notes/add-connection-info-to-attachment-84d4dg45uh41db15.yaml new file mode 100644 index 00000000000..f140ce3446a --- /dev/null +++ b/releasenotes/notes/add-connection-info-to-attachment-84d4dg45uh41db15.yaml @@ -0,0 +1,3 @@ +--- +features: + - Added attribute ``connection_info`` to attachment object.