Introduce storage adapter mapping conf object
This patch introduces a oslo config object that can be used by the physical_storage_adapter_mappings configuration option. The config object validates the config option along a regex and returns a tuple (adapter_id, port). It accepts adapter-ids in upper and lower case. But for users it returns the adapter-id always in lower case (like desired by the HMC web services API). Some parts of this code can be removed once they are available in a new os-dpm release (e.g. the MultiStringWithKwargsType). Change-Id: I03319412ad15c608ecc43b0421f0cae42bd6b5dc Partial-Bug: #1663369
This commit is contained in:
parent
4e9526f42b
commit
6f3261499c
|
@ -0,0 +1,30 @@
|
|||
# Copyright 2017 IBM Corp. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from nova_dpm.conf import types
|
||||
|
||||
|
||||
class MultiStorageAdapterMappingOpt(cfg.MultiOpt):
|
||||
def __init__(self, name, **kwargs):
|
||||
"""Option with DPM AdapterPortMapping type
|
||||
|
||||
Option with ``type`` :class:`StorageAdapterMappingType`
|
||||
|
||||
:param name: the option's name
|
||||
"""
|
||||
super(MultiStorageAdapterMappingOpt, self).__init__(
|
||||
name, item_type=types.StorageAdapterMappingType(), **kwargs)
|
|
@ -0,0 +1,45 @@
|
|||
# Copyright 2017 IBM Corp. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
OBJECT_ID_REGEX = "[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}"
|
||||
# A FCP adapter can have up to 4 ports
|
||||
PORT_REGEX = "[0-3]"
|
||||
MAPPING_REGEX = "^" + OBJECT_ID_REGEX + ":" + PORT_REGEX + "$"
|
||||
|
||||
|
||||
class StorageAdapterMappingType(cfg.types.String):
|
||||
"""Storage adapter mapping type.
|
||||
|
||||
Values are returned as tuple (adapter-id, port)
|
||||
"""
|
||||
def __init__(self, type_name='multi valued'):
|
||||
super(StorageAdapterMappingType, self).__init__(
|
||||
type_name=type_name,
|
||||
regex=MAPPING_REGEX,
|
||||
ignore_case=True
|
||||
)
|
||||
|
||||
def format_defaults(self, default, sample_default=None):
|
||||
multi = cfg.types.MultiString()
|
||||
return multi.format_defaults(default, sample_default)
|
||||
|
||||
def __call__(self, value):
|
||||
val = super(StorageAdapterMappingType, self).__call__(value)
|
||||
# No extra checking for None required here.
|
||||
# The regex ensures the format of the value in the super class.
|
||||
split_result = val.split(':')
|
||||
return split_result[0].lower(), split_result[1]
|
|
@ -0,0 +1,97 @@
|
|||
# Copyright 2016 IBM Corp. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
from oslo_config import cfg
|
||||
from oslo_config.fixture import Config
|
||||
import tempfile
|
||||
|
||||
from nova.test import TestCase
|
||||
|
||||
from nova_dpm.conf.cfg import MultiStorageAdapterMappingOpt
|
||||
from nova_dpm.conf.types import StorageAdapterMappingType
|
||||
|
||||
|
||||
class TestStorageAdapterMappingOpt(TestCase):
|
||||
|
||||
def create_tempfiles(self, files, ext='.conf'):
|
||||
"""Create temp files for testing
|
||||
|
||||
:param files: A list of files of tuples in the format
|
||||
[('filename1', 'line1\nline2\n'), ('filename2', 'line1\nline2\n')]
|
||||
:param ext: The file extension to be used
|
||||
:return: List of file paths
|
||||
paths[0] = path of filename1
|
||||
paths[1] = path of filename2
|
||||
"""
|
||||
# TODO(andreas_s): Make a mixin in os-dpm and move this there
|
||||
# (also required in nova-dpm)
|
||||
tempfiles = []
|
||||
for (basename, contents) in files:
|
||||
if not os.path.isabs(basename):
|
||||
# create all the tempfiles in a tempdir
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
path = os.path.join(tmpdir, basename + ext)
|
||||
# the path can start with a subdirectory so create
|
||||
# it if it doesn't exist yet
|
||||
if not os.path.exists(os.path.dirname(path)):
|
||||
os.makedirs(os.path.dirname(path))
|
||||
else:
|
||||
path = basename + ext
|
||||
fd = os.open(path, os.O_CREAT | os.O_WRONLY)
|
||||
tempfiles.append(path)
|
||||
try:
|
||||
os.write(fd, contents.encode('utf-8'))
|
||||
finally:
|
||||
os.close(fd)
|
||||
return tempfiles
|
||||
|
||||
def test_object(self):
|
||||
opt = MultiStorageAdapterMappingOpt("mapping", help="foo-help")
|
||||
self.assertEqual("foo-help", opt.help)
|
||||
self.assertEqual("mapping", opt.name)
|
||||
self.assertEqual(StorageAdapterMappingType, type(opt.type))
|
||||
|
||||
def test_config_single(self):
|
||||
mapping = ["aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:0"]
|
||||
opt = MultiStorageAdapterMappingOpt("mapping")
|
||||
cfg.CONF.register_opt(opt)
|
||||
self.flags(mapping=mapping)
|
||||
self.assertEqual([("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "0")],
|
||||
cfg.CONF.mapping)
|
||||
|
||||
def test_config_multiple_set_override(self):
|
||||
mapping = ["aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:0",
|
||||
"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb:1"]
|
||||
opt = MultiStorageAdapterMappingOpt("mapping")
|
||||
cfg.CONF.register_opt(opt)
|
||||
self.flags(mapping=mapping)
|
||||
expected_mapping = [
|
||||
("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "0"),
|
||||
("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "1")]
|
||||
self.assertEqual(expected_mapping, cfg.CONF.mapping)
|
||||
|
||||
def test_config_multiple_config_file(self):
|
||||
mapping = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:0"
|
||||
other_mapping = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb:1"
|
||||
paths = self.create_tempfiles([
|
||||
('test', '[DEFAULT]\nmapping = ' + mapping + '\n'
|
||||
'mapping = ' + other_mapping + '\n')])
|
||||
|
||||
conf = self.useFixture(Config())
|
||||
conf.register_opt(MultiStorageAdapterMappingOpt("mapping"))
|
||||
conf.conf(args=['--config-file', paths[0]])
|
||||
self.assertEqual([("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "0"),
|
||||
("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "1")],
|
||||
cfg.CONF.mapping)
|
|
@ -0,0 +1,76 @@
|
|||
# Copyright 2017 IBM Corp. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from nova.test import TestCase
|
||||
|
||||
from nova_dpm.conf.types import MAPPING_REGEX
|
||||
from nova_dpm.conf.types import StorageAdapterMappingType
|
||||
|
||||
VALID_DPM_OBJECT_ID = "fa1f2466-12df-311a-804c-4ed2cc1d656b"
|
||||
VALID_DPM_OBJECT_ID_UC = "FA1F2466-12DF-311A-804C-4ED2CC1D656B"
|
||||
VALID_STORAGE_MAPPING = VALID_DPM_OBJECT_ID + ":0"
|
||||
|
||||
|
||||
class TestStorageAdapterMappingType(TestCase):
|
||||
def setUp(self):
|
||||
super(TestStorageAdapterMappingType, self).setUp()
|
||||
self.conf_type = StorageAdapterMappingType()
|
||||
|
||||
def test_valid_mapping(self):
|
||||
adapter_id, port = self.conf_type(VALID_STORAGE_MAPPING)
|
||||
self.assertEqual("0", port)
|
||||
self.assertEqual(VALID_DPM_OBJECT_ID, adapter_id)
|
||||
|
||||
def test_valid_port_values_ok(self):
|
||||
for p in ["0", "1", "2", "3"]:
|
||||
adapter_id, port = self.conf_type(VALID_DPM_OBJECT_ID + ":" + p)
|
||||
self.assertEqual(p, port)
|
||||
|
||||
def test_upper_case_to_lower_case_adapter_id(self):
|
||||
adapter_id, port = self.conf_type(VALID_DPM_OBJECT_ID_UC + ":0")
|
||||
self.assertEqual("0", port)
|
||||
self.assertEqual(VALID_DPM_OBJECT_ID, adapter_id)
|
||||
|
||||
def test_empty_value_fail(self):
|
||||
self.assertRaises(ValueError, self.conf_type, '')
|
||||
|
||||
def test_invalid_value_fail(self):
|
||||
self.assertRaises(ValueError, self.conf_type, 'foobar')
|
||||
|
||||
def test_missing_port_fail(self):
|
||||
self.assertRaises(ValueError, self.conf_type, VALID_DPM_OBJECT_ID)
|
||||
|
||||
def test_missing_port_fail_2(self):
|
||||
self.assertRaises(ValueError, self.conf_type,
|
||||
VALID_DPM_OBJECT_ID + ":")
|
||||
|
||||
def test_invalid_port_fail(self):
|
||||
self.assertRaises(ValueError, self.conf_type,
|
||||
VALID_DPM_OBJECT_ID + ":4")
|
||||
|
||||
def test_invalid_port_type_fail(self):
|
||||
self.assertRaises(ValueError, self.conf_type,
|
||||
VALID_DPM_OBJECT_ID + ":a")
|
||||
|
||||
def test_invalid_adapter_id_fail(self):
|
||||
self.assertRaises(ValueError, self.conf_type, "foo:1")
|
||||
|
||||
def test_repr(self):
|
||||
self.assertEqual(
|
||||
"String(regex='" + MAPPING_REGEX + "')",
|
||||
repr(self.conf_type))
|
||||
|
||||
def test_equal(self):
|
||||
self.assertTrue(
|
||||
StorageAdapterMappingType() == StorageAdapterMappingType())
|
Loading…
Reference in New Issue