Browse Source

Support for enable password configuration

When enable password is configured in device, ML2 plugin
will not be able to configure the device via SSH or TELNET.

Now this is supported by reading the response of each command
and identifying the prompt.

Change-Id: I3344f63b2f076809046fdbe92b9c9227bc0f1e1c
Closes-Bug: 1481161
tags/mitaka-eol
Pradeep Sathasivam 3 years ago
parent
commit
e2c8ecd218

+ 4
- 0
etc/neutron/plugins/brocade/brocade_mlx.ini View File

@@ -9,6 +9,8 @@
9 9
 # password = The SSH password to use to connect to device
10 10
 # physical_networks = Allowed physical networks for VLAN configuration
11 11
 # ports = Comma separated list of ports on the switch which needs to be tagged to VLAN
12
+# enable_username = The username for the enable configuration prompt, if any.
13
+# enable_password = The password for the enable configuration prompt, if any.
12 14
 #
13 15
 # Example:
14 16
 # [mlx]
@@ -17,3 +19,5 @@
17 19
 # password = password
18 20
 # physical_networks = physnet1
19 21
 # ports = 3/3, 3/9
22
+# enable_username = admin
23
+# enable_password = password

+ 6
- 0
etc/neutron/plugins/ml2/ml2_conf_brocade_fi_ni.ini View File

@@ -11,6 +11,8 @@
11 11
 # ports = Ports on the switch which needs to tagged to VLAN. Multiple ports can be separated by a comma.
12 12
 # transport = Protocol to use for device connection(SSH or Telnet). Default is SSH. This is an optional parameter
13 13
 # ostype   = Optional parameter, which will identify the firmware version(FI/NI)
14
+# enable_username = The username for the enable configuration prompt, if any.
15
+# enable_password = The password for the enable configuration prompt, if any.
14 16
 #
15 17
 # Example:
16 18
 # [icx-1]
@@ -21,6 +23,8 @@
21 23
 # ports = 1/1/1, 1/1/2
22 24
 # transport = SSH
23 25
 # ostype   = FI
26
+# enable_username = admin
27
+# enable_password = password
24 28
 
25 29
 # Example:
26 30
 # [mlx]
@@ -31,3 +35,5 @@
31 35
 # ports = 3/3, 3/9
32 36
 # transport = SSH
33 37
 # ostype   = NI
38
+# enable_username = admin
39
+# enable_password = password

+ 15
- 45
networking_brocade/mlx/ml2/device_connector.py View File

@@ -18,7 +18,7 @@
18 18
     Decides which connector to use - TELNET or SSH, based on
19 19
     the argument passed
20 20
 """
21
-import networking_brocade.mlx.ml2.commands as Commands
21
+from networking_brocade.mlx.ml2 import commands
22 22
 from oslo_log import log as logging
23 23
 
24 24
 LOG = logging.getLogger(__name__)
@@ -36,31 +36,10 @@ class DeviceConnector(object):
36 36
         self.host = device_info.get('address')
37 37
         self.username = device_info.get('username')
38 38
         self.password = device_info.get('password')
39
+        self.enable_username = device_info.get('enable_username')
40
+        self.enable_password = device_info.get('enable_password')
39 41
         self.transport = device_info.get('transport')
40 42
 
41
-    def enter_configuration_mode(self):
42
-        """
43
-        Enter configuration mode. First it enters enable mode
44
-        and then to configuration mode. There should be no
45
-        Enable password.
46
-        """
47
-
48
-        self.write(Commands.ENABLE_TERMINAL_CMD)
49
-        self.write(Commands.CONFIGURE_TERMINAL_CMD)
50
-
51
-    def exit_configuration_mode(self):
52
-        """
53
-        Exit Configuration mode.
54
-        """
55
-        self.send_exit(2)
56
-
57
-    def exit_from_device(self):
58
-        """
59
-        Exit Configuration mode and device.
60
-        """
61
-        self.exit_configuration_mode()
62
-        self.send_exit(1)
63
-
64 43
     def create_vlan(self, vlanid, ports):
65 44
         """
66 45
         Creates VLAN and tags the ports to the created VLAN
@@ -72,7 +51,7 @@ class DeviceConnector(object):
72 51
                   "device %(host)s", {'vlanid': vlanid, 'host': self.host})
73 52
         self.enter_configuration_mode()
74 53
         self.write(
75
-            Commands.CONFIGURE_VLAN.format(
54
+            commands.CONFIGURE_VLAN.format(
76 55
                 vlan_id=vlanid))
77 56
         LOG.debug(
78 57
             "Created VLAN with id %(vlanid)s on device %(host)s", {
@@ -80,8 +59,6 @@ class DeviceConnector(object):
80 59
         for port in ports:
81 60
             self.tag_port(port)
82 61
             LOG.debug("tagged port %(port)s", {'port': port})
83
-        self.send_exit(1)
84
-        self.exit_from_device()
85 62
         return self.read_response()
86 63
 
87 64
     def tag_port(self, port):
@@ -92,7 +69,7 @@ class DeviceConnector(object):
92 69
         LOG.debug("DeviceConnector:tag_port:Tagging port %(portid)s on device"
93 70
                   " %(host)s", {'portid': port, 'host': self.host})
94 71
         self.write(
95
-            Commands.CONFIGURE_ETHERNET.format(
72
+            commands.CONFIGURE_ETHERNET.format(
96 73
                 port_number=port))
97 74
 
98 75
     def delete_vlan(self, vlan_id):
@@ -106,12 +83,11 @@ class DeviceConnector(object):
106 83
                                                      'host': self.host})
107 84
         self.enter_configuration_mode()
108 85
         self.write(
109
-            Commands.DELETE_CONFIGURED_VLAN.format(
86
+            commands.DELETE_CONFIGURED_VLAN.format(
110 87
                 vlan_id=vlan_id))
111 88
         LOG.debug(
112 89
             "Deleted VLAN with id %(vlan_id)s on device %(host)s", {
113 90
                 'vlan_id': vlan_id, 'host': self.host})
114
-        self.exit_from_device()
115 91
         return self.read_response()
116 92
 
117 93
     def get_version(self):
@@ -121,10 +97,9 @@ class DeviceConnector(object):
121 97
         """
122 98
         LOG.debug("DeviceConnector:get_version:Executing show version for "
123 99
                   "device %(host)s", {'host': self.host})
124
-        self.write(Commands.SHOW_VERSION)
125
-        self.write(Commands.CTRL_C)
126
-        self.send_exit(1)
127
-        return self.read_response(read_lines=False)
100
+        self.write(commands.SHOW_VERSION)
101
+        self.write(commands.CTRL_C)
102
+        return self.read_response()
128 103
 
129 104
     def create_l3_router(self, vlan_id, gateway_ip_cidr):
130 105
         """Configures a Router Interface interface
@@ -144,7 +119,6 @@ class DeviceConnector(object):
144 119
         LOG.debug(("DeviceConnector:create_l3_router:Configured router "
145 120
                    "interface with id %(vlanid)s on device %(host)s"),
146 121
                   {'vlanid': vlan_id, 'host': self.host})
147
-        self.exit_from_device()
148 122
         return self.read_response()
149 123
 
150 124
     def _create_router_interface(self, vlan_id):
@@ -158,16 +132,15 @@ class DeviceConnector(object):
158 132
                   "%(host)s", {'vlanid': vlan_id,
159 133
                                'host': self.host})
160 134
         self.write(
161
-            Commands.CONFIGURE_VLAN.format(
135
+            commands.CONFIGURE_VLAN.format(
162 136
                 vlan_id=vlan_id))
163 137
         self.write(
164
-            Commands.CONFIGURE_ROUTER_INTERFACE.format(
138
+            commands.CONFIGURE_ROUTER_INTERFACE.format(
165 139
                 vlan_id=vlan_id))
166 140
         LOG.debug("DeviceConnector:_create_router_interface:Created l3 "
167 141
                   "router interface %(vlanid)s on device "
168 142
                   "%(host)s", {'vlanid': vlan_id,
169 143
                                'host': self.host})
170
-        self.send_exit(1)
171 144
 
172 145
     def _configure_ipaddress(self, vlan_id, gateway_ip_cidr):
173 146
         """Assigns Gateway ip for the configured vlan
@@ -182,17 +155,16 @@ class DeviceConnector(object):
182 155
                                 'vlanid': vlan_id,
183 156
                                 'host': self.host})
184 157
         self.write(
185
-            Commands.CONFIGURE_INTERFACE.format(
158
+            commands.CONFIGURE_INTERFACE.format(
186 159
                 vlan_id=vlan_id))
187 160
         self.write(
188
-            Commands.CONFIGURE_GATEWAY_IP.format(
161
+            commands.CONFIGURE_GATEWAY_IP.format(
189 162
                 gateway_ip_addr=gateway_ip_cidr))
190 163
         LOG.debug("DeviceConnector:_configure_ipaddress:Assigned IP address"
191 164
                   " %(ipaddr)s to the router interface %(vlanid)s on device"
192 165
                   " %(host)s", {'ipaddr': gateway_ip_cidr,
193 166
                                 'vlanid': vlan_id,
194 167
                                 'host': self.host})
195
-        self.send_exit(1)
196 168
 
197 169
     def delete_l3_router(self, vlan_id):
198 170
         """
@@ -209,15 +181,13 @@ class DeviceConnector(object):
209 181
                    " %(vlan_id)s from vlan %(vlan_id)s on device %(host)s"),
210 182
                   {'vlan_id': vlan_id, 'host': self.host})
211 183
         self.write(
212
-            Commands.CONFIGURE_VLAN.format(vlan_id=vlan_id))
184
+            commands.CONFIGURE_VLAN.format(vlan_id=vlan_id))
213 185
         self.write(
214
-            Commands.DELETE_ROUTER_INTERFACE.format(
186
+            commands.DELETE_ROUTER_INTERFACE.format(
215 187
                 vlan_id=vlan_id))
216
-        self.send_exit(1)
217 188
 
218 189
         LOG.debug(("DeviceConnector:delete_l3_router:Deleted router interface"
219 190
                    " %(vlan_id)s from vlan %(vlan_id)s on device %(host)s"),
220 191
                   {'vlan_id': vlan_id, 'host': self.host})
221 192
 
222
-        self.exit_from_device()
223 193
         return self.read_response()

+ 8
- 0
networking_brocade/mlx/ml2/fi_ni/brcd_config.py View File

@@ -45,6 +45,10 @@ ML2_BROCADE = [cfg.StrOpt('address', default='',
45 45
                           help=('OS type of the device.  NI is NetIron '
46 46
                                 'for MLX switches. FI is FastIron for '
47 47
                                 'ICX switches.')),
48
+               cfg.StrOpt('enable_username', default='admin',
49
+                          help=('Username of the enable config prompt')),
50
+               cfg.StrOpt('enable_password', default='password', secret=True,
51
+                          help=('Password of the enable config prompt')),
48 52
                ]
49 53
 L3_BROCADE = [cfg.StrOpt('address', default='',
50 54
                          help=('The IP address of the MLX switch')),
@@ -58,6 +62,10 @@ L3_BROCADE = [cfg.StrOpt('address', default='',
58 62
               cfg.StrOpt('ports', default='',
59 63
                          help=('Ports to be tagged in the VLAN being '
60 64
                                'configured on the switch')),
65
+              cfg.StrOpt('enable_username', default='admin',
66
+                         help=('Username of the enable config prompt')),
67
+              cfg.StrOpt('enable_password', default='password', secret=True,
68
+                         help=('Password of the enable config prompt')),
61 69
               ]
62 70
 cfg.CONF.register_opts(SWITCHES, 'ml2_brocade_fi_ni')
63 71
 cfg.CONF.register_opts(SWITCHES, 'l3_brocade_mlx')

+ 77
- 37
networking_brocade/mlx/ml2/ssh_connector.py View File

@@ -15,19 +15,29 @@
15 15
 
16 16
 """ Implementation of SSH Connector"""
17 17
 
18
-import networking_brocade.mlx.ml2.commands as Commands
19
-from networking_brocade.mlx.ml2.device_connector import (
20
-    DeviceConnector as DevConn)
18
+from networking_brocade.mlx.ml2 import device_connector as dev_conn
21 19
 from neutron.i18n import _LE
22 20
 from oslo_log import log as logging
21
+
23 22
 import paramiko
23
+import time
24 24
 
25 25
 LOG = logging.getLogger(__name__)
26
-WRITE = "wb"
27
-READ = "rb"
26
+PROMPT = '>'
27
+ENABLE_PROMPT = '#'
28
+ENABLE_USERNAME_PROMPT = 'User Name:'
29
+ENABLE_PASSWORD_PROMPT = 'Password:'
30
+CONFIG_MODE = '(config)#'
31
+
32
+CONFIG_COMMAND = "conf t\n"
33
+ENABLE_COMMAND = "en\n"
34
+NEW_LINE = "\n"
35
+
36
+TIMEOUT = 30.0
37
+SLEEP_TIME = 1
28 38
 
29 39
 
30
-class SSHConnector(DevConn):
40
+class SSHConnector(dev_conn.DeviceConnector):
31 41
 
32 42
     """
33 43
     Uses SSH to connect to device
@@ -46,10 +56,10 @@ class SSHConnector(DevConn):
46 56
                 username=self.username,
47 57
                 password=self.password)
48 58
 
49
-            channel = self.connector.invoke_shell()
50
-            self.stdin_stream = channel.makefile(WRITE)
51
-            self.stdout_stream = channel.makefile(READ)
52
-            self.stderr_stream = channel.makefile(READ)
59
+            self.channel = self.connector.invoke_shell()
60
+            self.channel.settimeout(TIMEOUT)
61
+            self.channel_data = str()
62
+            self._enter_prompt(False)
53 63
 
54 64
         except Exception as e:
55 65
             LOG.exception(_LE("Connect failed to switch %(host)s with error"
@@ -57,45 +67,75 @@ class SSHConnector(DevConn):
57 67
                           {'host': self.host, 'error': e.args})
58 68
             raise Exception(_("Connection Failed"))
59 69
 
70
+    def _send_command(self, prompt, command):
71
+        """
72
+        Executes the command passed, if the response matches the prompt
73
+        """
74
+        execution_state = False
75
+        if self.channel_data.endswith(prompt):
76
+            self.channel.send(command)
77
+            execution_state = True
78
+        return execution_state
79
+
80
+    def _enter_prompt(self, is_config_mode):
81
+        """
82
+        Enters enable prompt mode or config mode based on the parameter
83
+
84
+        param:is_config_mode: if this is True, will enter config mode, else
85
+                              it will make sure it is in enable prompt.
86
+        """
87
+        commands = []
88
+        prompt_command = {}
89
+        if is_config_mode:
90
+            prompts = [ENABLE_PROMPT, CONFIG_MODE]
91
+            prompt_command = {ENABLE_PROMPT: CONFIG_COMMAND,
92
+                              CONFIG_MODE: NEW_LINE}
93
+        else:
94
+            prompts = [PROMPT, ENABLE_USERNAME_PROMPT,
95
+                       ENABLE_PASSWORD_PROMPT, ENABLE_PROMPT]
96
+            prompt_command = {PROMPT: ENABLE_COMMAND,
97
+                              ENABLE_USERNAME_PROMPT: self.enable_username +
98
+                              NEW_LINE,
99
+                              ENABLE_PASSWORD_PROMPT: self.enable_password +
100
+                              NEW_LINE,
101
+                              ENABLE_PROMPT: NEW_LINE}
102
+        cmd_executed = True
103
+        # Send new line so that channel will have something to read for the
104
+        # first time.
105
+        self.channel.send(NEW_LINE)
106
+        index = 0
107
+        while index < len(commands):
108
+            prompt = prompts[index]
109
+            if cmd_executed:
110
+                self.channel_data += self.channel.recv(9999)
111
+            command = prompt_command.get(prompt)
112
+            cmd_executed = self._send_command(prompt, command)
113
+            index += 1
114
+            time.sleep(SLEEP_TIME)
115
+
116
+    def enter_configuration_mode(self):
117
+        """
118
+        This method will ensure the session is in configuration mode
119
+        """
120
+        self._enter_prompt(True)
121
+
60 122
     def write(self, command):
61 123
         """
62 124
         Write from input stream to device
63 125
 
64 126
         :param:command: Command to be executed on the device
65 127
         """
66
-        self.stdin_stream.write(command)
67
-        self.stdin_stream.flush()
128
+        self.channel.send(command)
129
+        time.sleep(SLEEP_TIME)
130
+        self.channel_data += self.channel.recv(9999)
68 131
 
69 132
     def read_response(self, read_lines=True):
70 133
         """Read the response from the output stream.
71
-
72
-        :param:read_lines: Boolean value which indicated to read multiple line
73
-            or single line. It is true by default.
74
-        :returns: Response from the device as list when read_lines is True or
75
-            string when read_lines is false.
76 134
         """
77
-        response = None
78
-        if read_lines:
79
-            response = self.stdout_stream.readlines()
80
-        else:
81
-            response = self.stdout_stream.read()
82
-        return response
83
-
84
-    def send_exit(self, count):
85
-        """Send Exit command.
86
-
87
-        :param:count: Indicates number of times to execute exit command
88
-        """
89
-        index = 0
90
-        while index < count:
91
-            self.stdin_stream.write(Commands.EXIT)
92
-            self.stdin_stream.flush()
93
-            index += 1
135
+        return self.channel_data
94 136
 
95 137
     def close_session(self):
96 138
         """Close SSH session."""
97 139
         if self.connector:
98
-            self.stdin_stream.close()
99
-            self.stdout_stream.close()
100
-            self.stderr_stream.close()
140
+            self.channel.close()
101 141
             self.connector.close()

+ 70
- 13
networking_brocade/mlx/ml2/telnet_connector.py View File

@@ -16,9 +16,9 @@
16 16
 """ Implementation of Telnet Connector """
17 17
 
18 18
 import telnetlib
19
+import time
19 20
 
20
-from networking_brocade.mlx.ml2.device_connector import (
21
-    DeviceConnector as DevConn)
21
+from networking_brocade.mlx.ml2 import device_connector as dev_conn
22 22
 from neutron.i18n import _LE
23 23
 from oslo_log import log as logging
24 24
 
@@ -32,15 +32,23 @@ SUPER_USER_AUTH = '^Password\\:$'
32 32
 TERMINAL_LENGTH = "terminal length 0"
33 33
 
34 34
 END_OF_LINE = "\r"
35
-TELNET_TERMINAL = ">"
35
+PROMPT = ">"
36
+ENABLE_PROMPT = '#'
36 37
 CONFIGURE_TERMINAL = "#"
38
+ENABLE_USERNAME_PROMPT = 'User Name:'
39
+ENABLE_PASSWORD_PROMPT = 'Password:'
40
+CONFIG_MODE = "(config)#"
41
+
42
+RETURN_COMMAND = "\r"
43
+ENABLE_COMMAND = "en\r"
44
+CONFIG_COMMAND = "conf t\r"
37 45
 
38 46
 MIN_TIMEOUT = 2
39 47
 AVG_TIMEOUT = 4
40 48
 MAX_TIMEOUT = 8
41 49
 
42 50
 
43
-class TelnetConnector(DevConn):
51
+class TelnetConnector(dev_conn.DeviceConnector):
44 52
 
45 53
     """
46 54
     Uses Telnet to connect to device
@@ -56,13 +64,66 @@ class TelnetConnector(DevConn):
56 64
             self.connector.write(self.username + END_OF_LINE)
57 65
             self.connector.read_until(LOGIN_PASS_TOKEN, AVG_TIMEOUT)
58 66
             self.connector.write(self.password + END_OF_LINE)
59
-            self.connector.read_until(TELNET_TERMINAL, MAX_TIMEOUT)
67
+            self.response = str()
68
+            self._enter_prompt(False)
60 69
         except Exception as e:
61 70
             LOG.exception(_LE("Connect failed to switch %(host)s with error"
62 71
                               " %(error)s"),
63 72
                           {'host': self.host, 'error': e.args})
64 73
             raise Exception(_("Connection Failed"))
65 74
 
75
+    def _send_command(self, prompt, command):
76
+        """
77
+        Executes the command passed, if the response matches the prompt
78
+        """
79
+        execution_state = False
80
+        if self.response.endswith(prompt):
81
+            self.connector.write(command)
82
+            execution_state = True
83
+        return execution_state
84
+
85
+    def _enter_prompt(self, is_config_mode):
86
+        """
87
+        Enters enable prompt mode or config mode based on the parameter
88
+
89
+        param:is_config_mode: if this is True, will enter config mode, else
90
+                              it will make sure it is in enable prompt.
91
+        """
92
+        commands = []
93
+        prompt_command = {}
94
+        if is_config_mode:
95
+            prompts = [ENABLE_PROMPT, CONFIG_MODE]
96
+            prompt_command = {ENABLE_PROMPT: CONFIG_COMMAND,
97
+                              CONFIG_MODE: RETURN_COMMAND}
98
+        else:
99
+            prompts = [PROMPT, ENABLE_USERNAME_PROMPT,
100
+                       ENABLE_PASSWORD_PROMPT, ENABLE_PROMPT]
101
+            prompt_command = {PROMPT: ENABLE_COMMAND,
102
+                              ENABLE_USERNAME_PROMPT: self.enable_username +
103
+                              RETURN_COMMAND,
104
+                              ENABLE_PASSWORD_PROMPT: self.enable_password +
105
+                              RETURN_COMMAND,
106
+                              ENABLE_PROMPT: RETURN_COMMAND}
107
+        cmd_executed = True
108
+        # Send new line so that channel will have something to read for the
109
+        # first time.
110
+        self.connector.write(RETURN_COMMAND)
111
+        index = 0
112
+        while index < len(commands):
113
+            prompt = prompts[index]
114
+            if cmd_executed:
115
+                self.response += self.connector.read_until(prompt, AVG_TIMEOUT)
116
+            command = prompt_command.get(prompt)
117
+            cmd_executed = self._send_command(prompt, command)
118
+            index += 1
119
+            time.sleep(MIN_TIMEOUT)
120
+
121
+    def enter_configuration_mode(self):
122
+        """
123
+        This method will ensure the session is in configuration mode
124
+        """
125
+        self._enter_prompt(True)
126
+
66 127
     def write(self, command):
67 128
         """
68 129
         Write from input stream to device
@@ -70,21 +131,17 @@ class TelnetConnector(DevConn):
70 131
         :param:command: Command to be executed on the device
71 132
         """
72 133
         self.connector.write(command)
134
+        self.response += self.connector.read_until(PROMPT, MAX_TIMEOUT)
73 135
 
74
-    def read_response(self, read_lines=True):
136
+    def read_response(self):
75 137
         """Read the response from the output stream.
76 138
 
77
-        :param:read_lines: This is used only by the SSH connector.
78
-        :returns: Response from the device as list.
139
+        :returns: Response from the device as string.
79 140
         """
80
-        return self.connector.read_until(CONFIGURE_TERMINAL, MIN_TIMEOUT)
141
+        return self.response
81 142
 
82 143
     def close_session(self):
83 144
         """Close TELNET session."""
84 145
         if self.connector:
85 146
             self.connector.close()
86 147
             self.connector = None
87
-
88
-    def send_exit(self, count):
89
-        """No operation. Used by SSH connector only."""
90
-        pass

+ 2
- 3
networking_brocade/test_discover/test_discover.py View File

@@ -28,10 +28,9 @@ def load_tests(loader, tests, pattern):
28 28
     base_path = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
29 29
     base_path = os.path.split(base_path)[0]
30 30
     test_dirs = {'./networking_brocade/tests',
31
-#                 './networking_brocade/vdx/tests/unit/ml2/drivers/brocade',
31
+                 './networking_brocade/vdx/tests/unit/ml2/drivers/brocade',
32 32
                  MLX_TEST_BASE_PATH + '/unit/ml2/drivers/brocade',
33
-                 MLX_TEST_BASE_PATH + '/unit/services/l3_router/brocade',
34
-                 }
33
+                 MLX_TEST_BASE_PATH + '/unit/services/l3_router/brocade'}
35 34
     for test_dir in test_dirs:
36 35
         if not pattern:
37 36
             suite.addTests(loader.discover(test_dir, top_level_dir=base_path))

Loading…
Cancel
Save