Browse Source

Introduce oslo config object for adapter_mapping

This patch introduces a oslo config object for a network adapter port
mapping. It encapsulates the validation of the config value and does
the parsing. It's a multi value config option.

It requires the following arguments

* name: The name of the config option.

The config value must be of the following format in the config file

  <name>=<physnet>:<adapter-id>:<port>
  <name>=>physnet>:<adapter-id>:
  <name>=>physnet>:<adapter-id>
  # (multi valued)

It returns a list of tuples:

  [(<physnet>, <adapter-id>, <port>),
   (<physnet>, <adapter-id>, <port>),
   (<physnet>, <adapter-id>, <port>)]

where

* physnet:    The physical network string. Can not be 'None'.
              No restriction in length. Keeps case from config file.
* adapter-id: The object-id of the adapter that should be used.
              Cannot be 'None'. Will be returned in lower case.
* port:       The port-element-id string to be used. Defaults to '0'.
              Cannot be 'None'

Change-Id: I407bda2896d667ef6fad4138a9787da4b39a19f7
Partial-Bug: #1663369
Partial-Bug: #1659315
Andreas Scheuring 2 years ago
parent
commit
52b5f3600d

+ 30
- 0
networking_dpm/conf/cfg.py View File

@@ -0,0 +1,30 @@
1
+# Copyright 2017 IBM Corp. All Rights Reserved.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+#
7
+#    http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+# See the License for the specific language governing permissions and
13
+# limitations under the License.
14
+
15
+
16
+from oslo_config import cfg
17
+
18
+from networking_dpm.conf.types import NetworkAdapterMappingType
19
+
20
+
21
+class MultiNetworkAdapterMappingOpt(cfg.MultiOpt):
22
+    def __init__(self, name, **kwargs):
23
+        """Option with DPM NetworkAdapterMappingType type
24
+
25
+           Option with ``type`` :class:`NetworkAdapterMappingType`
26
+
27
+        :param name: the option's name
28
+        """
29
+        super(MultiNetworkAdapterMappingOpt, self).__init__(
30
+            name, item_type=NetworkAdapterMappingType(), **kwargs)

+ 49
- 0
networking_dpm/conf/types.py View File

@@ -0,0 +1,49 @@
1
+# Copyright 2017 IBM Corp. All Rights Reserved.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+#
7
+#    http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+# See the License for the specific language governing permissions and
13
+# limitations under the License.
14
+
15
+
16
+from oslo_config import cfg
17
+
18
+OBJECT_ID_REGEX = "[a-f0-9]{8}-([a-f0-9]{4}-){3}[a-f0-9]{12}"
19
+PORT_REGEX = "[0,1]"
20
+MAPPING_REGEX = "^[^:]+:" + OBJECT_ID_REGEX + "(:?|(:" + PORT_REGEX + ")?)$"
21
+
22
+
23
+class NetworkAdapterMappingType(cfg.types.String):
24
+    """Network adapter mapping type.
25
+
26
+    Values are returned as tuple (net, adapter-id, port).
27
+    Port defaults to '0' if non given.
28
+    """
29
+    def __init__(self, type_name='multi valued'):
30
+        super(NetworkAdapterMappingType, self).__init__(
31
+            type_name=type_name,
32
+            regex=MAPPING_REGEX,
33
+            ignore_case=True
34
+        )
35
+
36
+    def format_defaults(self, default, sample_default=None):
37
+        multi = cfg.types.MultiString()
38
+        return multi.format_defaults(default, sample_default)
39
+
40
+    def __call__(self, value):
41
+        val = super(NetworkAdapterMappingType, self).__call__(value)
42
+        # No extra checking for None required here.
43
+        # The regex ensures the format of the value in the super class.
44
+        split_result = val.split(':')
45
+        net = split_result[0]
46
+        adapter_id = split_result[1].lower()
47
+        port = (split_result[2] if len(split_result) == 3 and
48
+                split_result[2] else "0")
49
+        return net, adapter_id, port

+ 97
- 0
networking_dpm/tests/unit/conf/test_cfg.py View File

@@ -0,0 +1,97 @@
1
+# Copyright 2017 IBM Corp. All Rights Reserved.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+#
7
+#    http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+# See the License for the specific language governing permissions and
13
+# limitations under the License.
14
+
15
+import os
16
+from oslo_config import cfg
17
+from oslo_config.fixture import Config
18
+import tempfile
19
+
20
+from networking_dpm.conf.cfg import MultiNetworkAdapterMappingOpt
21
+from networking_dpm.conf.types import NetworkAdapterMappingType
22
+from networking_dpm.tests import base
23
+
24
+
25
+class TestNetworkAdapterMappingType(base.BaseTestCase):
26
+
27
+    def create_tempfiles(self, files, ext='.conf'):
28
+        """Create temp files for testing
29
+
30
+        :param files: A list of files of tuples in the format
31
+           [('filename1', 'line1\nline2\n'), ('filename2', 'line1\nline2\n')]
32
+        :param ext: The file extension to be used
33
+        :return List of file paths
34
+           paths[0] = path of filename1
35
+           paths[1] = path of filename2
36
+        """
37
+        # TODO(andreas_s): Make a mixin in os-dpm and move this there
38
+        # (also required in nova-dpm)
39
+        tempfiles = []
40
+        for (basename, contents) in files:
41
+            if not os.path.isabs(basename):
42
+                # create all the tempfiles in a tempdir
43
+                tmpdir = tempfile.mkdtemp()
44
+                path = os.path.join(tmpdir, basename + ext)
45
+                # the path can start with a subdirectory so create
46
+                # it if it doesn't exist yet
47
+                if not os.path.exists(os.path.dirname(path)):
48
+                    os.makedirs(os.path.dirname(path))
49
+            else:
50
+                path = basename + ext
51
+            fd = os.open(path, os.O_CREAT | os.O_WRONLY)
52
+            tempfiles.append(path)
53
+            try:
54
+                os.write(fd, contents.encode('utf-8'))
55
+            finally:
56
+                os.close(fd)
57
+        return tempfiles
58
+
59
+    def test_object(self):
60
+        opt = MultiNetworkAdapterMappingOpt("mapping", help="foo-help")
61
+        self.assertEqual("foo-help", opt.help)
62
+        self.assertEqual("mapping", opt.name)
63
+        self.assertEqual(NetworkAdapterMappingType, type(opt.type))
64
+
65
+    def test_config_single_set_override(self):
66
+        mapping = ["physnet:aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:0"]
67
+        opt = MultiNetworkAdapterMappingOpt("mapping")
68
+        cfg.CONF.register_opt(opt)
69
+        self.flags(mapping=mapping)
70
+        self.assertEqual([("physnet", "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
71
+                           "0")], cfg.CONF.mapping)
72
+
73
+    def test_config_multiple_set_override(self):
74
+        mapping = ["physnet:aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:0",
75
+                   "net2:bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"]
76
+        opt = MultiNetworkAdapterMappingOpt("mapping")
77
+        cfg.CONF.register_opt(opt)
78
+        self.flags(mapping=mapping)
79
+        expected_mapping = [
80
+            ("physnet", "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "0"),
81
+            ("net2", "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "0")]
82
+        self.assertEqual(expected_mapping, cfg.CONF.mapping)
83
+
84
+    def test_config_multiple_with_conf_file(self):
85
+        mapping = "physnet:aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:0"
86
+        other_mapping = "net2:bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
87
+        paths = self.create_tempfiles([
88
+            ('test', '[DEFAULT]\nmapping = ' + mapping + '\n'
89
+             'mapping = ' + other_mapping + '\n')])
90
+
91
+        conf = self.useFixture(Config())
92
+        conf.register_opt(MultiNetworkAdapterMappingOpt("mapping"))
93
+        conf.conf(args=['--config-file', paths[0]])
94
+        expected_mapping = [
95
+            ("physnet", "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "0"),
96
+            ("net2", "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "0")]
97
+        self.assertEqual(expected_mapping, cfg.CONF.mapping)

+ 91
- 0
networking_dpm/tests/unit/conf/test_types.py View File

@@ -0,0 +1,91 @@
1
+# Copyright 2017 IBM Corp. All Rights Reserved.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+#
7
+#    http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+# See the License for the specific language governing permissions and
13
+# limitations under the License.
14
+
15
+from networking_dpm.conf.types import MAPPING_REGEX
16
+from networking_dpm.conf.types import NetworkAdapterMappingType
17
+from networking_dpm.tests import base
18
+
19
+VALID_DPM_OBJECT_ID = "fa1f2466-12df-311a-804c-4ed2cc1d656b"
20
+VALID_DPM_OBJECT_ID_UC = "FA1F2466-12DF-311A-804C-4ED2CC1D656B"
21
+VALID_NETWORK_MAPPING = "physnet:" + VALID_DPM_OBJECT_ID + ":0"
22
+VALID_NETWORK_MAPPING_NO_PORT1 = "physnet:" + VALID_DPM_OBJECT_ID + ":"
23
+VALID_NETWORK_MAPPING_NO_PORT2 = "physnet:" + VALID_DPM_OBJECT_ID
24
+
25
+
26
+class TestNetworkAdapterMappingType(base.BaseTestCase):
27
+    conf_type = NetworkAdapterMappingType()
28
+
29
+    def test_valid_mapping(self):
30
+        net, adapter_id, port = self.conf_type(VALID_NETWORK_MAPPING)
31
+        self.assertEqual("physnet", net)
32
+        self.assertEqual("0", port)
33
+        self.assertEqual(VALID_DPM_OBJECT_ID, adapter_id)
34
+
35
+    def test_upper_case_to_lower_case_adapter_id(self):
36
+        net, adapter_id, port = self.conf_type(
37
+            "physnet:" + VALID_DPM_OBJECT_ID_UC + ":0")
38
+        self.assertEqual(VALID_DPM_OBJECT_ID, adapter_id)
39
+
40
+    def test_keep_case_physnet(self):
41
+        net, adapter_id, port = self.conf_type(
42
+            "PHysnet:" + VALID_DPM_OBJECT_ID_UC + ":0")
43
+        self.assertEqual("PHysnet", net)
44
+
45
+    def test_missing_port_default_1_ok(self):
46
+        net, adapter_id, port = self.conf_type(VALID_NETWORK_MAPPING_NO_PORT1)
47
+        self.assertEqual("physnet", net)
48
+        self.assertEqual("0", port)
49
+        self.assertEqual(VALID_DPM_OBJECT_ID, adapter_id)
50
+
51
+    def test_missing_port_default_2_ok(self):
52
+        net, adapter_id, port = self.conf_type(VALID_NETWORK_MAPPING_NO_PORT2)
53
+        self.assertEqual("physnet", net)
54
+        self.assertEqual("0", port)
55
+        self.assertEqual(VALID_DPM_OBJECT_ID, adapter_id)
56
+
57
+    def test_empty_value_fail(self):
58
+        self.assertRaises(ValueError, self.conf_type, '')
59
+
60
+    def test_invalid_value_fail(self):
61
+        self.assertRaises(ValueError, self.conf_type, 'foobar')
62
+
63
+    def test_invalid_port_fail(self):
64
+        self.assertRaises(ValueError, self.conf_type,
65
+                          VALID_NETWORK_MAPPING_NO_PORT1 + "2")
66
+
67
+    def test_invalid_port_type_fail(self):
68
+        self.assertRaises(ValueError, self.conf_type,
69
+                          VALID_NETWORK_MAPPING_NO_PORT1 + "a")
70
+
71
+    def test_invalid_adapter_id_fail(self):
72
+        self.assertRaises(ValueError, self.conf_type, "physnet:foo:1")
73
+
74
+    def test_invalid_pysnet_fail(self):
75
+        invalid = ["phys:net:" + VALID_DPM_OBJECT_ID,
76
+                   "phys:net:" + VALID_DPM_OBJECT_ID + ":",
77
+                   "phys:net:" + VALID_DPM_OBJECT_ID + ":1",
78
+                   "foo:phys:net:" + VALID_DPM_OBJECT_ID,
79
+                   ":net:" + VALID_DPM_OBJECT_ID,
80
+                   "::net:" + VALID_DPM_OBJECT_ID]
81
+        for conf in invalid:
82
+            self.assertRaises(ValueError, self.conf_type, conf)
83
+
84
+    def test_repr(self):
85
+        self.assertEqual(
86
+            "String(regex='" + MAPPING_REGEX + "')",
87
+            repr(self.conf_type))
88
+
89
+    def test_equal(self):
90
+        self.assertTrue(
91
+            NetworkAdapterMappingType() == NetworkAdapterMappingType())

Loading…
Cancel
Save