diff --git a/ironic/db/api.py b/ironic/db/api.py index c86b7e3c21..df8ab39f0a 100644 --- a/ironic/db/api.py +++ b/ironic/db/api.py @@ -823,6 +823,23 @@ class Connection(object): :raises: InvalidParameterValue if sort_key does not exist. """ + @abc.abstractmethod + def get_volume_targets_by_volume_id(self, volume_id, limit=None, + marker=None, sort_key=None, + sort_dir=None): + """List all the volume targets for a given volume id. + + :param volume_id: The UUID of the volume. + :param limit: Maximum number of volume targets to return. + :param marker: the last item of the previous page; we return the next + result set. + :param sort_key: Attribute by which results should be sorted + :param sort_dir: direction in which results should be sorted + (asc, desc) + :returns: A list of volume targets. + :raises: InvalidParameterValue if sort_key does not exist. + """ + @abc.abstractmethod def create_volume_target(self, target_info): """Create a new volume target. diff --git a/ironic/db/sqlalchemy/api.py b/ironic/db/sqlalchemy/api.py index e6c3ab6824..52ab2ae3d7 100644 --- a/ironic/db/sqlalchemy/api.py +++ b/ironic/db/sqlalchemy/api.py @@ -1067,6 +1067,13 @@ class Connection(api.Connection): return _paginate_query(models.VolumeTarget, limit, marker, sort_key, sort_dir, query) + def get_volume_targets_by_volume_id(self, volume_id, limit=None, + marker=None, sort_key=None, + sort_dir=None): + query = model_query(models.VolumeTarget).filter_by(volume_id=volume_id) + return _paginate_query(models.VolumeTarget, limit, marker, sort_key, + sort_dir, query) + @oslo_db_api.retry_on_deadlock def create_volume_target(self, target_info): if 'uuid' not in target_info: diff --git a/ironic/objects/volume_target.py b/ironic/objects/volume_target.py index b1ce2c2d4e..8fc555743d 100644 --- a/ironic/objects/volume_target.py +++ b/ironic/objects/volume_target.py @@ -149,6 +149,33 @@ class VolumeTarget(base.IronicObject, sort_dir=sort_dir) return cls._from_db_object_list(context, db_targets) + # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable + # methods can be used in the future to replace current explicit RPC calls. + # Implications of calling new remote procedures should be thought through. + # @object_base.remotable_classmethod + @classmethod + def list_by_volume_id(cls, context, volume_id, limit=None, marker=None, + sort_key=None, sort_dir=None): + """Return a list of VolumeTarget objects related to a given volume ID. + + :param context: security context + :param volume_id: the UUID of the volume + :param limit: maximum number of volume targets to return in a + single result + :param marker: pagination marker for large data sets + :param sort_key: column to sort results by + :param sort_dir: direction to sort. "asc" or "desc". + :returns: a list of :class:`VolumeTarget` objects + :raises: InvalidParameterValue if sort_key does not exist + """ + db_targets = cls.dbapi.get_volume_targets_by_volume_id( + volume_id, + limit=limit, + marker=marker, + sort_key=sort_key, + sort_dir=sort_dir) + return cls._from_db_object_list(context, db_targets) + # NOTE(xek): We don't want to enable RPC on this call just yet. Remotable # methods can be used in the future to replace current explicit RPC calls. # Implications of calling new remote procedures should be thought through. diff --git a/ironic/tests/unit/db/test_volume_targets.py b/ironic/tests/unit/db/test_volume_targets.py index cd970e2021..4dd4a3af78 100644 --- a/ironic/tests/unit/db/test_volume_targets.py +++ b/ironic/tests/unit/db/test_volume_targets.py @@ -115,6 +115,16 @@ class DbVolumeTargetTestCase(base.DbTestCase): def test_get_volume_targets_by_node_id_that_does_not_exist(self): self.assertEqual([], self.dbapi.get_volume_targets_by_node_id(99)) + def test_get_volume_targets_by_volume_id(self): + # Create two volume_targets. They'll have the same volume_id. + uuids = self._create_list_of_volume_targets(2) + res = self.dbapi.get_volume_targets_by_volume_id('12345678') + res_uuids = [r.uuid for r in res] + self.assertEqual(uuids, res_uuids) + + def test_get_volume_targets_by_volume_id_that_does_not_exist(self): + self.assertEqual([], self.dbapi.get_volume_targets_by_volume_id('dne')) + def test_update_volume_target(self): old_boot_index = self.target.boot_index new_boot_index = old_boot_index + 1 diff --git a/ironic/tests/unit/objects/test_volume_target.py b/ironic/tests/unit/objects/test_volume_target.py index 40f71a25e6..bf53e81c81 100644 --- a/ironic/tests/unit/objects/test_volume_target.py +++ b/ironic/tests/unit/objects/test_volume_target.py @@ -110,6 +110,21 @@ class TestVolumeTargetObject(base.DbTestCase): self.assertIsInstance(volume_targets[0], objects.VolumeTarget) self.assertEqual(self.context, volume_targets[0]._context) + def test_list_by_volume_id(self): + with mock.patch.object(self.dbapi, 'get_volume_targets_by_volume_id', + autospec=True) as mock_get_list_by_volume_id: + mock_get_list_by_volume_id.return_value = [self.volume_target_dict] + volume_id = self.volume_target_dict['volume_id'] + volume_targets = objects.VolumeTarget.list_by_volume_id( + self.context, volume_id, limit=10, sort_dir='desc') + + mock_get_list_by_volume_id.assert_called_once_with( + volume_id, limit=10, marker=None, + sort_key=None, sort_dir='desc') + self.assertThat(volume_targets, HasLength(1)) + self.assertIsInstance(volume_targets[0], objects.VolumeTarget) + self.assertEqual(self.context, volume_targets[0]._context) + def test_create(self): with mock.patch.object(self.dbapi, 'create_volume_target', autospec=True) as mock_db_create: