Add "encryption-*" options in volume type commands
Add "--encryption-provider", "--encryption-cipher", "--encryption-key-size" and "--encryption-control-location" options to "volume type set" and "volume type create" commands. Add "--encryption-type" option to "volume type unset", "volume type list" and "volume type show" commands. Change-Id: I3572635d5913d971a723a62d7790ffe0f20ec39a Implements: bp cinder-command-support Closes-Bug: #1651117
This commit is contained in:
parent
339ab40ee6
commit
b2fd8ba869
@ -18,6 +18,10 @@ Create new volume type
|
|||||||
[--property <key=value> [...] ]
|
[--property <key=value> [...] ]
|
||||||
[--project <project>]
|
[--project <project>]
|
||||||
[--project-domain <project-domain>]
|
[--project-domain <project-domain>]
|
||||||
|
[--encryption-provider <provider>]
|
||||||
|
[--encryption-cipher <cipher>]
|
||||||
|
[--encryption-key-size <key-size>]
|
||||||
|
[--encryption-control-location <control-location>]
|
||||||
<name>
|
<name>
|
||||||
|
|
||||||
.. option:: --description <description>
|
.. option:: --description <description>
|
||||||
@ -56,6 +60,34 @@ Create new volume type
|
|||||||
|
|
||||||
*Volume version 2 only*
|
*Volume version 2 only*
|
||||||
|
|
||||||
|
.. option:: --encryption-provider <provider>
|
||||||
|
|
||||||
|
Set the class that provides encryption support for this volume type
|
||||||
|
(e.g "LuksEncryptor") (admin only)
|
||||||
|
|
||||||
|
This option is required when setting encryption type of a volume.
|
||||||
|
Consider using other encryption options such as: :option:`--encryption-cipher`,
|
||||||
|
:option:`--encryption-key-size` and :option:`--encryption-control-location`
|
||||||
|
|
||||||
|
.. option:: --encryption-cipher <cipher>
|
||||||
|
|
||||||
|
Set the encryption algorithm or mode for this volume type
|
||||||
|
(e.g "aes-xts-plain64") (admin only)
|
||||||
|
|
||||||
|
.. option:: --encryption-key-size <key-size>
|
||||||
|
|
||||||
|
Set the size of the encryption key of this volume type
|
||||||
|
(e.g "128" or "256") (admin only)
|
||||||
|
|
||||||
|
.. option:: --encryption-control-location <control-location>
|
||||||
|
|
||||||
|
Set the notional service where the encryption is performed
|
||||||
|
("front-end" or "back-end") (admin only)
|
||||||
|
|
||||||
|
The default value for this option is "front-end" when setting encryption type of
|
||||||
|
a volume. Consider using other encryption options such as: :option:`--encryption-cipher`,
|
||||||
|
:option:`--encryption-key-size` and :option:`--encryption-provider`
|
||||||
|
|
||||||
.. _volume_type_create-name:
|
.. _volume_type_create-name:
|
||||||
.. describe:: <name>
|
.. describe:: <name>
|
||||||
|
|
||||||
@ -88,6 +120,7 @@ List volume types
|
|||||||
openstack volume type list
|
openstack volume type list
|
||||||
[--long]
|
[--long]
|
||||||
[--default | --public | --private]
|
[--default | --public | --private]
|
||||||
|
[--encryption-type]
|
||||||
|
|
||||||
.. option:: --long
|
.. option:: --long
|
||||||
|
|
||||||
@ -111,6 +144,10 @@ List volume types
|
|||||||
|
|
||||||
*Volume version 2 only*
|
*Volume version 2 only*
|
||||||
|
|
||||||
|
.. option:: --encryption-type
|
||||||
|
|
||||||
|
Display encryption information for each volume type (admin only)
|
||||||
|
|
||||||
volume type set
|
volume type set
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
@ -125,6 +162,10 @@ Set volume type properties
|
|||||||
[--property <key=value> [...] ]
|
[--property <key=value> [...] ]
|
||||||
[--project <project>]
|
[--project <project>]
|
||||||
[--project-domain <project-domain>]
|
[--project-domain <project-domain>]
|
||||||
|
[--encryption-provider <provider>]
|
||||||
|
[--encryption-cipher <cipher>]
|
||||||
|
[--encryption-key-size <key-size>]
|
||||||
|
[--encryption-control-location <control-location>]
|
||||||
<volume-type>
|
<volume-type>
|
||||||
|
|
||||||
.. option:: --name <name>
|
.. option:: --name <name>
|
||||||
@ -154,6 +195,34 @@ Set volume type properties
|
|||||||
|
|
||||||
Set a property on this volume type (repeat option to set multiple properties)
|
Set a property on this volume type (repeat option to set multiple properties)
|
||||||
|
|
||||||
|
.. option:: --encryption-provider <provider>
|
||||||
|
|
||||||
|
Set the class that provides encryption support for this volume type
|
||||||
|
(e.g "LuksEncryptor") (admin only)
|
||||||
|
|
||||||
|
This option is required when setting encryption type of a volume for the first time.
|
||||||
|
Consider using other encryption options such as: :option:`--encryption-cipher`,
|
||||||
|
:option:`--encryption-key-size` and :option:`--encryption-control-location`
|
||||||
|
|
||||||
|
.. option:: --encryption-cipher <cipher>
|
||||||
|
|
||||||
|
Set the encryption algorithm or mode for this volume type
|
||||||
|
(e.g "aes-xts-plain64") (admin only)
|
||||||
|
|
||||||
|
.. option:: --encryption-key-size <key-size>
|
||||||
|
|
||||||
|
Set the size of the encryption key of this volume type
|
||||||
|
(e.g "128" or "256") (admin only)
|
||||||
|
|
||||||
|
.. option:: --encryption-control-location <control-location>
|
||||||
|
|
||||||
|
Set the notional service where the encryption is performed
|
||||||
|
("front-end" or "back-end") (admin only)
|
||||||
|
|
||||||
|
The default value for this option is "front-end" when setting encryption type of
|
||||||
|
a volume for the first time. Consider using other encryption options such as:
|
||||||
|
:option:`--encryption-cipher`, :option:`--encryption-key-size` and :option:`--encryption-provider`
|
||||||
|
|
||||||
.. _volume_type_set-volume-type:
|
.. _volume_type_set-volume-type:
|
||||||
.. describe:: <volume-type>
|
.. describe:: <volume-type>
|
||||||
|
|
||||||
@ -168,8 +237,13 @@ Display volume type details
|
|||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
openstack volume type show
|
openstack volume type show
|
||||||
|
[--encryption-type]
|
||||||
<volume-type>
|
<volume-type>
|
||||||
|
|
||||||
|
.. option:: --encryption-type
|
||||||
|
|
||||||
|
Display encryption information of this volume type (admin only)
|
||||||
|
|
||||||
.. _volume_type_show-volume-type:
|
.. _volume_type_show-volume-type:
|
||||||
.. describe:: <volume-type>
|
.. describe:: <volume-type>
|
||||||
|
|
||||||
@ -187,6 +261,7 @@ Unset volume type properties
|
|||||||
[--property <key> [...] ]
|
[--property <key> [...] ]
|
||||||
[--project <project>]
|
[--project <project>]
|
||||||
[--project-domain <project-domain>]
|
[--project-domain <project-domain>]
|
||||||
|
[--encryption-type]
|
||||||
<volume-type>
|
<volume-type>
|
||||||
|
|
||||||
.. option:: --property <key>
|
.. option:: --property <key>
|
||||||
@ -204,6 +279,12 @@ Unset volume type properties
|
|||||||
Domain the project belongs to (name or ID).
|
Domain the project belongs to (name or ID).
|
||||||
This can be used in case collisions between project names exist.
|
This can be used in case collisions between project names exist.
|
||||||
|
|
||||||
|
*Volume version 2 only*
|
||||||
|
|
||||||
|
.. option:: --encryption-type
|
||||||
|
|
||||||
|
Remove the encryption type for this volume type (admin only)
|
||||||
|
|
||||||
.. _volume_type_unset-volume-type:
|
.. _volume_type_unset-volume-type:
|
||||||
.. describe:: <volume-type>
|
.. describe:: <volume-type>
|
||||||
|
|
||||||
|
@ -87,3 +87,74 @@ class VolumeTypeTests(common.BaseVolumeTests):
|
|||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
raw_output = self.openstack(cmd)
|
raw_output = self.openstack(cmd)
|
||||||
self.assertOutput('', raw_output)
|
self.assertOutput('', raw_output)
|
||||||
|
|
||||||
|
# NOTE: Add some basic funtional tests with the old format to
|
||||||
|
# make sure the command works properly, need to change
|
||||||
|
# these to new test format when beef up all tests for
|
||||||
|
# volume tye commands.
|
||||||
|
def test_encryption_type(self):
|
||||||
|
encryption_type = uuid.uuid4().hex
|
||||||
|
# test create new encryption type
|
||||||
|
opts = self.get_opts(['encryption'])
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type create '
|
||||||
|
'--encryption-provider LuksEncryptor '
|
||||||
|
'--encryption-cipher aes-xts-plain64 '
|
||||||
|
'--encryption-key-size 128 '
|
||||||
|
'--encryption-control-location front-end ' +
|
||||||
|
encryption_type + opts)
|
||||||
|
expected = ["provider='LuksEncryptor'",
|
||||||
|
"cipher='aes-xts-plain64'",
|
||||||
|
"key_size='128'",
|
||||||
|
"control_location='front-end'"]
|
||||||
|
for attr in expected:
|
||||||
|
self.assertIn(attr, raw_output)
|
||||||
|
# test show encryption type
|
||||||
|
opts = self.get_opts(['encryption'])
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type show --encryption-type ' + encryption_type + opts)
|
||||||
|
expected = ["provider='LuksEncryptor'",
|
||||||
|
"cipher='aes-xts-plain64'",
|
||||||
|
"key_size='128'",
|
||||||
|
"control_location='front-end'"]
|
||||||
|
for attr in expected:
|
||||||
|
self.assertIn(attr, raw_output)
|
||||||
|
# test list encryption type
|
||||||
|
opts = self.get_opts(['Encryption'])
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type list --encryption-type ' + opts)
|
||||||
|
expected = ["provider='LuksEncryptor'",
|
||||||
|
"cipher='aes-xts-plain64'",
|
||||||
|
"key_size='128'",
|
||||||
|
"control_location='front-end'"]
|
||||||
|
for attr in expected:
|
||||||
|
self.assertIn(attr, raw_output)
|
||||||
|
# test set new encryption type
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type set '
|
||||||
|
'--encryption-provider LuksEncryptor '
|
||||||
|
'--encryption-cipher aes-xts-plain64 '
|
||||||
|
'--encryption-key-size 128 '
|
||||||
|
'--encryption-control-location front-end ' +
|
||||||
|
self.NAME)
|
||||||
|
self.assertEqual('', raw_output)
|
||||||
|
opts = self.get_opts(['encryption'])
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type show --encryption-type ' + self.NAME + opts)
|
||||||
|
expected = ["provider='LuksEncryptor'",
|
||||||
|
"cipher='aes-xts-plain64'",
|
||||||
|
"key_size='128'",
|
||||||
|
"control_location='front-end'"]
|
||||||
|
for attr in expected:
|
||||||
|
self.assertIn(attr, raw_output)
|
||||||
|
# test unset encryption type
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type unset --encryption-type ' + self.NAME)
|
||||||
|
self.assertEqual('', raw_output)
|
||||||
|
opts = self.get_opts(['encryption'])
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type show --encryption-type ' + self.NAME + opts)
|
||||||
|
self.assertEqual('\n', raw_output)
|
||||||
|
# test delete encryption type
|
||||||
|
raw_output = self.openstack('volume type delete ' + encryption_type)
|
||||||
|
self.assertEqual('', raw_output)
|
||||||
|
@ -102,3 +102,90 @@ class VolumeTypeTests(common.BaseVolumeTests):
|
|||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
raw_output = self.openstack(cmd)
|
raw_output = self.openstack(cmd)
|
||||||
self.assertOutput('', raw_output)
|
self.assertOutput('', raw_output)
|
||||||
|
|
||||||
|
# NOTE: Add some basic funtional tests with the old format to
|
||||||
|
# make sure the command works properly, need to change
|
||||||
|
# these to new test format when beef up all tests for
|
||||||
|
# volume tye commands.
|
||||||
|
def test_encryption_type(self):
|
||||||
|
encryption_type = uuid.uuid4().hex
|
||||||
|
# test create new encryption type
|
||||||
|
opts = self.get_opts(['encryption'])
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type create '
|
||||||
|
'--encryption-provider LuksEncryptor '
|
||||||
|
'--encryption-cipher aes-xts-plain64 '
|
||||||
|
'--encryption-key-size 128 '
|
||||||
|
'--encryption-control-location front-end ' +
|
||||||
|
encryption_type + opts)
|
||||||
|
expected = ["provider='LuksEncryptor'",
|
||||||
|
"cipher='aes-xts-plain64'",
|
||||||
|
"key_size='128'",
|
||||||
|
"control_location='front-end'"]
|
||||||
|
for attr in expected:
|
||||||
|
self.assertIn(attr, raw_output)
|
||||||
|
# test show encryption type
|
||||||
|
opts = self.get_opts(['encryption'])
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type show --encryption-type ' + encryption_type + opts)
|
||||||
|
expected = ["provider='LuksEncryptor'",
|
||||||
|
"cipher='aes-xts-plain64'",
|
||||||
|
"key_size='128'",
|
||||||
|
"control_location='front-end'"]
|
||||||
|
for attr in expected:
|
||||||
|
self.assertIn(attr, raw_output)
|
||||||
|
# test list encryption type
|
||||||
|
opts = self.get_opts(['Encryption'])
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type list --encryption-type ' + opts)
|
||||||
|
expected = ["provider='LuksEncryptor'",
|
||||||
|
"cipher='aes-xts-plain64'",
|
||||||
|
"key_size='128'",
|
||||||
|
"control_location='front-end'"]
|
||||||
|
for attr in expected:
|
||||||
|
self.assertIn(attr, raw_output)
|
||||||
|
# test set existing encryption type
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type set '
|
||||||
|
'--encryption-key-size 256 '
|
||||||
|
'--encryption-control-location back-end ' +
|
||||||
|
encryption_type)
|
||||||
|
self.assertEqual('', raw_output)
|
||||||
|
opts = self.get_opts(['encryption'])
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type show --encryption-type ' + encryption_type + opts)
|
||||||
|
expected = ["provider='LuksEncryptor'",
|
||||||
|
"cipher='aes-xts-plain64'",
|
||||||
|
"key_size='256'",
|
||||||
|
"control_location='back-end'"]
|
||||||
|
for attr in expected:
|
||||||
|
self.assertIn(attr, raw_output)
|
||||||
|
# test set new encryption type
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type set '
|
||||||
|
'--encryption-provider LuksEncryptor '
|
||||||
|
'--encryption-cipher aes-xts-plain64 '
|
||||||
|
'--encryption-key-size 128 '
|
||||||
|
'--encryption-control-location front-end ' +
|
||||||
|
self.NAME)
|
||||||
|
self.assertEqual('', raw_output)
|
||||||
|
opts = self.get_opts(['encryption'])
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type show --encryption-type ' + self.NAME + opts)
|
||||||
|
expected = ["provider='LuksEncryptor'",
|
||||||
|
"cipher='aes-xts-plain64'",
|
||||||
|
"key_size='128'",
|
||||||
|
"control_location='front-end'"]
|
||||||
|
for attr in expected:
|
||||||
|
self.assertIn(attr, raw_output)
|
||||||
|
# test unset encryption type
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type unset --encryption-type ' + self.NAME)
|
||||||
|
self.assertEqual('', raw_output)
|
||||||
|
opts = self.get_opts(['encryption'])
|
||||||
|
raw_output = self.openstack(
|
||||||
|
'volume type show --encryption-type ' + self.NAME + opts)
|
||||||
|
self.assertEqual('\n', raw_output)
|
||||||
|
# test delete encryption type
|
||||||
|
raw_output = self.openstack('volume type delete ' + encryption_type)
|
||||||
|
self.assertEqual('', raw_output)
|
||||||
|
@ -364,6 +364,9 @@ class FakeVolumev1Client(object):
|
|||||||
self.qos_specs.resource_class = fakes.FakeResource(None, {})
|
self.qos_specs.resource_class = fakes.FakeResource(None, {})
|
||||||
self.volume_types = mock.Mock()
|
self.volume_types = mock.Mock()
|
||||||
self.volume_types.resource_class = fakes.FakeResource(None, {})
|
self.volume_types.resource_class = fakes.FakeResource(None, {})
|
||||||
|
self.volume_encryption_types = mock.Mock()
|
||||||
|
self.volume_encryption_types.resource_class = (
|
||||||
|
fakes.FakeResource(None, {}))
|
||||||
self.transfers = mock.Mock()
|
self.transfers = mock.Mock()
|
||||||
self.transfers.resource_class = fakes.FakeResource(None, {})
|
self.transfers.resource_class = fakes.FakeResource(None, {})
|
||||||
self.volume_snapshots = mock.Mock()
|
self.volume_snapshots = mock.Mock()
|
||||||
@ -470,6 +473,34 @@ class FakeType(object):
|
|||||||
|
|
||||||
return mock.Mock(side_effect=types)
|
return mock.Mock(side_effect=types)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_one_encryption_type(attrs=None):
|
||||||
|
"""Create a fake encryption type.
|
||||||
|
|
||||||
|
:param Dictionary attrs:
|
||||||
|
A dictionary with all attributes
|
||||||
|
:return:
|
||||||
|
A FakeResource object with volume_type_id etc.
|
||||||
|
"""
|
||||||
|
attrs = attrs or {}
|
||||||
|
|
||||||
|
# Set default attributes.
|
||||||
|
encryption_info = {
|
||||||
|
"volume_type_id": 'type-id-' + uuid.uuid4().hex,
|
||||||
|
'provider': 'LuksEncryptor',
|
||||||
|
'cipher': None,
|
||||||
|
'key_size': None,
|
||||||
|
'control_location': 'front-end',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Overwrite default attributes.
|
||||||
|
encryption_info.update(attrs)
|
||||||
|
|
||||||
|
encryption_type = fakes.FakeResource(
|
||||||
|
info=copy.deepcopy(encryption_info),
|
||||||
|
loaded=True)
|
||||||
|
return encryption_type
|
||||||
|
|
||||||
|
|
||||||
class FakeSnapshot(object):
|
class FakeSnapshot(object):
|
||||||
"""Fake one or more snapshot."""
|
"""Fake one or more snapshot."""
|
||||||
|
@ -31,6 +31,10 @@ class TestType(volume_fakes.TestVolumev1):
|
|||||||
self.types_mock = self.app.client_manager.volume.volume_types
|
self.types_mock = self.app.client_manager.volume.volume_types
|
||||||
self.types_mock.reset_mock()
|
self.types_mock.reset_mock()
|
||||||
|
|
||||||
|
self.encryption_types_mock = (
|
||||||
|
self.app.client_manager.volume.volume_encryption_types)
|
||||||
|
self.encryption_types_mock.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
class TestTypeCreate(TestType):
|
class TestTypeCreate(TestType):
|
||||||
|
|
||||||
@ -75,6 +79,67 @@ class TestTypeCreate(TestType):
|
|||||||
self.assertEqual(self.columns, columns)
|
self.assertEqual(self.columns, columns)
|
||||||
self.assertEqual(self.data, data)
|
self.assertEqual(self.data, data)
|
||||||
|
|
||||||
|
def test_type_create_with_encryption(self):
|
||||||
|
encryption_info = {
|
||||||
|
'provider': 'LuksEncryptor',
|
||||||
|
'cipher': 'aes-xts-plain64',
|
||||||
|
'key_size': '128',
|
||||||
|
'control_location': 'front-end',
|
||||||
|
}
|
||||||
|
encryption_type = volume_fakes.FakeType.create_one_encryption_type(
|
||||||
|
attrs=encryption_info
|
||||||
|
)
|
||||||
|
self.new_volume_type = volume_fakes.FakeType.create_one_type(
|
||||||
|
attrs={'encryption': encryption_info})
|
||||||
|
self.types_mock.create.return_value = self.new_volume_type
|
||||||
|
self.encryption_types_mock.create.return_value = encryption_type
|
||||||
|
encryption_columns = (
|
||||||
|
'description',
|
||||||
|
'encryption',
|
||||||
|
'id',
|
||||||
|
'is_public',
|
||||||
|
'name',
|
||||||
|
)
|
||||||
|
encryption_data = (
|
||||||
|
self.new_volume_type.description,
|
||||||
|
utils.format_dict(encryption_info),
|
||||||
|
self.new_volume_type.id,
|
||||||
|
True,
|
||||||
|
self.new_volume_type.name,
|
||||||
|
)
|
||||||
|
arglist = [
|
||||||
|
'--encryption-provider', 'LuksEncryptor',
|
||||||
|
'--encryption-cipher', 'aes-xts-plain64',
|
||||||
|
'--encryption-key-size', '128',
|
||||||
|
'--encryption-control-location', 'front-end',
|
||||||
|
self.new_volume_type.name,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('encryption_provider', 'LuksEncryptor'),
|
||||||
|
('encryption_cipher', 'aes-xts-plain64'),
|
||||||
|
('encryption_key_size', 128),
|
||||||
|
('encryption_control_location', 'front-end'),
|
||||||
|
('name', self.new_volume_type.name),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
self.types_mock.create.assert_called_with(
|
||||||
|
self.new_volume_type.name,
|
||||||
|
)
|
||||||
|
body = {
|
||||||
|
'provider': 'LuksEncryptor',
|
||||||
|
'cipher': 'aes-xts-plain64',
|
||||||
|
'key_size': 128,
|
||||||
|
'control_location': 'front-end',
|
||||||
|
}
|
||||||
|
self.encryption_types_mock.create.assert_called_with(
|
||||||
|
self.new_volume_type,
|
||||||
|
body,
|
||||||
|
)
|
||||||
|
self.assertEqual(encryption_columns, columns)
|
||||||
|
self.assertEqual(encryption_data, data)
|
||||||
|
|
||||||
|
|
||||||
class TestTypeDelete(TestType):
|
class TestTypeDelete(TestType):
|
||||||
|
|
||||||
@ -156,17 +221,17 @@ class TestTypeList(TestType):
|
|||||||
|
|
||||||
volume_types = volume_fakes.FakeType.create_types()
|
volume_types = volume_fakes.FakeType.create_types()
|
||||||
|
|
||||||
columns = (
|
columns = [
|
||||||
"ID",
|
"ID",
|
||||||
"Name",
|
"Name",
|
||||||
"Is Public",
|
"Is Public",
|
||||||
)
|
]
|
||||||
columns_long = (
|
columns_long = [
|
||||||
"ID",
|
"ID",
|
||||||
"Name",
|
"Name",
|
||||||
"Is Public",
|
"Is Public",
|
||||||
"Properties"
|
"Properties"
|
||||||
)
|
]
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
for t in volume_types:
|
for t in volume_types:
|
||||||
@ -188,6 +253,8 @@ class TestTypeList(TestType):
|
|||||||
super(TestTypeList, self).setUp()
|
super(TestTypeList, self).setUp()
|
||||||
|
|
||||||
self.types_mock.list.return_value = self.volume_types
|
self.types_mock.list.return_value = self.volume_types
|
||||||
|
self.encryption_types_mock.create.return_value = None
|
||||||
|
self.encryption_types_mock.update.return_value = None
|
||||||
# get the command to test
|
# get the command to test
|
||||||
self.cmd = volume_type.ListVolumeType(self.app, None)
|
self.cmd = volume_type.ListVolumeType(self.app, None)
|
||||||
|
|
||||||
@ -195,6 +262,7 @@ class TestTypeList(TestType):
|
|||||||
arglist = []
|
arglist = []
|
||||||
verifylist = [
|
verifylist = [
|
||||||
("long", False),
|
("long", False),
|
||||||
|
("encryption_type", False),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
@ -217,6 +285,47 @@ class TestTypeList(TestType):
|
|||||||
self.assertEqual(self.columns_long, columns)
|
self.assertEqual(self.columns_long, columns)
|
||||||
self.assertEqual(self.data_long, list(data))
|
self.assertEqual(self.data_long, list(data))
|
||||||
|
|
||||||
|
def test_type_list_with_encryption(self):
|
||||||
|
encryption_type = volume_fakes.FakeType.create_one_encryption_type(
|
||||||
|
attrs={'volume_type_id': self.volume_types[0].id})
|
||||||
|
encryption_info = {
|
||||||
|
'provider': 'LuksEncryptor',
|
||||||
|
'cipher': None,
|
||||||
|
'key_size': None,
|
||||||
|
'control_location': 'front-end',
|
||||||
|
}
|
||||||
|
encryption_columns = self.columns + [
|
||||||
|
"Encryption",
|
||||||
|
]
|
||||||
|
encryption_data = []
|
||||||
|
encryption_data.append((
|
||||||
|
self.volume_types[0].id,
|
||||||
|
self.volume_types[0].name,
|
||||||
|
self.volume_types[0].is_public,
|
||||||
|
utils.format_dict(encryption_info),
|
||||||
|
))
|
||||||
|
encryption_data.append((
|
||||||
|
self.volume_types[1].id,
|
||||||
|
self.volume_types[1].name,
|
||||||
|
self.volume_types[1].is_public,
|
||||||
|
'-',
|
||||||
|
))
|
||||||
|
|
||||||
|
self.encryption_types_mock.list.return_value = [encryption_type]
|
||||||
|
arglist = [
|
||||||
|
"--encryption-type",
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("encryption_type", True),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
self.encryption_types_mock.list.assert_called_once_with()
|
||||||
|
self.types_mock.list.assert_called_once_with()
|
||||||
|
self.assertEqual(encryption_columns, columns)
|
||||||
|
self.assertEqual(encryption_data, list(data))
|
||||||
|
|
||||||
|
|
||||||
class TestTypeSet(TestType):
|
class TestTypeSet(TestType):
|
||||||
|
|
||||||
@ -260,6 +369,60 @@ class TestTypeSet(TestType):
|
|||||||
{'myprop': 'myvalue'})
|
{'myprop': 'myvalue'})
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_type_set_new_encryption(self):
|
||||||
|
arglist = [
|
||||||
|
'--encryption-provider', 'LuksEncryptor',
|
||||||
|
'--encryption-cipher', 'aes-xts-plain64',
|
||||||
|
'--encryption-key-size', '128',
|
||||||
|
'--encryption-control-location', 'front-end',
|
||||||
|
self.volume_type.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('encryption_provider', 'LuksEncryptor'),
|
||||||
|
('encryption_cipher', 'aes-xts-plain64'),
|
||||||
|
('encryption_key_size', 128),
|
||||||
|
('encryption_control_location', 'front-end'),
|
||||||
|
('volume_type', self.volume_type.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
body = {
|
||||||
|
'provider': 'LuksEncryptor',
|
||||||
|
'cipher': 'aes-xts-plain64',
|
||||||
|
'key_size': 128,
|
||||||
|
'control_location': 'front-end',
|
||||||
|
}
|
||||||
|
self.encryption_types_mock.create.assert_called_with(
|
||||||
|
self.volume_type,
|
||||||
|
body,
|
||||||
|
)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_type_set_new_encryption_without_provider(self):
|
||||||
|
arglist = [
|
||||||
|
'--encryption-cipher', 'aes-xts-plain64',
|
||||||
|
'--encryption-key-size', '128',
|
||||||
|
'--encryption-control-location', 'front-end',
|
||||||
|
self.volume_type.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('encryption_cipher', 'aes-xts-plain64'),
|
||||||
|
('encryption_key_size', 128),
|
||||||
|
('encryption_control_location', 'front-end'),
|
||||||
|
('volume_type', self.volume_type.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
try:
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
self.fail('CommandError should be raised.')
|
||||||
|
except exceptions.CommandError as e:
|
||||||
|
self.assertEqual("Command Failed: One or more of"
|
||||||
|
" the operations failed",
|
||||||
|
str(e))
|
||||||
|
self.encryption_types_mock.create.assert_not_called()
|
||||||
|
self.encryption_types_mock.update.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
class TestTypeShow(TestType):
|
class TestTypeShow(TestType):
|
||||||
|
|
||||||
@ -293,7 +456,8 @@ class TestTypeShow(TestType):
|
|||||||
self.volume_type.id
|
self.volume_type.id
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
("volume_type", self.volume_type.id)
|
("volume_type", self.volume_type.id),
|
||||||
|
("encryption_type", False),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
@ -303,6 +467,50 @@ class TestTypeShow(TestType):
|
|||||||
self.assertEqual(self.columns, columns)
|
self.assertEqual(self.columns, columns)
|
||||||
self.assertEqual(self.data, data)
|
self.assertEqual(self.data, data)
|
||||||
|
|
||||||
|
def test_type_show_with_encryption(self):
|
||||||
|
encryption_type = volume_fakes.FakeType.create_one_encryption_type()
|
||||||
|
encryption_info = {
|
||||||
|
'provider': 'LuksEncryptor',
|
||||||
|
'cipher': None,
|
||||||
|
'key_size': None,
|
||||||
|
'control_location': 'front-end',
|
||||||
|
}
|
||||||
|
self.volume_type = volume_fakes.FakeType.create_one_type(
|
||||||
|
attrs={'encryption': encryption_info})
|
||||||
|
self.types_mock.get.return_value = self.volume_type
|
||||||
|
self.encryption_types_mock.get.return_value = encryption_type
|
||||||
|
encryption_columns = (
|
||||||
|
'description',
|
||||||
|
'encryption',
|
||||||
|
'id',
|
||||||
|
'is_public',
|
||||||
|
'name',
|
||||||
|
'properties',
|
||||||
|
)
|
||||||
|
encryption_data = (
|
||||||
|
self.volume_type.description,
|
||||||
|
utils.format_dict(encryption_info),
|
||||||
|
self.volume_type.id,
|
||||||
|
True,
|
||||||
|
self.volume_type.name,
|
||||||
|
utils.format_dict(self.volume_type.extra_specs)
|
||||||
|
)
|
||||||
|
arglist = [
|
||||||
|
'--encryption-type',
|
||||||
|
self.volume_type.id
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('encryption_type', True),
|
||||||
|
("volume_type", self.volume_type.id)
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
self.types_mock.get.assert_called_with(self.volume_type.id)
|
||||||
|
self.encryption_types_mock.get.assert_called_with(self.volume_type.id)
|
||||||
|
self.assertEqual(encryption_columns, columns)
|
||||||
|
self.assertEqual(encryption_data, data)
|
||||||
|
|
||||||
|
|
||||||
class TestTypeUnset(TestType):
|
class TestTypeUnset(TestType):
|
||||||
|
|
||||||
@ -317,13 +525,14 @@ class TestTypeUnset(TestType):
|
|||||||
# Get the command object to test
|
# Get the command object to test
|
||||||
self.cmd = volume_type.UnsetVolumeType(self.app, None)
|
self.cmd = volume_type.UnsetVolumeType(self.app, None)
|
||||||
|
|
||||||
def test_type_unset(self):
|
def test_type_unset_property(self):
|
||||||
arglist = [
|
arglist = [
|
||||||
'--property', 'property',
|
'--property', 'property',
|
||||||
'--property', 'multi_property',
|
'--property', 'multi_property',
|
||||||
self.volume_type.id,
|
self.volume_type.id,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
|
('encryption_type', False),
|
||||||
('property', ['property', 'multi_property']),
|
('property', ['property', 'multi_property']),
|
||||||
('volume_type', self.volume_type.id),
|
('volume_type', self.volume_type.id),
|
||||||
]
|
]
|
||||||
@ -333,6 +542,7 @@ class TestTypeUnset(TestType):
|
|||||||
result = self.cmd.take_action(parsed_args)
|
result = self.cmd.take_action(parsed_args)
|
||||||
self.volume_type.unset_keys.assert_called_once_with(
|
self.volume_type.unset_keys.assert_called_once_with(
|
||||||
['property', 'multi_property'])
|
['property', 'multi_property'])
|
||||||
|
self.encryption_types_mock.delete.assert_not_called()
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
|
||||||
def test_type_unset_failed_with_missing_volume_type_argument(self):
|
def test_type_unset_failed_with_missing_volume_type_argument(self):
|
||||||
@ -362,3 +572,18 @@ class TestTypeUnset(TestType):
|
|||||||
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
result = self.cmd.take_action(parsed_args)
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_type_unset_encryption_type(self):
|
||||||
|
arglist = [
|
||||||
|
'--encryption-type',
|
||||||
|
self.volume_type.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('encryption_type', True),
|
||||||
|
('volume_type', self.volume_type.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.encryption_types_mock.delete.assert_called_with(self.volume_type)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
@ -208,6 +208,9 @@ class FakeVolumeClient(object):
|
|||||||
self.volume_types.resource_class = fakes.FakeResource(None, {})
|
self.volume_types.resource_class = fakes.FakeResource(None, {})
|
||||||
self.volume_type_access = mock.Mock()
|
self.volume_type_access = mock.Mock()
|
||||||
self.volume_type_access.resource_class = fakes.FakeResource(None, {})
|
self.volume_type_access.resource_class = fakes.FakeResource(None, {})
|
||||||
|
self.volume_encryption_types = mock.Mock()
|
||||||
|
self.volume_encryption_types.resource_class = (
|
||||||
|
fakes.FakeResource(None, {}))
|
||||||
self.restores = mock.Mock()
|
self.restores = mock.Mock()
|
||||||
self.restores.resource_class = fakes.FakeResource(None, {})
|
self.restores.resource_class = fakes.FakeResource(None, {})
|
||||||
self.qos_specs = mock.Mock()
|
self.qos_specs = mock.Mock()
|
||||||
@ -923,3 +926,31 @@ class FakeType(object):
|
|||||||
types = FakeType.create_types(count)
|
types = FakeType.create_types(count)
|
||||||
|
|
||||||
return mock.Mock(side_effect=types)
|
return mock.Mock(side_effect=types)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_one_encryption_type(attrs=None):
|
||||||
|
"""Create a fake encryption type.
|
||||||
|
|
||||||
|
:param Dictionary attrs:
|
||||||
|
A dictionary with all attributes
|
||||||
|
:return:
|
||||||
|
A FakeResource object with volume_type_id etc.
|
||||||
|
"""
|
||||||
|
attrs = attrs or {}
|
||||||
|
|
||||||
|
# Set default attributes.
|
||||||
|
encryption_info = {
|
||||||
|
"volume_type_id": 'type-id-' + uuid.uuid4().hex,
|
||||||
|
'provider': 'LuksEncryptor',
|
||||||
|
'cipher': None,
|
||||||
|
'key_size': None,
|
||||||
|
'control_location': 'front-end',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Overwrite default attributes.
|
||||||
|
encryption_info.update(attrs)
|
||||||
|
|
||||||
|
encryption_type = fakes.FakeResource(
|
||||||
|
info=copy.deepcopy(encryption_info),
|
||||||
|
loaded=True)
|
||||||
|
return encryption_type
|
||||||
|
@ -36,6 +36,10 @@ class TestType(volume_fakes.TestVolume):
|
|||||||
self.app.client_manager.volume.volume_type_access)
|
self.app.client_manager.volume.volume_type_access)
|
||||||
self.types_access_mock.reset_mock()
|
self.types_access_mock.reset_mock()
|
||||||
|
|
||||||
|
self.encryption_types_mock = (
|
||||||
|
self.app.client_manager.volume.volume_encryption_types)
|
||||||
|
self.encryption_types_mock.reset_mock()
|
||||||
|
|
||||||
self.projects_mock = self.app.client_manager.identity.projects
|
self.projects_mock = self.app.client_manager.identity.projects
|
||||||
self.projects_mock.reset_mock()
|
self.projects_mock.reset_mock()
|
||||||
|
|
||||||
@ -131,6 +135,68 @@ class TestTypeCreate(TestType):
|
|||||||
self.cmd.take_action,
|
self.cmd.take_action,
|
||||||
parsed_args)
|
parsed_args)
|
||||||
|
|
||||||
|
def test_type_create_with_encryption(self):
|
||||||
|
encryption_info = {
|
||||||
|
'provider': 'LuksEncryptor',
|
||||||
|
'cipher': 'aes-xts-plain64',
|
||||||
|
'key_size': '128',
|
||||||
|
'control_location': 'front-end',
|
||||||
|
}
|
||||||
|
encryption_type = volume_fakes.FakeType.create_one_encryption_type(
|
||||||
|
attrs=encryption_info
|
||||||
|
)
|
||||||
|
self.new_volume_type = volume_fakes.FakeType.create_one_type(
|
||||||
|
attrs={'encryption': encryption_info})
|
||||||
|
self.types_mock.create.return_value = self.new_volume_type
|
||||||
|
self.encryption_types_mock.create.return_value = encryption_type
|
||||||
|
encryption_columns = (
|
||||||
|
'description',
|
||||||
|
'encryption',
|
||||||
|
'id',
|
||||||
|
'is_public',
|
||||||
|
'name',
|
||||||
|
)
|
||||||
|
encryption_data = (
|
||||||
|
self.new_volume_type.description,
|
||||||
|
utils.format_dict(encryption_info),
|
||||||
|
self.new_volume_type.id,
|
||||||
|
True,
|
||||||
|
self.new_volume_type.name,
|
||||||
|
)
|
||||||
|
arglist = [
|
||||||
|
'--encryption-provider', 'LuksEncryptor',
|
||||||
|
'--encryption-cipher', 'aes-xts-plain64',
|
||||||
|
'--encryption-key-size', '128',
|
||||||
|
'--encryption-control-location', 'front-end',
|
||||||
|
self.new_volume_type.name,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('encryption_provider', 'LuksEncryptor'),
|
||||||
|
('encryption_cipher', 'aes-xts-plain64'),
|
||||||
|
('encryption_key_size', 128),
|
||||||
|
('encryption_control_location', 'front-end'),
|
||||||
|
('name', self.new_volume_type.name),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
self.types_mock.create.assert_called_with(
|
||||||
|
self.new_volume_type.name,
|
||||||
|
description=None,
|
||||||
|
)
|
||||||
|
body = {
|
||||||
|
'provider': 'LuksEncryptor',
|
||||||
|
'cipher': 'aes-xts-plain64',
|
||||||
|
'key_size': 128,
|
||||||
|
'control_location': 'front-end',
|
||||||
|
}
|
||||||
|
self.encryption_types_mock.create.assert_called_with(
|
||||||
|
self.new_volume_type,
|
||||||
|
body,
|
||||||
|
)
|
||||||
|
self.assertEqual(encryption_columns, columns)
|
||||||
|
self.assertEqual(encryption_data, data)
|
||||||
|
|
||||||
|
|
||||||
class TestTypeDelete(TestType):
|
class TestTypeDelete(TestType):
|
||||||
|
|
||||||
@ -305,6 +371,7 @@ class TestTypeList(TestType):
|
|||||||
"--default",
|
"--default",
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
|
("encryption_type", False),
|
||||||
("long", False),
|
("long", False),
|
||||||
("private", False),
|
("private", False),
|
||||||
("public", False),
|
("public", False),
|
||||||
@ -317,6 +384,47 @@ class TestTypeList(TestType):
|
|||||||
self.assertEqual(self.columns, columns)
|
self.assertEqual(self.columns, columns)
|
||||||
self.assertEqual(self.data_with_default_type, list(data))
|
self.assertEqual(self.data_with_default_type, list(data))
|
||||||
|
|
||||||
|
def test_type_list_with_encryption(self):
|
||||||
|
encryption_type = volume_fakes.FakeType.create_one_encryption_type(
|
||||||
|
attrs={'volume_type_id': self.volume_types[0].id})
|
||||||
|
encryption_info = {
|
||||||
|
'provider': 'LuksEncryptor',
|
||||||
|
'cipher': None,
|
||||||
|
'key_size': None,
|
||||||
|
'control_location': 'front-end',
|
||||||
|
}
|
||||||
|
encryption_columns = self.columns + [
|
||||||
|
"Encryption",
|
||||||
|
]
|
||||||
|
encryption_data = []
|
||||||
|
encryption_data.append((
|
||||||
|
self.volume_types[0].id,
|
||||||
|
self.volume_types[0].name,
|
||||||
|
self.volume_types[0].is_public,
|
||||||
|
utils.format_dict(encryption_info),
|
||||||
|
))
|
||||||
|
encryption_data.append((
|
||||||
|
self.volume_types[1].id,
|
||||||
|
self.volume_types[1].name,
|
||||||
|
self.volume_types[1].is_public,
|
||||||
|
'-',
|
||||||
|
))
|
||||||
|
|
||||||
|
self.encryption_types_mock.list.return_value = [encryption_type]
|
||||||
|
arglist = [
|
||||||
|
"--encryption-type",
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("encryption_type", True),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
self.encryption_types_mock.list.assert_called_once_with()
|
||||||
|
self.types_mock.list.assert_called_once_with(is_public=None)
|
||||||
|
self.assertEqual(encryption_columns, columns)
|
||||||
|
self.assertEqual(encryption_data, list(data))
|
||||||
|
|
||||||
|
|
||||||
class TestTypeSet(TestType):
|
class TestTypeSet(TestType):
|
||||||
|
|
||||||
@ -331,6 +439,8 @@ class TestTypeSet(TestType):
|
|||||||
|
|
||||||
# Return a project
|
# Return a project
|
||||||
self.projects_mock.get.return_value = self.project
|
self.projects_mock.get.return_value = self.project
|
||||||
|
self.encryption_types_mock.create.return_value = None
|
||||||
|
self.encryption_types_mock.update.return_value = None
|
||||||
# Get the command object to test
|
# Get the command object to test
|
||||||
self.cmd = volume_type.SetVolumeType(self.app, None)
|
self.cmd = volume_type.SetVolumeType(self.app, None)
|
||||||
|
|
||||||
@ -454,6 +564,107 @@ class TestTypeSet(TestType):
|
|||||||
self.project.id,
|
self.project.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_type_set_new_encryption(self):
|
||||||
|
self.encryption_types_mock.update.side_effect = (
|
||||||
|
exceptions.NotFound('NotFound'))
|
||||||
|
arglist = [
|
||||||
|
'--encryption-provider', 'LuksEncryptor',
|
||||||
|
'--encryption-cipher', 'aes-xts-plain64',
|
||||||
|
'--encryption-key-size', '128',
|
||||||
|
'--encryption-control-location', 'front-end',
|
||||||
|
self.volume_type.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('encryption_provider', 'LuksEncryptor'),
|
||||||
|
('encryption_cipher', 'aes-xts-plain64'),
|
||||||
|
('encryption_key_size', 128),
|
||||||
|
('encryption_control_location', 'front-end'),
|
||||||
|
('volume_type', self.volume_type.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
body = {
|
||||||
|
'provider': 'LuksEncryptor',
|
||||||
|
'cipher': 'aes-xts-plain64',
|
||||||
|
'key_size': 128,
|
||||||
|
'control_location': 'front-end',
|
||||||
|
}
|
||||||
|
self.encryption_types_mock.update.assert_called_with(
|
||||||
|
self.volume_type,
|
||||||
|
body,
|
||||||
|
)
|
||||||
|
self.encryption_types_mock.create.assert_called_with(
|
||||||
|
self.volume_type,
|
||||||
|
body,
|
||||||
|
)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
@mock.patch.object(utils, 'find_resource')
|
||||||
|
def test_type_set_existing_encryption(self, mock_find):
|
||||||
|
mock_find.side_effect = [self.volume_type,
|
||||||
|
"existing_encryption_type"]
|
||||||
|
arglist = [
|
||||||
|
'--encryption-provider', 'LuksEncryptor',
|
||||||
|
'--encryption-cipher', 'aes-xts-plain64',
|
||||||
|
'--encryption-control-location', 'front-end',
|
||||||
|
self.volume_type.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('encryption_provider', 'LuksEncryptor'),
|
||||||
|
('encryption_cipher', 'aes-xts-plain64'),
|
||||||
|
('encryption_control_location', 'front-end'),
|
||||||
|
('volume_type', self.volume_type.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
body = {
|
||||||
|
'provider': 'LuksEncryptor',
|
||||||
|
'cipher': 'aes-xts-plain64',
|
||||||
|
'control_location': 'front-end',
|
||||||
|
}
|
||||||
|
self.encryption_types_mock.update.assert_called_with(
|
||||||
|
self.volume_type,
|
||||||
|
body,
|
||||||
|
)
|
||||||
|
self.encryption_types_mock.create.assert_not_called()
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
|
def test_type_set_new_encryption_without_provider(self):
|
||||||
|
self.encryption_types_mock.update.side_effect = (
|
||||||
|
exceptions.NotFound('NotFound'))
|
||||||
|
arglist = [
|
||||||
|
'--encryption-cipher', 'aes-xts-plain64',
|
||||||
|
'--encryption-key-size', '128',
|
||||||
|
'--encryption-control-location', 'front-end',
|
||||||
|
self.volume_type.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('encryption_cipher', 'aes-xts-plain64'),
|
||||||
|
('encryption_key_size', 128),
|
||||||
|
('encryption_control_location', 'front-end'),
|
||||||
|
('volume_type', self.volume_type.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
try:
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
self.fail('CommandError should be raised.')
|
||||||
|
except exceptions.CommandError as e:
|
||||||
|
self.assertEqual("Command Failed: One or more of"
|
||||||
|
" the operations failed",
|
||||||
|
str(e))
|
||||||
|
body = {
|
||||||
|
'cipher': 'aes-xts-plain64',
|
||||||
|
'key_size': 128,
|
||||||
|
'control_location': 'front-end',
|
||||||
|
}
|
||||||
|
self.encryption_types_mock.update.assert_called_with(
|
||||||
|
self.volume_type,
|
||||||
|
body,
|
||||||
|
)
|
||||||
|
self.encryption_types_mock.create.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
class TestTypeShow(TestType):
|
class TestTypeShow(TestType):
|
||||||
|
|
||||||
@ -489,6 +700,7 @@ class TestTypeShow(TestType):
|
|||||||
self.volume_type.id
|
self.volume_type.id
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
|
("encryption_type", False),
|
||||||
("volume_type", self.volume_type.id)
|
("volume_type", self.volume_type.id)
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
@ -564,6 +776,52 @@ class TestTypeShow(TestType):
|
|||||||
)
|
)
|
||||||
self.assertEqual(private_type_data, data)
|
self.assertEqual(private_type_data, data)
|
||||||
|
|
||||||
|
def test_type_show_with_encryption(self):
|
||||||
|
encryption_type = volume_fakes.FakeType.create_one_encryption_type()
|
||||||
|
encryption_info = {
|
||||||
|
'provider': 'LuksEncryptor',
|
||||||
|
'cipher': None,
|
||||||
|
'key_size': None,
|
||||||
|
'control_location': 'front-end',
|
||||||
|
}
|
||||||
|
self.volume_type = volume_fakes.FakeType.create_one_type(
|
||||||
|
attrs={'encryption': encryption_info})
|
||||||
|
self.types_mock.get.return_value = self.volume_type
|
||||||
|
self.encryption_types_mock.get.return_value = encryption_type
|
||||||
|
encryption_columns = (
|
||||||
|
'access_project_ids',
|
||||||
|
'description',
|
||||||
|
'encryption',
|
||||||
|
'id',
|
||||||
|
'is_public',
|
||||||
|
'name',
|
||||||
|
'properties',
|
||||||
|
)
|
||||||
|
encryption_data = (
|
||||||
|
None,
|
||||||
|
self.volume_type.description,
|
||||||
|
utils.format_dict(encryption_info),
|
||||||
|
self.volume_type.id,
|
||||||
|
True,
|
||||||
|
self.volume_type.name,
|
||||||
|
utils.format_dict(self.volume_type.extra_specs)
|
||||||
|
)
|
||||||
|
arglist = [
|
||||||
|
'--encryption-type',
|
||||||
|
self.volume_type.id
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('encryption_type', True),
|
||||||
|
("volume_type", self.volume_type.id)
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
self.types_mock.get.assert_called_with(self.volume_type.id)
|
||||||
|
self.encryption_types_mock.get.assert_called_with(self.volume_type.id)
|
||||||
|
self.assertEqual(encryption_columns, columns)
|
||||||
|
self.assertEqual(encryption_data, data)
|
||||||
|
|
||||||
|
|
||||||
class TestTypeUnset(TestType):
|
class TestTypeUnset(TestType):
|
||||||
|
|
||||||
@ -625,6 +883,7 @@ class TestTypeUnset(TestType):
|
|||||||
self.volume_type.id,
|
self.volume_type.id,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
|
('encryption_type', False),
|
||||||
('project', ''),
|
('project', ''),
|
||||||
('volume_type', self.volume_type.id),
|
('volume_type', self.volume_type.id),
|
||||||
]
|
]
|
||||||
@ -633,7 +892,7 @@ class TestTypeUnset(TestType):
|
|||||||
|
|
||||||
result = self.cmd.take_action(parsed_args)
|
result = self.cmd.take_action(parsed_args)
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
self.encryption_types_mock.delete.assert_not_called()
|
||||||
self.assertFalse(self.types_access_mock.remove_project_access.called)
|
self.assertFalse(self.types_access_mock.remove_project_access.called)
|
||||||
|
|
||||||
def test_type_unset_failed_with_missing_volume_type_argument(self):
|
def test_type_unset_failed_with_missing_volume_type_argument(self):
|
||||||
@ -649,3 +908,18 @@ class TestTypeUnset(TestType):
|
|||||||
self.cmd,
|
self.cmd,
|
||||||
arglist,
|
arglist,
|
||||||
verifylist)
|
verifylist)
|
||||||
|
|
||||||
|
def test_type_unset_encryption_type(self):
|
||||||
|
arglist = [
|
||||||
|
'--encryption-type',
|
||||||
|
self.volume_type.id,
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
('encryption_type', True),
|
||||||
|
('volume_type', self.volume_type.id),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
result = self.cmd.take_action(parsed_args)
|
||||||
|
self.encryption_types_mock.delete.assert_called_with(self.volume_type)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
@ -29,6 +29,26 @@ from openstackclient.i18n import _
|
|||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _create_encryption_type(volume_client, volume_type, parsed_args):
|
||||||
|
if not parsed_args.encryption_provider:
|
||||||
|
msg = _("'--encryption-provider' should be specified while "
|
||||||
|
"creating a new encryption type")
|
||||||
|
raise exceptions.CommandError(msg)
|
||||||
|
# set the default of control location while creating
|
||||||
|
control_location = 'front-end'
|
||||||
|
if parsed_args.encryption_control_location:
|
||||||
|
control_location = parsed_args.encryption_control_location
|
||||||
|
body = {
|
||||||
|
'provider': parsed_args.encryption_provider,
|
||||||
|
'cipher': parsed_args.encryption_cipher,
|
||||||
|
'key_size': parsed_args.encryption_key_size,
|
||||||
|
'control_location': control_location
|
||||||
|
}
|
||||||
|
encryption = volume_client.volume_encryption_types.create(
|
||||||
|
volume_type, body)
|
||||||
|
return encryption
|
||||||
|
|
||||||
|
|
||||||
class CreateVolumeType(command.ShowOne):
|
class CreateVolumeType(command.ShowOne):
|
||||||
_description = _("Create new volume type")
|
_description = _("Create new volume type")
|
||||||
|
|
||||||
@ -46,6 +66,42 @@ class CreateVolumeType(command.ShowOne):
|
|||||||
help=_('Set a property on this volume type '
|
help=_('Set a property on this volume type '
|
||||||
'(repeat option to set multiple properties)'),
|
'(repeat option to set multiple properties)'),
|
||||||
)
|
)
|
||||||
|
# TODO(Huanxuan Ao): Add choices for each "--encryption-*" option.
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-provider',
|
||||||
|
metavar='<provider>',
|
||||||
|
help=_('Set the class that provides encryption support for '
|
||||||
|
'this volume type (e.g "LuksEncryptor") (admin only) '
|
||||||
|
'(This option is required when setting encryption type '
|
||||||
|
'of a volume. Consider using other encryption options '
|
||||||
|
'such as: "--encryption-cipher", "--encryption-key-size" '
|
||||||
|
'and "--encryption-control-location")'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-cipher',
|
||||||
|
metavar='<cipher>',
|
||||||
|
help=_('Set the encryption algorithm or mode for this '
|
||||||
|
'volume type (e.g "aes-xts-plain64") (admin only)'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-key-size',
|
||||||
|
metavar='<key-size>',
|
||||||
|
type=int,
|
||||||
|
help=_('Set the size of the encryption key of this '
|
||||||
|
'volume type (e.g "128" or "256") (admin only)'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-control-location',
|
||||||
|
metavar='<control-location>',
|
||||||
|
choices=['front-end', 'back-end'],
|
||||||
|
help=_('Set the notional service where the encryption is '
|
||||||
|
'performed ("front-end" or "back-end") (admin only) '
|
||||||
|
'(The default value for this option is "front-end" '
|
||||||
|
'when setting encryption type of a volume. Consider '
|
||||||
|
'using other encryption options such as: '
|
||||||
|
'"--encryption-cipher", "--encryption-key-size" and '
|
||||||
|
'"--encryption-provider")'),
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -55,6 +111,21 @@ class CreateVolumeType(command.ShowOne):
|
|||||||
if parsed_args.property:
|
if parsed_args.property:
|
||||||
result = volume_type.set_keys(parsed_args.property)
|
result = volume_type.set_keys(parsed_args.property)
|
||||||
volume_type._info.update({'properties': utils.format_dict(result)})
|
volume_type._info.update({'properties': utils.format_dict(result)})
|
||||||
|
if (parsed_args.encryption_provider or
|
||||||
|
parsed_args.encryption_cipher or
|
||||||
|
parsed_args.encryption_key_size or
|
||||||
|
parsed_args.encryption_control_location):
|
||||||
|
try:
|
||||||
|
# create new encryption
|
||||||
|
encryption = _create_encryption_type(
|
||||||
|
volume_client, volume_type, parsed_args)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_("Failed to set encryption information for this "
|
||||||
|
"volume type: %s"), e)
|
||||||
|
# add encryption info in result
|
||||||
|
encryption._info.pop("volume_type_id", None)
|
||||||
|
volume_type._info.update(
|
||||||
|
{'encryption': utils.format_dict(encryption._info)})
|
||||||
volume_type._info.pop("os-volume-type-access:is_public", None)
|
volume_type._info.pop("os-volume-type-access:is_public", None)
|
||||||
|
|
||||||
return zip(*sorted(six.iteritems(volume_type._info)))
|
return zip(*sorted(six.iteritems(volume_type._info)))
|
||||||
@ -107,20 +178,58 @@ class ListVolumeType(command.Lister):
|
|||||||
default=False,
|
default=False,
|
||||||
help=_('List additional fields in output')
|
help=_('List additional fields in output')
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--encryption-type",
|
||||||
|
action="store_true",
|
||||||
|
help=_("Display encryption information for each volume type "
|
||||||
|
"(admin only)"),
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
|
volume_client = self.app.client_manager.volume
|
||||||
if parsed_args.long:
|
if parsed_args.long:
|
||||||
columns = ('ID', 'Name', 'Is Public', 'Extra Specs')
|
columns = ['ID', 'Name', 'Is Public', 'Extra Specs']
|
||||||
column_headers = ('ID', 'Name', 'Is Public', 'Properties')
|
column_headers = ['ID', 'Name', 'Is Public', 'Properties']
|
||||||
else:
|
else:
|
||||||
columns = ('ID', 'Name', 'Is Public')
|
columns = ['ID', 'Name', 'Is Public']
|
||||||
column_headers = columns
|
column_headers = ['ID', 'Name', 'Is Public']
|
||||||
data = self.app.client_manager.volume.volume_types.list()
|
data = volume_client.volume_types.list()
|
||||||
|
|
||||||
|
def _format_encryption_info(type_id, encryption_data=None):
|
||||||
|
encryption_data = encryption
|
||||||
|
encryption_info = '-'
|
||||||
|
if type_id in encryption_data.keys():
|
||||||
|
encryption_info = encryption_data[type_id]
|
||||||
|
return encryption_info
|
||||||
|
|
||||||
|
if parsed_args.encryption_type:
|
||||||
|
encryption = {}
|
||||||
|
for d in volume_client.volume_encryption_types.list():
|
||||||
|
volume_type_id = d._info['volume_type_id']
|
||||||
|
# remove some redundant information
|
||||||
|
del_key = [
|
||||||
|
'deleted',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
'deleted_at',
|
||||||
|
'volume_type_id'
|
||||||
|
]
|
||||||
|
for key in del_key:
|
||||||
|
d._info.pop(key, None)
|
||||||
|
# save the encryption information with their volume type ID
|
||||||
|
encryption[volume_type_id] = utils.format_dict(d._info)
|
||||||
|
# We need to get volume type ID, then show encryption
|
||||||
|
# information according to the ID, so use "id" to keep
|
||||||
|
# difference to the real "ID" column.
|
||||||
|
columns += ['id']
|
||||||
|
column_headers += ['Encryption']
|
||||||
|
|
||||||
return (column_headers,
|
return (column_headers,
|
||||||
(utils.get_item_properties(
|
(utils.get_item_properties(
|
||||||
s, columns,
|
s, columns,
|
||||||
formatters={'Extra Specs': utils.format_dict},
|
formatters={'Extra Specs': utils.format_dict,
|
||||||
|
'id': _format_encryption_info},
|
||||||
) for s in data))
|
) for s in data))
|
||||||
|
|
||||||
|
|
||||||
@ -141,6 +250,42 @@ class SetVolumeType(command.Command):
|
|||||||
help=_('Set a property on this volume type '
|
help=_('Set a property on this volume type '
|
||||||
'(repeat option to set multiple properties)'),
|
'(repeat option to set multiple properties)'),
|
||||||
)
|
)
|
||||||
|
# TODO(Huanxuan Ao): Add choices for each "--encryption-*" option.
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-provider',
|
||||||
|
metavar='<provider>',
|
||||||
|
help=_('Set the class that provides encryption support for '
|
||||||
|
'this volume type (e.g "LuksEncryptor") (admin only) '
|
||||||
|
'(This option is required when setting encryption type '
|
||||||
|
'of a volume. Consider using other encryption options '
|
||||||
|
'such as: "--encryption-cipher", "--encryption-key-size" '
|
||||||
|
'and "--encryption-control-location")'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-cipher',
|
||||||
|
metavar='<cipher>',
|
||||||
|
help=_('Set the encryption algorithm or mode for this '
|
||||||
|
'volume type (e.g "aes-xts-plain64") (admin only)'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-key-size',
|
||||||
|
metavar='<key-size>',
|
||||||
|
type=int,
|
||||||
|
help=_('Set the size of the encryption key of this '
|
||||||
|
'volume type (e.g "128" or "256") (admin only)'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-control-location',
|
||||||
|
metavar='<control-location>',
|
||||||
|
choices=['front-end', 'back-end'],
|
||||||
|
help=_('Set the notional service where the encryption is '
|
||||||
|
'performed ("front-end" or "back-end") (admin only) '
|
||||||
|
'(The default value for this option is "front-end" '
|
||||||
|
'when setting encryption type of a volume. Consider '
|
||||||
|
'using other encryption options such as: '
|
||||||
|
'"--encryption-cipher", "--encryption-key-size" and '
|
||||||
|
'"--encryption-provider")'),
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -148,8 +293,29 @@ class SetVolumeType(command.Command):
|
|||||||
volume_type = utils.find_resource(
|
volume_type = utils.find_resource(
|
||||||
volume_client.volume_types, parsed_args.volume_type)
|
volume_client.volume_types, parsed_args.volume_type)
|
||||||
|
|
||||||
|
result = 0
|
||||||
if parsed_args.property:
|
if parsed_args.property:
|
||||||
volume_type.set_keys(parsed_args.property)
|
try:
|
||||||
|
volume_type.set_keys(parsed_args.property)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_("Failed to set volume type property: %s"), e)
|
||||||
|
result += 1
|
||||||
|
|
||||||
|
if (parsed_args.encryption_provider or
|
||||||
|
parsed_args.encryption_cipher or
|
||||||
|
parsed_args.encryption_key_size or
|
||||||
|
parsed_args.encryption_control_location):
|
||||||
|
try:
|
||||||
|
_create_encryption_type(
|
||||||
|
volume_client, volume_type, parsed_args)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_("Failed to set encryption information for this "
|
||||||
|
"volume type: %s"), e)
|
||||||
|
result += 1
|
||||||
|
|
||||||
|
if result > 0:
|
||||||
|
raise exceptions.CommandError(_("Command Failed: One or more of"
|
||||||
|
" the operations failed"))
|
||||||
|
|
||||||
|
|
||||||
class ShowVolumeType(command.ShowOne):
|
class ShowVolumeType(command.ShowOne):
|
||||||
@ -162,6 +328,12 @@ class ShowVolumeType(command.ShowOne):
|
|||||||
metavar="<volume-type>",
|
metavar="<volume-type>",
|
||||||
help=_("Volume type to display (name or ID)")
|
help=_("Volume type to display (name or ID)")
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--encryption-type",
|
||||||
|
action="store_true",
|
||||||
|
help=_("Display encryption information of this volume type "
|
||||||
|
"(admin only)"),
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -170,6 +342,17 @@ class ShowVolumeType(command.ShowOne):
|
|||||||
volume_client.volume_types, parsed_args.volume_type)
|
volume_client.volume_types, parsed_args.volume_type)
|
||||||
properties = utils.format_dict(volume_type._info.pop('extra_specs'))
|
properties = utils.format_dict(volume_type._info.pop('extra_specs'))
|
||||||
volume_type._info.update({'properties': properties})
|
volume_type._info.update({'properties': properties})
|
||||||
|
if parsed_args.encryption_type:
|
||||||
|
# show encryption type information for this volume type
|
||||||
|
try:
|
||||||
|
encryption = volume_client.volume_encryption_types.get(
|
||||||
|
volume_type.id)
|
||||||
|
encryption._info.pop("volume_type_id", None)
|
||||||
|
volume_type._info.update(
|
||||||
|
{'encryption': utils.format_dict(encryption._info)})
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_("Failed to display the encryption information "
|
||||||
|
"of this volume type: %s"), e)
|
||||||
volume_type._info.pop("os-volume-type-access:is_public", None)
|
volume_type._info.pop("os-volume-type-access:is_public", None)
|
||||||
return zip(*sorted(six.iteritems(volume_type._info)))
|
return zip(*sorted(six.iteritems(volume_type._info)))
|
||||||
|
|
||||||
@ -191,6 +374,12 @@ class UnsetVolumeType(command.Command):
|
|||||||
help=_('Remove a property from this volume type '
|
help=_('Remove a property from this volume type '
|
||||||
'(repeat option to remove multiple properties)'),
|
'(repeat option to remove multiple properties)'),
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--encryption-type",
|
||||||
|
action="store_true",
|
||||||
|
help=_("Remove the encryption type for this volume type "
|
||||||
|
"(admin oly)"),
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -200,5 +389,21 @@ class UnsetVolumeType(command.Command):
|
|||||||
parsed_args.volume_type,
|
parsed_args.volume_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
result = 0
|
||||||
if parsed_args.property:
|
if parsed_args.property:
|
||||||
volume_type.unset_keys(parsed_args.property)
|
try:
|
||||||
|
volume_type.unset_keys(parsed_args.property)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_("Failed to unset volume type property: %s"), e)
|
||||||
|
result += 1
|
||||||
|
if parsed_args.encryption_type:
|
||||||
|
try:
|
||||||
|
volume_client.volume_encryption_types.delete(volume_type)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_("Failed to remove the encryption type for this "
|
||||||
|
"volume type: %s"), e)
|
||||||
|
result += 1
|
||||||
|
|
||||||
|
if result > 0:
|
||||||
|
raise exceptions.CommandError(_("Command Failed: One or more of"
|
||||||
|
" the operations failed"))
|
||||||
|
@ -29,6 +29,44 @@ from openstackclient.identity import common as identity_common
|
|||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _create_encryption_type(volume_client, volume_type, parsed_args):
|
||||||
|
if not parsed_args.encryption_provider:
|
||||||
|
msg = _("'--encryption-provider' should be specified while "
|
||||||
|
"creating a new encryption type")
|
||||||
|
raise exceptions.CommandError(msg)
|
||||||
|
# set the default of control location while creating
|
||||||
|
control_location = 'front-end'
|
||||||
|
if parsed_args.encryption_control_location:
|
||||||
|
control_location = parsed_args.encryption_control_location
|
||||||
|
body = {
|
||||||
|
'provider': parsed_args.encryption_provider,
|
||||||
|
'cipher': parsed_args.encryption_cipher,
|
||||||
|
'key_size': parsed_args.encryption_key_size,
|
||||||
|
'control_location': control_location
|
||||||
|
}
|
||||||
|
encryption = volume_client.volume_encryption_types.create(
|
||||||
|
volume_type, body)
|
||||||
|
return encryption
|
||||||
|
|
||||||
|
|
||||||
|
def _set_encryption_type(volume_client, volume_type, parsed_args):
|
||||||
|
# update the existing encryption type
|
||||||
|
body = {}
|
||||||
|
for attr in ['provider', 'cipher', 'key_size', 'control_location']:
|
||||||
|
info = getattr(parsed_args, 'encryption_' + attr, None)
|
||||||
|
if info is not None:
|
||||||
|
body[attr] = info
|
||||||
|
try:
|
||||||
|
volume_client.volume_encryption_types.update(volume_type, body)
|
||||||
|
except Exception as e:
|
||||||
|
if type(e).__name__ == 'NotFound':
|
||||||
|
# create new encryption type
|
||||||
|
LOG.warning(_("No existing encryption type found, creating "
|
||||||
|
"new encryption type for this volume type ..."))
|
||||||
|
_create_encryption_type(
|
||||||
|
volume_client, volume_type, parsed_args)
|
||||||
|
|
||||||
|
|
||||||
class CreateVolumeType(command.ShowOne):
|
class CreateVolumeType(command.ShowOne):
|
||||||
_description = _("Create new volume type")
|
_description = _("Create new volume type")
|
||||||
|
|
||||||
@ -70,6 +108,42 @@ class CreateVolumeType(command.ShowOne):
|
|||||||
help=_("Allow <project> to access private type (name or ID) "
|
help=_("Allow <project> to access private type (name or ID) "
|
||||||
"(Must be used with --private option)"),
|
"(Must be used with --private option)"),
|
||||||
)
|
)
|
||||||
|
# TODO(Huanxuan Ao): Add choices for each "--encryption-*" option.
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-provider',
|
||||||
|
metavar='<provider>',
|
||||||
|
help=_('Set the class that provides encryption support for '
|
||||||
|
'this volume type (e.g "LuksEncryptor") (admin only) '
|
||||||
|
'(This option is required when setting encryption type '
|
||||||
|
'of a volume. Consider using other encryption options '
|
||||||
|
'such as: "--encryption-cipher", "--encryption-key-size" '
|
||||||
|
'and "--encryption-control-location")'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-cipher',
|
||||||
|
metavar='<cipher>',
|
||||||
|
help=_('Set the encryption algorithm or mode for this '
|
||||||
|
'volume type (e.g "aes-xts-plain64") (admin only)'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-key-size',
|
||||||
|
metavar='<key-size>',
|
||||||
|
type=int,
|
||||||
|
help=_('Set the size of the encryption key of this '
|
||||||
|
'volume type (e.g "128" or "256") (admin only)'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-control-location',
|
||||||
|
metavar='<control-location>',
|
||||||
|
choices=['front-end', 'back-end'],
|
||||||
|
help=_('Set the notional service where the encryption is '
|
||||||
|
'performed ("front-end" or "back-end") (admin only) '
|
||||||
|
'(The default value for this option is "front-end" '
|
||||||
|
'when setting encryption type of a volume. Consider '
|
||||||
|
'using other encryption options such as: '
|
||||||
|
'"--encryption-cipher", "--encryption-key-size" and '
|
||||||
|
'"--encryption-provider")'),
|
||||||
|
)
|
||||||
identity_common.add_project_domain_option_to_parser(parser)
|
identity_common.add_project_domain_option_to_parser(parser)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@ -110,6 +184,21 @@ class CreateVolumeType(command.ShowOne):
|
|||||||
if parsed_args.property:
|
if parsed_args.property:
|
||||||
result = volume_type.set_keys(parsed_args.property)
|
result = volume_type.set_keys(parsed_args.property)
|
||||||
volume_type._info.update({'properties': utils.format_dict(result)})
|
volume_type._info.update({'properties': utils.format_dict(result)})
|
||||||
|
if (parsed_args.encryption_provider or
|
||||||
|
parsed_args.encryption_cipher or
|
||||||
|
parsed_args.encryption_key_size or
|
||||||
|
parsed_args.encryption_control_location):
|
||||||
|
try:
|
||||||
|
# create new encryption
|
||||||
|
encryption = _create_encryption_type(
|
||||||
|
volume_client, volume_type, parsed_args)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_("Failed to set encryption information for this "
|
||||||
|
"volume type: %s"), e)
|
||||||
|
# add encryption info in result
|
||||||
|
encryption._info.pop("volume_type_id", None)
|
||||||
|
volume_type._info.update(
|
||||||
|
{'encryption': utils.format_dict(encryption._info)})
|
||||||
volume_type._info.pop("os-volume-type-access:is_public", None)
|
volume_type._info.pop("os-volume-type-access:is_public", None)
|
||||||
|
|
||||||
return zip(*sorted(six.iteritems(volume_type._info)))
|
return zip(*sorted(six.iteritems(volume_type._info)))
|
||||||
@ -179,6 +268,12 @@ class ListVolumeType(command.Lister):
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help=_("List only private types (admin only)")
|
help=_("List only private types (admin only)")
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--encryption-type",
|
||||||
|
action="store_true",
|
||||||
|
help=_("Display encryption information for each volume type "
|
||||||
|
"(admin only)"),
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -189,7 +284,7 @@ class ListVolumeType(command.Lister):
|
|||||||
'ID', 'Name', 'Is Public', 'Description', 'Properties']
|
'ID', 'Name', 'Is Public', 'Description', 'Properties']
|
||||||
else:
|
else:
|
||||||
columns = ['ID', 'Name', 'Is Public']
|
columns = ['ID', 'Name', 'Is Public']
|
||||||
column_headers = columns
|
column_headers = ['ID', 'Name', 'Is Public']
|
||||||
if parsed_args.default:
|
if parsed_args.default:
|
||||||
data = [volume_client.volume_types.default()]
|
data = [volume_client.volume_types.default()]
|
||||||
else:
|
else:
|
||||||
@ -200,10 +295,41 @@ class ListVolumeType(command.Lister):
|
|||||||
is_public = False
|
is_public = False
|
||||||
data = volume_client.volume_types.list(
|
data = volume_client.volume_types.list(
|
||||||
is_public=is_public)
|
is_public=is_public)
|
||||||
|
|
||||||
|
def _format_encryption_info(type_id, encryption_data=None):
|
||||||
|
encryption_data = encryption
|
||||||
|
encryption_info = '-'
|
||||||
|
if type_id in encryption_data.keys():
|
||||||
|
encryption_info = encryption_data[type_id]
|
||||||
|
return encryption_info
|
||||||
|
|
||||||
|
if parsed_args.encryption_type:
|
||||||
|
encryption = {}
|
||||||
|
for d in volume_client.volume_encryption_types.list():
|
||||||
|
volume_type_id = d._info['volume_type_id']
|
||||||
|
# remove some redundant information
|
||||||
|
del_key = [
|
||||||
|
'deleted',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
'deleted_at',
|
||||||
|
'volume_type_id'
|
||||||
|
]
|
||||||
|
for key in del_key:
|
||||||
|
d._info.pop(key, None)
|
||||||
|
# save the encryption information with their volume type ID
|
||||||
|
encryption[volume_type_id] = utils.format_dict(d._info)
|
||||||
|
# We need to get volume type ID, then show encryption
|
||||||
|
# information according to the ID, so use "id" to keep
|
||||||
|
# difference to the real "ID" column.
|
||||||
|
columns += ['id']
|
||||||
|
column_headers += ['Encryption']
|
||||||
|
|
||||||
return (column_headers,
|
return (column_headers,
|
||||||
(utils.get_item_properties(
|
(utils.get_item_properties(
|
||||||
s, columns,
|
s, columns,
|
||||||
formatters={'Extra Specs': utils.format_dict},
|
formatters={'Extra Specs': utils.format_dict,
|
||||||
|
'id': _format_encryption_info},
|
||||||
) for s in data))
|
) for s in data))
|
||||||
|
|
||||||
|
|
||||||
@ -241,7 +367,43 @@ class SetVolumeType(command.Command):
|
|||||||
'(admin only)'),
|
'(admin only)'),
|
||||||
)
|
)
|
||||||
identity_common.add_project_domain_option_to_parser(parser)
|
identity_common.add_project_domain_option_to_parser(parser)
|
||||||
|
# TODO(Huanxuan Ao): Add choices for each "--encryption-*" option.
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-provider',
|
||||||
|
metavar='<provider>',
|
||||||
|
help=_('Set the class that provides encryption support for '
|
||||||
|
'this volume type (e.g "LuksEncryptor") (admin only) '
|
||||||
|
'(This option is required when setting encryption type '
|
||||||
|
'of a volume for the first time. Consider using other '
|
||||||
|
'encryption options such as: "--encryption-cipher", '
|
||||||
|
'"--encryption-key-size" and '
|
||||||
|
'"--encryption-control-location")'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-cipher',
|
||||||
|
metavar='<cipher>',
|
||||||
|
help=_('Set the encryption algorithm or mode for this '
|
||||||
|
'volume type (e.g "aes-xts-plain64") (admin only)'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-key-size',
|
||||||
|
metavar='<key-size>',
|
||||||
|
type=int,
|
||||||
|
help=_('Set the size of the encryption key of this '
|
||||||
|
'volume type (e.g "128" or "256") (admin only)'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--encryption-control-location',
|
||||||
|
metavar='<control-location>',
|
||||||
|
choices=['front-end', 'back-end'],
|
||||||
|
help=_('Set the notional service where the encryption is '
|
||||||
|
'performed ("front-end" or "back-end") (admin only) '
|
||||||
|
'(The default value for this option is "front-end" '
|
||||||
|
'when setting encryption type of a volume for the '
|
||||||
|
'first time. Consider using other encryption options '
|
||||||
|
'such as: "--encryption-cipher", "--encryption-key-size" '
|
||||||
|
'and "--encryption-provider")'),
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -290,6 +452,17 @@ class SetVolumeType(command.Command):
|
|||||||
"project: %s"), e)
|
"project: %s"), e)
|
||||||
result += 1
|
result += 1
|
||||||
|
|
||||||
|
if (parsed_args.encryption_provider or
|
||||||
|
parsed_args.encryption_cipher or
|
||||||
|
parsed_args.encryption_key_size or
|
||||||
|
parsed_args.encryption_control_location):
|
||||||
|
try:
|
||||||
|
_set_encryption_type(volume_client, volume_type, parsed_args)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_("Failed to set encryption information for this "
|
||||||
|
"volume type: %s"), e)
|
||||||
|
result += 1
|
||||||
|
|
||||||
if result > 0:
|
if result > 0:
|
||||||
raise exceptions.CommandError(_("Command Failed: One or more of"
|
raise exceptions.CommandError(_("Command Failed: One or more of"
|
||||||
" the operations failed"))
|
" the operations failed"))
|
||||||
@ -305,6 +478,12 @@ class ShowVolumeType(command.ShowOne):
|
|||||||
metavar="<volume-type>",
|
metavar="<volume-type>",
|
||||||
help=_("Volume type to display (name or ID)")
|
help=_("Volume type to display (name or ID)")
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--encryption-type",
|
||||||
|
action="store_true",
|
||||||
|
help=_("Display encryption information of this volume type "
|
||||||
|
"(admin only)"),
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -329,6 +508,17 @@ class ShowVolumeType(command.ShowOne):
|
|||||||
'%(type)s: %(e)s')
|
'%(type)s: %(e)s')
|
||||||
LOG.error(msg % {'type': volume_type.id, 'e': e})
|
LOG.error(msg % {'type': volume_type.id, 'e': e})
|
||||||
volume_type._info.update({'access_project_ids': access_project_ids})
|
volume_type._info.update({'access_project_ids': access_project_ids})
|
||||||
|
if parsed_args.encryption_type:
|
||||||
|
# show encryption type information for this volume type
|
||||||
|
try:
|
||||||
|
encryption = volume_client.volume_encryption_types.get(
|
||||||
|
volume_type.id)
|
||||||
|
encryption._info.pop("volume_type_id", None)
|
||||||
|
volume_type._info.update(
|
||||||
|
{'encryption': utils.format_dict(encryption._info)})
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_("Failed to display the encryption information "
|
||||||
|
"of this volume type: %s"), e)
|
||||||
volume_type._info.pop("os-volume-type-access:is_public", None)
|
volume_type._info.pop("os-volume-type-access:is_public", None)
|
||||||
return zip(*sorted(six.iteritems(volume_type._info)))
|
return zip(*sorted(six.iteritems(volume_type._info)))
|
||||||
|
|
||||||
@ -357,7 +547,12 @@ class UnsetVolumeType(command.Command):
|
|||||||
' (admin only)'),
|
' (admin only)'),
|
||||||
)
|
)
|
||||||
identity_common.add_project_domain_option_to_parser(parser)
|
identity_common.add_project_domain_option_to_parser(parser)
|
||||||
|
parser.add_argument(
|
||||||
|
"--encryption-type",
|
||||||
|
action="store_true",
|
||||||
|
help=_("Remove the encryption type for this volume type "
|
||||||
|
"(admin only)"),
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -391,6 +586,13 @@ class UnsetVolumeType(command.Command):
|
|||||||
LOG.error(_("Failed to remove volume type access from "
|
LOG.error(_("Failed to remove volume type access from "
|
||||||
"project: %s"), e)
|
"project: %s"), e)
|
||||||
result += 1
|
result += 1
|
||||||
|
if parsed_args.encryption_type:
|
||||||
|
try:
|
||||||
|
volume_client.volume_encryption_types.delete(volume_type)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(_("Failed to remove the encryption type for this "
|
||||||
|
"volume type: %s"), e)
|
||||||
|
result += 1
|
||||||
|
|
||||||
if result > 0:
|
if result > 0:
|
||||||
raise exceptions.CommandError(_("Command Failed: One or more of"
|
raise exceptions.CommandError(_("Command Failed: One or more of"
|
||||||
|
9
releasenotes/notes/bug-1651117-a1df37e7ea939ba4.yaml
Normal file
9
releasenotes/notes/bug-1651117-a1df37e7ea939ba4.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Add ``--encryption-provider``, ``--encryption-cipher``, ``--encryption-key-size``
|
||||||
|
and ``--encryption-control-location`` options to ``volume type set`` and
|
||||||
|
``volume type create`` commands.
|
||||||
|
Add ``--encryption-type`` option to ``volume type unset``, ``volume type list``
|
||||||
|
and ``volume type show`` commands.
|
||||||
|
[Bug `1651117 <https://bugs.launchpad.net/bugs/1651117>`_]
|
Loading…
Reference in New Issue
Block a user