From be2d2a1b8da02d6b8ab05240e4ab61b26f65e442 Mon Sep 17 00:00:00 2001
From: Sheel Rana <ranasheel2000@gmail.com>
Date: Wed, 30 Mar 2016 08:37:22 +0530
Subject: [PATCH] Add support for setting volume-type-access

OSC does not support to set volume type access to project.
This patch will provide support for adding volume type access
to existing project.

Closes-Bug:#1554889

Implements: bp cinder-command-support

Change-Id: Ie36e202bdde7de36eb263a476eb66699d82f7565
---
 doc/source/command-objects/volume-type.rst    | 13 ++++
 openstackclient/tests/volume/v2/fakes.py      |  2 +
 openstackclient/tests/volume/v2/test_type.py  | 66 +++++++++++++++++++
 openstackclient/volume/v2/volume_type.py      | 54 +++++++++++++--
 ...d_volume_type_access-32ba8d4bfb0f5f3d.yaml | 18 +++++
 5 files changed, 147 insertions(+), 6 deletions(-)
 create mode 100644 releasenotes/notes/add_volume_type_access-32ba8d4bfb0f5f3d.yaml

diff --git a/doc/source/command-objects/volume-type.rst b/doc/source/command-objects/volume-type.rst
index 69944fb954..64b1bd52d9 100644
--- a/doc/source/command-objects/volume-type.rst
+++ b/doc/source/command-objects/volume-type.rst
@@ -88,6 +88,8 @@ Set volume type properties
         [--name <name>]
         [--description <description>]
         [--property <key=value> [...] ]
+        [--project <project>]
+        [--project-domain <project-domain>]
         <volume-type>
 
 .. option:: --name <name>
@@ -102,6 +104,17 @@ Set volume type properties
 
     .. versionadded:: 2
 
+.. option:: --project <project>
+
+    Set volume type access to project (name or ID) (admin only)
+
+    *Volume version 2 only*
+
+.. option:: --project-domain <project-domain>
+
+    Domain the project belongs to (name or ID).
+    This can be used in case collisions between project names exist.
+
 .. option:: --property <key=value>
 
     Set a property on this volume type (repeat option to set multiple properties)
diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py
index 97bbc59bce..3c238d1006 100644
--- a/openstackclient/tests/volume/v2/fakes.py
+++ b/openstackclient/tests/volume/v2/fakes.py
@@ -243,6 +243,8 @@ class FakeVolumeClient(object):
         self.backups.resource_class = fakes.FakeResource(None, {})
         self.volume_types = mock.Mock()
         self.volume_types.resource_class = fakes.FakeResource(None, {})
+        self.volume_type_access = mock.Mock()
+        self.volume_type_access.resource_class = fakes.FakeResource(None, {})
         self.restores = mock.Mock()
         self.restores.resource_class = fakes.FakeResource(None, {})
         self.qos_specs = mock.Mock()
diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py
index b014706b69..448da4320e 100644
--- a/openstackclient/tests/volume/v2/test_type.py
+++ b/openstackclient/tests/volume/v2/test_type.py
@@ -15,6 +15,8 @@
 import copy
 
 from openstackclient.tests import fakes
+from openstackclient.tests.identity.v3 import fakes as identity_fakes
+from openstackclient.tests import utils as tests_utils
 from openstackclient.tests.volume.v2 import fakes as volume_fakes
 from openstackclient.volume.v2 import volume_type
 
@@ -41,6 +43,13 @@ class TestType(volume_fakes.TestVolume):
         self.types_mock = self.app.client_manager.volume.volume_types
         self.types_mock.reset_mock()
 
+        self.types_access_mock = (
+            self.app.client_manager.volume.volume_type_access)
+        self.types_access_mock.reset_mock()
+
+        self.projects_mock = self.app.client_manager.identity.projects
+        self.projects_mock.reset_mock()
+
 
 class TestTypeCreate(TestType):
 
@@ -211,6 +220,13 @@ class TestTypeSet(TestType):
             loaded=True,
         )
 
+        # Return a project
+        self.projects_mock.get.return_value = fakes.FakeResource(
+            None,
+            copy.deepcopy(identity_fakes.PROJECT),
+            loaded=True,
+        )
+
         # Get the command object to test
         self.cmd = volume_type.SetVolumeType(self.app, None)
 
@@ -286,6 +302,56 @@ class TestTypeSet(TestType):
         self.assertIn('myprop', result)
         self.assertEqual('myvalue', result['myprop'])
 
+    def test_type_set_not_called_without_project_argument(self):
+        arglist = [
+            '--project', '',
+            volume_fakes.type_id,
+        ]
+        verifylist = [
+            ('project', ''),
+            ('volume_type', volume_fakes.type_id),
+        ]
+
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        result = self.cmd.take_action(parsed_args)
+        self.assertIsNone(result)
+
+        self.assertFalse(self.types_access_mock.add_project_access.called)
+
+    def test_type_set_failed_with_missing_volume_type_argument(self):
+        arglist = [
+            '--project', 'identity_fakes.project_id',
+        ]
+        verifylist = [
+            ('project', 'identity_fakes.project_id'),
+        ]
+
+        self.assertRaises(tests_utils.ParserException,
+                          self.check_parser,
+                          self.cmd,
+                          arglist,
+                          verifylist)
+
+    def test_type_set_project_access(self):
+        arglist = [
+            '--project', identity_fakes.project_id,
+            volume_fakes.type_id,
+        ]
+        verifylist = [
+            ('project', identity_fakes.project_id),
+            ('volume_type', volume_fakes.type_id),
+        ]
+        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+        result = self.cmd.take_action(parsed_args)
+        self.assertIsNone(result)
+
+        self.types_access_mock.add_project_access.assert_called_with(
+            volume_fakes.type_id,
+            identity_fakes.project_id,
+        )
+
 
 class TestTypeShow(TestType):
 
diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py
index 5509ac5261..12828de2c4 100644
--- a/openstackclient/volume/v2/volume_type.py
+++ b/openstackclient/volume/v2/volume_type.py
@@ -17,8 +17,10 @@
 import six
 
 from openstackclient.common import command
+from openstackclient.common import exceptions
 from openstackclient.common import parseractions
 from openstackclient.common import utils
+from openstackclient.identity import common as identity_common
 
 
 class CreateVolumeType(command.ShowOne):
@@ -156,19 +158,30 @@ class SetVolumeType(command.Command):
             help='Set a property on this volume type '
                  '(repeat option to set multiple properties)',
         )
+        parser.add_argument(
+            '--project',
+            metavar='<project>',
+            help='Set volume type access to project (name or ID) (admin only)',
+        )
+        identity_common.add_project_domain_option_to_parser(parser)
+
         return parser
 
     def take_action(self, parsed_args):
         volume_client = self.app.client_manager.volume
+        identity_client = self.app.client_manager.identity
+
         volume_type = utils.find_resource(
             volume_client.volume_types, parsed_args.volume_type)
 
         if (not parsed_args.name
                 and not parsed_args.description
-                and not parsed_args.property):
+                and not parsed_args.property
+                and not parsed_args.project):
             self.app.log.error("No changes requested\n")
             return
 
+        result = 0
         kwargs = {}
         if parsed_args.name:
             kwargs['name'] = parsed_args.name
@@ -176,13 +189,42 @@ class SetVolumeType(command.Command):
             kwargs['description'] = parsed_args.description
 
         if kwargs:
-            volume_client.volume_types.update(
-                volume_type.id,
-                **kwargs
-            )
+            try:
+                volume_client.volume_types.update(
+                    volume_type.id,
+                    **kwargs
+                )
+            except Exception as e:
+                self.app.log.error("Failed to update volume type name or"
+                                   " description: " + str(e))
+                result += 1
 
         if parsed_args.property:
-            volume_type.set_keys(parsed_args.property)
+            try:
+                volume_type.set_keys(parsed_args.property)
+            except Exception as e:
+                self.app.log.error("Failed to set volume type property: " +
+                                   str(e))
+                result += 1
+
+        if parsed_args.project:
+            project_info = None
+            try:
+                project_info = identity_common.find_project(
+                    identity_client,
+                    parsed_args.project,
+                    parsed_args.project_domain)
+
+                volume_client.volume_type_access.add_project_access(
+                    volume_type.id, project_info.id)
+            except Exception as e:
+                self.app.log.error("Failed to set volume type access to"
+                                   " project: " + str(e))
+                result += 1
+
+        if result > 0:
+            raise exceptions.CommandError("Command Failed: One or more of the"
+                                          " operations failed")
 
 
 class ShowVolumeType(command.ShowOne):
diff --git a/releasenotes/notes/add_volume_type_access-32ba8d4bfb0f5f3d.yaml b/releasenotes/notes/add_volume_type_access-32ba8d4bfb0f5f3d.yaml
new file mode 100644
index 0000000000..69ae6e76f0
--- /dev/null
+++ b/releasenotes/notes/add_volume_type_access-32ba8d4bfb0f5f3d.yaml
@@ -0,0 +1,18 @@
+---
+features:
+  - |
+    Added support for setting volume type access to project.
+
+    By default, volumes types are public.
+    To create a private volume type, user needs to set is_public boolean
+    field to false at volume type creation time.
+    To control access to a private volume type, user needs to add access
+    of a private volume type to project.
+
+    So, this feature enables user to add private volume type access to a
+    project using below command
+
+    ``volume type set --project <project> <volume_type>``.
+
+    [Bug 1554889 'https://bugs.launchpad.net/python-openstackclient/+bug/1554889'_]
+