Browse Source

Add VolumeConnector object

This patch adds the VolumeConnector object. It handles the
volume connector information that is stored in the database.

Co-Authored-By: Tomoki Sekiyama <tomoki.sekiyama.qu@hitachi.com>
Co-Authored-By: Stephane Miller <stephane@alum.mit.edu>
Co-Authored-By: Ruby Loo <ruby.loo@intel.com>
Change-Id: I06aa1a94401e7c7faeb019926272a072d1ed1c51
Partial-Bug: 1526231
tags/7.0.0
Satoru Moriya 3 years ago
parent
commit
bc7daf9779

+ 1
- 0
ironic/objects/__init__.py View File

@@ -29,3 +29,4 @@ def register_all():
29 29
     __import__('ironic.objects.node')
30 30
     __import__('ironic.objects.port')
31 31
     __import__('ironic.objects.portgroup')
32
+    __import__('ironic.objects.volume_connector')

+ 246
- 0
ironic/objects/volume_connector.py View File

@@ -0,0 +1,246 @@
1
+#    Copyright (c) 2015 Hitachi, Ltd.
2
+#
3
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+#    not use this file except in compliance with the License. You may obtain
5
+#    a copy of the License at
6
+#
7
+#         http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+#    Unless required by applicable law or agreed to in writing, software
10
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+#    License for the specific language governing permissions and limitations
13
+#    under the License.
14
+
15
+from oslo_utils import strutils
16
+from oslo_utils import uuidutils
17
+from oslo_versionedobjects import base as object_base
18
+
19
+from ironic.common import exception
20
+from ironic.db import api as db_api
21
+from ironic.objects import base
22
+from ironic.objects import fields as object_fields
23
+
24
+
25
+@base.IronicObjectRegistry.register
26
+class VolumeConnector(base.IronicObject,
27
+                      object_base.VersionedObjectDictCompat):
28
+    # Version 1.0: Initial version
29
+    VERSION = '1.0'
30
+
31
+    dbapi = db_api.get_instance()
32
+
33
+    fields = {
34
+        'id': object_fields.IntegerField(),
35
+        'uuid': object_fields.UUIDField(nullable=True),
36
+        'node_id': object_fields.IntegerField(nullable=True),
37
+        'type': object_fields.StringField(nullable=True),
38
+        'connector_id': object_fields.StringField(nullable=True),
39
+        'extra': object_fields.FlexibleDictField(nullable=True),
40
+    }
41
+
42
+    @staticmethod
43
+    def _from_db_object_list(db_objects, cls, context):
44
+        """Convert a list of database entities to a list of formal objects."""
45
+        return [VolumeConnector._from_db_object(cls(context), obj)
46
+                for obj in db_objects]
47
+
48
+    # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
49
+    # methods can be used in the future to replace current explicit RPC calls.
50
+    # Implications of calling new remote procedures should be thought through.
51
+    # @object_base.remotable_classmethod
52
+    @classmethod
53
+    def get(cls, context, ident):
54
+        """Find a volume connector based on its ID or UUID.
55
+
56
+        :param context: security context
57
+        :param ident: the database primary key ID *or* the UUID of a volume
58
+                      connector
59
+        :returns: a :class:`VolumeConnector` object
60
+        :raises: InvalidIdentity if ident is neither an integer ID nor a UUID
61
+        :raises: VolumeConnectorNotFound if no volume connector exists with
62
+                 the specified ident
63
+        """
64
+        if strutils.is_int_like(ident):
65
+            return cls.get_by_id(context, ident)
66
+        elif uuidutils.is_uuid_like(ident):
67
+            return cls.get_by_uuid(context, ident)
68
+        else:
69
+            raise exception.InvalidIdentity(identity=ident)
70
+
71
+    # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
72
+    # methods can be used in the future to replace current explicit RPC calls.
73
+    # Implications of calling new remote procedures should be thought through.
74
+    # @object_base.remotable_classmethod
75
+    @classmethod
76
+    def get_by_id(cls, context, id):
77
+        """Find a volume connector based on its integer ID.
78
+
79
+        :param context: security context
80
+        :param id: the integer (database primary key) ID of a volume connector
81
+        :returns: a :class:`VolumeConnector` object
82
+        :raises: VolumeConnectorNotFound if no volume connector exists with
83
+                 the specified ID
84
+        """
85
+        db_connector = cls.dbapi.get_volume_connector_by_id(id)
86
+        connector = VolumeConnector._from_db_object(cls(context), db_connector)
87
+        return connector
88
+
89
+    # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
90
+    # methods can be used in the future to replace current explicit RPC calls.
91
+    # Implications of calling new remote procedures should be thought through.
92
+    # @object_base.remotable_classmethod
93
+    @classmethod
94
+    def get_by_uuid(cls, context, uuid):
95
+        """Find a volume connector based on its UUID.
96
+
97
+        :param context: security context
98
+        :param uuid: the UUID of a volume connector
99
+        :returns: a :class:`VolumeConnector` object
100
+        :raises: VolumeConnectorNotFound if no volume connector exists with
101
+                 the specified UUID
102
+        """
103
+        db_connector = cls.dbapi.get_volume_connector_by_uuid(uuid)
104
+        connector = VolumeConnector._from_db_object(cls(context), db_connector)
105
+        return connector
106
+
107
+    # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
108
+    # methods can be used in the future to replace current explicit RPC calls.
109
+    # Implications of calling new remote procedures should be thought through.
110
+    # @object_base.remotable_classmethod
111
+    @classmethod
112
+    def list(cls, context, limit=None, marker=None,
113
+             sort_key=None, sort_dir=None):
114
+        """Return a list of VolumeConnector objects.
115
+
116
+        :param context: security context
117
+        :param limit: maximum number of resources to return in a single result
118
+        :param marker: pagination marker for large data sets
119
+        :param sort_key: column to sort results by
120
+        :param sort_dir: direction to sort. "asc" or "desc".
121
+        :returns: a list of :class:`VolumeConnector` objects
122
+        :raises: InvalidParameterValue if sort_key does not exist
123
+        """
124
+        db_connectors = cls.dbapi.get_volume_connector_list(limit=limit,
125
+                                                            marker=marker,
126
+                                                            sort_key=sort_key,
127
+                                                            sort_dir=sort_dir)
128
+        return VolumeConnector._from_db_object_list(db_connectors,
129
+                                                    cls, context)
130
+
131
+    # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
132
+    # methods can be used in the future to replace current explicit RPC calls.
133
+    # Implications of calling new remote procedures should be thought through.
134
+    # @object_base.remotable_classmethod
135
+    @classmethod
136
+    def list_by_node_id(cls, context, node_id, limit=None, marker=None,
137
+                        sort_key=None, sort_dir=None):
138
+        """Return a list of VolumeConnector objects related to a given node ID.
139
+
140
+        :param context: security context
141
+        :param node_id: the integer ID of the node
142
+        :param limit: maximum number of resources to return in a single result
143
+        :param marker: pagination marker for large data sets
144
+        :param sort_key: column to sort results by
145
+        :param sort_dir: direction to sort. "asc" or "desc".
146
+        :returns: a list of :class:`VolumeConnector` objects
147
+        :raises: InvalidParameterValue if sort_key does not exist
148
+        """
149
+        db_connectors = cls.dbapi.get_volume_connectors_by_node_id(
150
+            node_id,
151
+            limit=limit,
152
+            marker=marker,
153
+            sort_key=sort_key,
154
+            sort_dir=sort_dir)
155
+        return VolumeConnector._from_db_object_list(db_connectors,
156
+                                                    cls, context)
157
+
158
+    # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
159
+    # methods can be used in the future to replace current explicit RPC calls.
160
+    # Implications of calling new remote procedures should be thought through.
161
+    # @object_base.remotable
162
+    def create(self, context=None):
163
+        """Create a VolumeConnector record in the DB.
164
+
165
+        :param context: security context. NOTE: This should only
166
+                        be used internally by the indirection_api.
167
+                        Unfortunately, RPC requires context as the first
168
+                        argument, even though we don't use it.
169
+                        A context should be set when instantiating the
170
+                        object, e.g.: VolumeConnector(context).
171
+        :raises: VolumeConnectorTypeAndIdAlreadyExists if a volume
172
+                 connector already exists with the same type and connector_id
173
+        :raises: VolumeConnectorAlreadyExists if a volume connector with the
174
+                 same UUID already exists
175
+        """
176
+        values = self.obj_get_changes()
177
+        db_connector = self.dbapi.create_volume_connector(values)
178
+        self._from_db_object(self, db_connector)
179
+
180
+    # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
181
+    # methods can be used in the future to replace current explicit RPC calls.
182
+    # Implications of calling new remote procedures should be thought through.
183
+    # @object_base.remotable
184
+    def destroy(self, context=None):
185
+        """Delete the VolumeConnector from the DB.
186
+
187
+        :param context: security context. NOTE: This should only
188
+                        be used internally by the indirection_api.
189
+                        Unfortunately, RPC requires context as the first
190
+                        argument, even though we don't use it.
191
+                        A context should be set when instantiating the
192
+                        object, e.g.: VolumeConnector(context).
193
+        :raises: VolumeConnectorNotFound if the volume connector cannot be
194
+                 found
195
+        """
196
+        self.dbapi.destroy_volume_connector(self.uuid)
197
+        self.obj_reset_changes()
198
+
199
+    # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
200
+    # methods can be used in the future to replace current explicit RPC calls.
201
+    # Implications of calling new remote procedures should be thought through.
202
+    # @object_base.remotable
203
+    def save(self, context=None):
204
+        """Save updates to this VolumeConnector.
205
+
206
+        Updates will be made column by column based on the result
207
+        of self.obj_get_changes().
208
+
209
+        :param context: security context. NOTE: This should only
210
+                        be used internally by the indirection_api.
211
+                        Unfortunately, RPC requires context as the first
212
+                        argument, even though we don't use it.
213
+                        A context should be set when instantiating the
214
+                        object, e.g.: VolumeConnector(context).
215
+        :raises: VolumeConnectorNotFound if the volume connector cannot be
216
+                 found
217
+        :raises: VolumeConnectorTypeAndIdAlreadyExists if another connector
218
+                 already exists with the same values for type and connector_id
219
+                 fields
220
+        :raises: InvalidParameterValue when the UUID is being changed
221
+        """
222
+        updates = self.obj_get_changes()
223
+        updated_connector = self.dbapi.update_volume_connector(self.uuid,
224
+                                                               updates)
225
+        self._from_db_object(self, updated_connector)
226
+
227
+    # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable
228
+    # methods can be used in the future to replace current explicit RPC calls.
229
+    # Implications of calling new remote procedures should be thought through.
230
+    # @object_base.remotable
231
+    def refresh(self, context=None):
232
+        """Load updates for this VolumeConnector.
233
+
234
+        Load a volume connector with the same UUID from the database
235
+        and check for updated attributes. If there are any updates,
236
+        they are applied from the loaded volume connector, column by column.
237
+
238
+        :param context: security context. NOTE: This should only
239
+                        be used internally by the indirection_api.
240
+                        Unfortunately, RPC requires context as the first
241
+                        argument, even though we don't use it.
242
+                        A context should be set when instantiating the
243
+                        object, e.g.: VolumeConnector(context).
244
+        """
245
+        current = self.__class__.get_by_uuid(self._context, uuid=self.uuid)
246
+        self.obj_refresh(current)

+ 2
- 1
ironic/tests/unit/objects/test_objects.py View File

@@ -420,7 +420,8 @@ expected_object_fingerprints = {
420 420
     'NodeCorrectedPowerStatePayload': '1.0-2a484d7c342caa9fe488de16dc5f1f1e',
421 421
     'NodeSetProvisionStateNotification':
422 422
         '1.0-59acc533c11d306f149846f922739c15',
423
-    'NodeSetProvisionStatePayload': '1.0-91be7439b9b6b04931c9b99b8e1ea87a'
423
+    'NodeSetProvisionStatePayload': '1.0-91be7439b9b6b04931c9b99b8e1ea87a',
424
+    'VolumeConnector': '1.0-3e0252c0ab6e6b9d158d09238a577d97'
424 425
 }
425 426
 
426 427
 

+ 179
- 0
ironic/tests/unit/objects/test_volume_connector.py View File

@@ -0,0 +1,179 @@
1
+# Copyright 2015 Hitachi Data Systems
2
+#
3
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+#    not use this file except in compliance with the License. You may obtain
5
+#    a copy of the License at
6
+#
7
+#         http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+#    Unless required by applicable law or agreed to in writing, software
10
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+#    License for the specific language governing permissions and limitations
13
+#    under the License.
14
+
15
+import datetime
16
+
17
+import mock
18
+from testtools.matchers import HasLength
19
+
20
+from ironic.common import exception
21
+from ironic import objects
22
+from ironic.tests.unit.db import base
23
+from ironic.tests.unit.db import utils
24
+
25
+
26
+class TestVolumeConnectorObject(base.DbTestCase):
27
+
28
+    def setUp(self):
29
+        super(TestVolumeConnectorObject, self).setUp()
30
+        self.volume_connector_dict = utils.get_test_volume_connector()
31
+
32
+    @mock.patch('ironic.objects.VolumeConnector.get_by_uuid')
33
+    @mock.patch('ironic.objects.VolumeConnector.get_by_id')
34
+    def test_get(self, mock_get_by_id, mock_get_by_uuid):
35
+        id = self.volume_connector_dict['id']
36
+        uuid = self.volume_connector_dict['uuid']
37
+
38
+        objects.VolumeConnector.get(self.context, id)
39
+        mock_get_by_id.assert_called_once_with(self.context, id)
40
+        self.assertFalse(mock_get_by_uuid.called)
41
+
42
+        objects.VolumeConnector.get(self.context, uuid)
43
+        mock_get_by_uuid.assert_called_once_with(self.context, uuid)
44
+
45
+        # Invalid identifier (not ID or UUID)
46
+        self.assertRaises(exception.InvalidIdentity,
47
+                          objects.VolumeConnector.get,
48
+                          self.context, 'not-valid-identifier')
49
+
50
+    def test_get_by_id(self):
51
+        id = self.volume_connector_dict['id']
52
+        with mock.patch.object(self.dbapi, 'get_volume_connector_by_id',
53
+                               autospec=True) as mock_get_volume_connector:
54
+            mock_get_volume_connector.return_value = self.volume_connector_dict
55
+
56
+            connector = objects.VolumeConnector.get_by_id(self.context, id)
57
+
58
+            mock_get_volume_connector.assert_called_once_with(id)
59
+            self.assertIsInstance(connector, objects.VolumeConnector)
60
+            self.assertEqual(self.context, connector._context)
61
+
62
+    def test_get_by_uuid(self):
63
+        uuid = self.volume_connector_dict['uuid']
64
+        with mock.patch.object(self.dbapi, 'get_volume_connector_by_uuid',
65
+                               autospec=True) as mock_get_volume_connector:
66
+            mock_get_volume_connector.return_value = self.volume_connector_dict
67
+
68
+            connector = objects.VolumeConnector.get_by_uuid(self.context, uuid)
69
+
70
+            mock_get_volume_connector.assert_called_once_with(uuid)
71
+            self.assertIsInstance(connector, objects.VolumeConnector)
72
+            self.assertEqual(self.context, connector._context)
73
+
74
+    def test_list(self):
75
+        with mock.patch.object(self.dbapi, 'get_volume_connector_list',
76
+                               autospec=True) as mock_get_list:
77
+            mock_get_list.return_value = [self.volume_connector_dict]
78
+            volume_connectors = objects.VolumeConnector.list(
79
+                self.context, limit=4, sort_key='uuid', sort_dir='asc')
80
+
81
+            mock_get_list.assert_called_once_with(
82
+                limit=4, marker=None, sort_key='uuid', sort_dir='asc')
83
+            self.assertThat(volume_connectors, HasLength(1))
84
+            self.assertIsInstance(volume_connectors[0],
85
+                                  objects.VolumeConnector)
86
+            self.assertEqual(self.context, volume_connectors[0]._context)
87
+
88
+    def test_list_none(self):
89
+        with mock.patch.object(self.dbapi, 'get_volume_connector_list',
90
+                               autospec=True) as mock_get_list:
91
+            mock_get_list.return_value = []
92
+            volume_connectors = objects.VolumeConnector.list(
93
+                self.context, limit=4, sort_key='uuid', sort_dir='asc')
94
+
95
+            mock_get_list.assert_called_once_with(
96
+                limit=4, marker=None, sort_key='uuid', sort_dir='asc')
97
+            self.assertEqual([], volume_connectors)
98
+
99
+    def test_list_by_node_id(self):
100
+        with mock.patch.object(self.dbapi, 'get_volume_connectors_by_node_id',
101
+                               autospec=True) as mock_get_list_by_node_id:
102
+            mock_get_list_by_node_id.return_value = [
103
+                self.volume_connector_dict]
104
+            node_id = self.volume_connector_dict['node_id']
105
+            volume_connectors = objects.VolumeConnector.list_by_node_id(
106
+                self.context, node_id, limit=10, sort_dir='desc')
107
+
108
+            mock_get_list_by_node_id.assert_called_once_with(
109
+                node_id, limit=10, marker=None, sort_key=None, sort_dir='desc')
110
+            self.assertThat(volume_connectors, HasLength(1))
111
+            self.assertIsInstance(volume_connectors[0],
112
+                                  objects.VolumeConnector)
113
+            self.assertEqual(self.context, volume_connectors[0]._context)
114
+
115
+    def test_create(self):
116
+        with mock.patch.object(self.dbapi, 'create_volume_connector',
117
+                               autospec=True) as mock_db_create:
118
+            mock_db_create.return_value = self.volume_connector_dict
119
+            new_connector = objects.VolumeConnector(
120
+                self.context, **self.volume_connector_dict)
121
+            new_connector.create()
122
+
123
+            mock_db_create.assert_called_once_with(self.volume_connector_dict)
124
+
125
+    def test_destroy(self):
126
+        uuid = self.volume_connector_dict['uuid']
127
+        with mock.patch.object(self.dbapi, 'get_volume_connector_by_uuid',
128
+                               autospec=True) as mock_get_volume_connector:
129
+            mock_get_volume_connector.return_value = self.volume_connector_dict
130
+            with mock.patch.object(self.dbapi, 'destroy_volume_connector',
131
+                                   autospec=True) as mock_db_destroy:
132
+                connector = objects.VolumeConnector.get_by_uuid(self.context,
133
+                                                                uuid)
134
+                connector.destroy()
135
+
136
+                mock_db_destroy.assert_called_once_with(uuid)
137
+
138
+    def test_save(self):
139
+        uuid = self.volume_connector_dict['uuid']
140
+        connector_id = "new_connector_id"
141
+        test_time = datetime.datetime(2000, 1, 1, 0, 0)
142
+        with mock.patch.object(self.dbapi, 'get_volume_connector_by_uuid',
143
+                               autospec=True) as mock_get_volume_connector:
144
+            mock_get_volume_connector.return_value = self.volume_connector_dict
145
+            with mock.patch.object(self.dbapi, 'update_volume_connector',
146
+                                   autospec=True) as mock_update_connector:
147
+                mock_update_connector.return_value = (
148
+                    utils.get_test_volume_connector(connector_id=connector_id,
149
+                                                    updated_at=test_time))
150
+                c = objects.VolumeConnector.get_by_uuid(self.context, uuid)
151
+                c.connector_id = connector_id
152
+                c.save()
153
+
154
+                mock_get_volume_connector.assert_called_once_with(uuid)
155
+                mock_update_connector.assert_called_once_with(
156
+                    uuid,
157
+                    {'connector_id': connector_id})
158
+                self.assertEqual(self.context, c._context)
159
+                res_updated_at = (c.updated_at).replace(tzinfo=None)
160
+                self.assertEqual(test_time, res_updated_at)
161
+
162
+    def test_refresh(self):
163
+        uuid = self.volume_connector_dict['uuid']
164
+        old_connector_id = self.volume_connector_dict['connector_id']
165
+        returns = [self.volume_connector_dict,
166
+                   utils.get_test_volume_connector(
167
+                       connector_id="new_connector_id")]
168
+        expected = [mock.call(uuid), mock.call(uuid)]
169
+        with mock.patch.object(self.dbapi, 'get_volume_connector_by_uuid',
170
+                               side_effect=returns,
171
+                               autospec=True) as mock_get_volume_connector:
172
+            c = objects.VolumeConnector.get_by_uuid(self.context, uuid)
173
+            self.assertEqual(old_connector_id, c.connector_id)
174
+            c.refresh()
175
+            self.assertEqual('new_connector_id', c.connector_id)
176
+
177
+            self.assertEqual(expected,
178
+                             mock_get_volume_connector.call_args_list)
179
+            self.assertEqual(self.context, c._context)

Loading…
Cancel
Save