Browse Source

Merge "Detection of config errors for netmiko drivers"

Zuul 8 months ago
parent
commit
4d3a08b1cc

+ 40
- 4
networking_generic_switch/devices/netmiko_devices/__init__.py View File

@@ -48,6 +48,13 @@ class NetmikoSwitch(devices.GenericSwitchDevice):
48 48
 
49 49
     SAVE_CONFIGURATION = None
50 50
 
51
+    ERROR_MSG_PATTERNS = ()
52
+    """Sequence of error message patterns.
53
+
54
+    Sequence of re.RegexObject objects representing patterns to check for in
55
+    device output that indicate a failure to apply configuration.
56
+    """
57
+
51 58
     def __init__(self, device_cfg):
52 59
         super(NetmikoSwitch, self).__init__(device_cfg)
53 60
         device_type = self.config.get('device_type', '')
@@ -162,7 +169,8 @@ class NetmikoSwitch(devices.GenericSwitchDevice):
162 169
             cmds += self._format_commands(self.ADD_NETWORK_TO_TRUNK,
163 170
                                           port=port,
164 171
                                           segmentation_id=segmentation_id)
165
-        self.send_commands_to_device(cmds)
172
+        output = self.send_commands_to_device(cmds)
173
+        self.check_output(output, 'add network')
166 174
 
167 175
     def del_network(self, segmentation_id, network_id):
168 176
         # NOTE(zhenguo): Remove dashes from uuid as on most devices 32 chars
@@ -176,7 +184,8 @@ class NetmikoSwitch(devices.GenericSwitchDevice):
176 184
         cmds += self._format_commands(self.DELETE_NETWORK,
177 185
                                       segmentation_id=segmentation_id,
178 186
                                       network_id=network_id)
179
-        self.send_commands_to_device(cmds)
187
+        output = self.send_commands_to_device(cmds)
188
+        self.check_output(output, 'delete network')
180 189
 
181 190
     def plug_port_to_network(self, port, segmentation_id):
182 191
         cmds = []
@@ -190,7 +199,8 @@ class NetmikoSwitch(devices.GenericSwitchDevice):
190 199
             self.PLUG_PORT_TO_NETWORK,
191 200
             port=port,
192 201
             segmentation_id=segmentation_id)
193
-        self.send_commands_to_device(cmds)
202
+        output = self.send_commands_to_device(cmds)
203
+        self.check_output(output, 'plug port')
194 204
 
195 205
     def delete_port(self, port, segmentation_id):
196 206
         cmds = self._format_commands(self.DELETE_PORT,
@@ -206,7 +216,8 @@ class NetmikoSwitch(devices.GenericSwitchDevice):
206 216
                 self.PLUG_PORT_TO_NETWORK,
207 217
                 port=port,
208 218
                 segmentation_id=ngs_port_default_vlan)
209
-        self.send_commands_to_device(cmds)
219
+        output = self.send_commands_to_device(cmds)
220
+        self.check_output(output, 'unplug port')
210 221
 
211 222
     def send_config_set(self, net_connect, cmd_set):
212 223
         """Send a set of configuration lines to the device.
@@ -233,3 +244,28 @@ class NetmikoSwitch(devices.GenericSwitchDevice):
233 244
                 LOG.warning("Saving config is not supported for %s,"
234 245
                             " all changes will be lost after switch"
235 246
                             " reboot", self.config['device_type'])
247
+
248
+    def check_output(self, output, operation):
249
+        """Check the output from the device following an operation.
250
+
251
+        Drivers should implement this method to handle output from devices and
252
+        perform any checks necessary to validate that the configuration was
253
+        applied successfully.
254
+
255
+        :param output: Output from the device.
256
+        :param operation: Operation being attempted. One of 'add network',
257
+            'delete network', 'plug port', 'unplug port'.
258
+        :raises: GenericSwitchNetmikoConfigError if the driver detects that an
259
+            error has occurred.
260
+        """
261
+        if not output:
262
+            return
263
+
264
+        for pattern in self.ERROR_MSG_PATTERNS:
265
+            if pattern.search(output):
266
+                msg = ("Found invalid configuration in device response. "
267
+                       "Operation: %(operation)s. Output: %(output)s" %
268
+                       {'operation': operation, 'output': output})
269
+                raise exc.GenericSwitchNetmikoConfigError(
270
+                    config=device_utils.sanitise_config(self.config),
271
+                    error=msg)

+ 2
- 4
networking_generic_switch/devices/netmiko_devices/brocade.py View File

@@ -76,7 +76,5 @@ class BrocadeFastIron(netmiko_devices.NetmikoSwitch):
76 76
 
77 77
     def plug_port_to_network(self, port, segmentation_id):
78 78
         self.clean_port_vlan_if_necessary(port)
79
-        self.send_commands_to_device(
80
-            self._format_commands(self.PLUG_PORT_TO_NETWORK,
81
-                                  port=port,
82
-                                  segmentation_id=segmentation_id))
79
+        super(BrocadeFastIron, self).plug_port_to_network(port,
80
+                                                          segmentation_id)

+ 64
- 16
networking_generic_switch/tests/unit/netmiko/test_netmiko_base.py View File

@@ -12,6 +12,8 @@
12 12
 #    License for the specific language governing permissions and limitations
13 13
 #    under the License.
14 14
 
15
+import re
16
+
15 17
 import fixtures
16 18
 import mock
17 19
 import netmiko
@@ -45,56 +47,88 @@ class NetmikoSwitchTestBase(fixtures.TestWithFixtures):
45 47
 class TestNetmikoSwitch(NetmikoSwitchTestBase):
46 48
 
47 49
     @mock.patch('networking_generic_switch.devices.netmiko_devices.'
48
-                'NetmikoSwitch.send_commands_to_device')
49
-    def test_add_network(self, m_sctd):
50
+                'NetmikoSwitch.send_commands_to_device',
51
+                return_value='fake output')
52
+    @mock.patch('networking_generic_switch.devices.netmiko_devices.'
53
+                'NetmikoSwitch.check_output')
54
+    def test_add_network(self, m_check, m_sctd):
50 55
         self.switch.add_network(22, '0ae071f5-5be9-43e4-80ea-e41fefe85b21')
51 56
         m_sctd.assert_called_with([])
57
+        m_check.assert_called_once_with('fake output', 'add network')
52 58
 
53 59
     @mock.patch('networking_generic_switch.devices.netmiko_devices.'
54
-                'NetmikoSwitch.send_commands_to_device')
55
-    def test_add_network_with_trunk_ports(self, m_sctd):
60
+                'NetmikoSwitch.send_commands_to_device',
61
+                return_value='fake output')
62
+    @mock.patch('networking_generic_switch.devices.netmiko_devices.'
63
+                'NetmikoSwitch.check_output')
64
+    def test_add_network_with_trunk_ports(self, m_check, m_sctd):
56 65
         switch = self._make_switch_device({'ngs_trunk_ports': 'port1,port2'})
57 66
         switch.add_network(22, '0ae071f5-5be9-43e4-80ea-e41fefe85b21')
58 67
         m_sctd.assert_called_with([])
68
+        m_check.assert_called_once_with('fake output', 'add network')
59 69
 
60 70
     @mock.patch('networking_generic_switch.devices.netmiko_devices.'
61
-                'NetmikoSwitch.send_commands_to_device')
62
-    def test_del_network(self, m_sctd):
71
+                'NetmikoSwitch.send_commands_to_device',
72
+                return_value='fake output')
73
+    @mock.patch('networking_generic_switch.devices.netmiko_devices.'
74
+                'NetmikoSwitch.check_output')
75
+    def test_del_network(self, m_check, m_sctd):
63 76
         self.switch.del_network(22, '0ae071f5-5be9-43e4-80ea-e41fefe85b21')
64 77
         m_sctd.assert_called_with([])
78
+        m_check.assert_called_once_with('fake output', 'delete network')
65 79
 
66 80
     @mock.patch('networking_generic_switch.devices.netmiko_devices.'
67
-                'NetmikoSwitch.send_commands_to_device')
68
-    def test_del_network_with_trunk_ports(self, m_sctd):
81
+                'NetmikoSwitch.send_commands_to_device',
82
+                return_value='fake output')
83
+    @mock.patch('networking_generic_switch.devices.netmiko_devices.'
84
+                'NetmikoSwitch.check_output')
85
+    def test_del_network_with_trunk_ports(self, m_check, m_sctd):
69 86
         switch = self._make_switch_device({'ngs_trunk_ports': 'port1,port2'})
70 87
         switch.del_network(22, '0ae071f5-5be9-43e4-80ea-e41fefe85b21')
71 88
         m_sctd.assert_called_with([])
89
+        m_check.assert_called_once_with('fake output', 'delete network')
72 90
 
73 91
     @mock.patch('networking_generic_switch.devices.netmiko_devices.'
74
-                'NetmikoSwitch.send_commands_to_device')
75
-    def test_plug_port_to_network(self, m_sctd):
92
+                'NetmikoSwitch.send_commands_to_device',
93
+                return_value='fake output')
94
+    @mock.patch('networking_generic_switch.devices.netmiko_devices.'
95
+                'NetmikoSwitch.check_output')
96
+    def test_plug_port_to_network(self, m_check, m_sctd):
76 97
         self.switch.plug_port_to_network(2222, 22)
77 98
         m_sctd.assert_called_with([])
99
+        m_check.assert_called_once_with('fake output', 'plug port')
78 100
 
79 101
     @mock.patch('networking_generic_switch.devices.netmiko_devices.'
80
-                'NetmikoSwitch.send_commands_to_device')
81
-    def test_plug_port_has_default_vlan(self, m_sctd):
102
+                'NetmikoSwitch.send_commands_to_device',
103
+                return_value='fake output')
104
+    @mock.patch('networking_generic_switch.devices.netmiko_devices.'
105
+                'NetmikoSwitch.check_output')
106
+    def test_plug_port_has_default_vlan(self, m_check, m_sctd):
82 107
         switch = self._make_switch_device({'ngs_port_default_vlan': '20'})
83 108
         switch.plug_port_to_network(2222, 22)
84 109
         m_sctd.assert_called_with([])
110
+        m_check.assert_called_once_with('fake output', 'plug port')
85 111
 
86 112
     @mock.patch('networking_generic_switch.devices.netmiko_devices.'
87
-                'NetmikoSwitch.send_commands_to_device')
88
-    def test_delete_port(self, m_sctd):
113
+                'NetmikoSwitch.send_commands_to_device',
114
+                return_value='fake output')
115
+    @mock.patch('networking_generic_switch.devices.netmiko_devices.'
116
+                'NetmikoSwitch.check_output')
117
+    def test_delete_port(self, m_check, m_sctd):
89 118
         self.switch.delete_port(2222, 22)
90 119
         m_sctd.assert_called_with([])
120
+        m_check.assert_called_once_with('fake output', 'unplug port')
91 121
 
92 122
     @mock.patch('networking_generic_switch.devices.netmiko_devices.'
93
-                'NetmikoSwitch.send_commands_to_device')
94
-    def test_delete_port_has_default_vlan(self, m_sctd):
123
+                'NetmikoSwitch.send_commands_to_device',
124
+                return_value='fake output')
125
+    @mock.patch('networking_generic_switch.devices.netmiko_devices.'
126
+                'NetmikoSwitch.check_output')
127
+    def test_delete_port_has_default_vlan(self, m_check, m_sctd):
95 128
         switch = self._make_switch_device({'ngs_port_default_vlan': '20'})
96 129
         switch.delete_port(2222, 22)
97 130
         m_sctd.assert_called_with([])
131
+        m_check.assert_called_once_with('fake output', 'unplug port')
98 132
 
99 133
     def test__format_commands(self):
100 134
         self.switch._format_commands(
@@ -290,3 +324,17 @@ class TestNetmikoSwitch(NetmikoSwitchTestBase):
290 324
                                           timeout=120)
291 325
         lock_mock.return_value.__exit__.assert_called_once()
292 326
         lock_mock.return_value.__enter__.assert_called_once()
327
+
328
+    def test_check_output(self):
329
+        self.switch.check_output('fake output', 'fake op')
330
+
331
+    def test_check_output_error(self):
332
+        self.switch.ERROR_MSG_PATTERNS = (re.compile('fake error message'),)
333
+        output = """
334
+fake switch command
335
+fake error message
336
+"""
337
+        msg = ("Found invalid configuration in device response. Operation: "
338
+               "fake op. Output: %s" % output)
339
+        self.assertRaisesRegexp(exc.GenericSwitchNetmikoConfigError, msg,
340
+                                self.switch.check_output, output, 'fake op')

Loading…
Cancel
Save