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
changes/71/210271/6
Pradeep Sathasivam 8 years ago committed by Angela Smith
parent 9d3c974cbb
commit e2c8ecd218

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

@ -11,6 +11,8 @@
# ports = Ports on the switch which needs to tagged to VLAN. Multiple ports can be separated by a comma.
# transport = Protocol to use for device connection(SSH or Telnet). Default is SSH. This is an optional parameter
# ostype = Optional parameter, which will identify the firmware version(FI/NI)
# enable_username = The username for the enable configuration prompt, if any.
# enable_password = The password for the enable configuration prompt, if any.
#
# Example:
# [icx-1]
@ -21,6 +23,8 @@
# ports = 1/1/1, 1/1/2
# transport = SSH
# ostype = FI
# enable_username = admin
# enable_password = password
# Example:
# [mlx]
@ -31,3 +35,5 @@
# ports = 3/3, 3/9
# transport = SSH
# ostype = NI
# enable_username = admin
# enable_password = password

@ -18,7 +18,7 @@
Decides which connector to use - TELNET or SSH, based on
the argument passed
"""
import networking_brocade.mlx.ml2.commands as Commands
from networking_brocade.mlx.ml2 import commands
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
@ -36,31 +36,10 @@ class DeviceConnector(object):
self.host = device_info.get('address')
self.username = device_info.get('username')
self.password = device_info.get('password')
self.enable_username = device_info.get('enable_username')
self.enable_password = device_info.get('enable_password')
self.transport = device_info.get('transport')
def enter_configuration_mode(self):
"""
Enter configuration mode. First it enters enable mode
and then to configuration mode. There should be no
Enable password.
"""
self.write(Commands.ENABLE_TERMINAL_CMD)
self.write(Commands.CONFIGURE_TERMINAL_CMD)
def exit_configuration_mode(self):
"""
Exit Configuration mode.
"""
self.send_exit(2)
def exit_from_device(self):
"""
Exit Configuration mode and device.
"""
self.exit_configuration_mode()
self.send_exit(1)
def create_vlan(self, vlanid, ports):
"""
Creates VLAN and tags the ports to the created VLAN
@ -72,7 +51,7 @@ class DeviceConnector(object):
"device %(host)s", {'vlanid': vlanid, 'host': self.host})
self.enter_configuration_mode()
self.write(
Commands.CONFIGURE_VLAN.format(
commands.CONFIGURE_VLAN.format(
vlan_id=vlanid))
LOG.debug(
"Created VLAN with id %(vlanid)s on device %(host)s", {
@ -80,8 +59,6 @@ class DeviceConnector(object):
for port in ports:
self.tag_port(port)
LOG.debug("tagged port %(port)s", {'port': port})
self.send_exit(1)
self.exit_from_device()
return self.read_response()
def tag_port(self, port):
@ -92,7 +69,7 @@ class DeviceConnector(object):
LOG.debug("DeviceConnector:tag_port:Tagging port %(portid)s on device"
" %(host)s", {'portid': port, 'host': self.host})
self.write(
Commands.CONFIGURE_ETHERNET.format(
commands.CONFIGURE_ETHERNET.format(
port_number=port))
def delete_vlan(self, vlan_id):
@ -106,12 +83,11 @@ class DeviceConnector(object):
'host': self.host})
self.enter_configuration_mode()
self.write(
Commands.DELETE_CONFIGURED_VLAN.format(
commands.DELETE_CONFIGURED_VLAN.format(
vlan_id=vlan_id))
LOG.debug(
"Deleted VLAN with id %(vlan_id)s on device %(host)s", {
'vlan_id': vlan_id, 'host': self.host})
self.exit_from_device()
return self.read_response()
def get_version(self):
@ -121,10 +97,9 @@ class DeviceConnector(object):
"""
LOG.debug("DeviceConnector:get_version:Executing show version for "
"device %(host)s", {'host': self.host})
self.write(Commands.SHOW_VERSION)
self.write(Commands.CTRL_C)
self.send_exit(1)
return self.read_response(read_lines=False)
self.write(commands.SHOW_VERSION)
self.write(commands.CTRL_C)
return self.read_response()
def create_l3_router(self, vlan_id, gateway_ip_cidr):
"""Configures a Router Interface interface
@ -144,7 +119,6 @@ class DeviceConnector(object):
LOG.debug(("DeviceConnector:create_l3_router:Configured router "
"interface with id %(vlanid)s on device %(host)s"),
{'vlanid': vlan_id, 'host': self.host})
self.exit_from_device()
return self.read_response()
def _create_router_interface(self, vlan_id):
@ -158,16 +132,15 @@ class DeviceConnector(object):
"%(host)s", {'vlanid': vlan_id,
'host': self.host})
self.write(
Commands.CONFIGURE_VLAN.format(
commands.CONFIGURE_VLAN.format(
vlan_id=vlan_id))
self.write(
Commands.CONFIGURE_ROUTER_INTERFACE.format(
commands.CONFIGURE_ROUTER_INTERFACE.format(
vlan_id=vlan_id))
LOG.debug("DeviceConnector:_create_router_interface:Created l3 "
"router interface %(vlanid)s on device "
"%(host)s", {'vlanid': vlan_id,
'host': self.host})
self.send_exit(1)
def _configure_ipaddress(self, vlan_id, gateway_ip_cidr):
"""Assigns Gateway ip for the configured vlan
@ -182,17 +155,16 @@ class DeviceConnector(object):
'vlanid': vlan_id,
'host': self.host})
self.write(
Commands.CONFIGURE_INTERFACE.format(
commands.CONFIGURE_INTERFACE.format(
vlan_id=vlan_id))
self.write(
Commands.CONFIGURE_GATEWAY_IP.format(
commands.CONFIGURE_GATEWAY_IP.format(
gateway_ip_addr=gateway_ip_cidr))
LOG.debug("DeviceConnector:_configure_ipaddress:Assigned IP address"
" %(ipaddr)s to the router interface %(vlanid)s on device"
" %(host)s", {'ipaddr': gateway_ip_cidr,
'vlanid': vlan_id,
'host': self.host})
self.send_exit(1)
def delete_l3_router(self, vlan_id):
"""
@ -209,15 +181,13 @@ class DeviceConnector(object):
" %(vlan_id)s from vlan %(vlan_id)s on device %(host)s"),
{'vlan_id': vlan_id, 'host': self.host})
self.write(
Commands.CONFIGURE_VLAN.format(vlan_id=vlan_id))
commands.CONFIGURE_VLAN.format(vlan_id=vlan_id))
self.write(
Commands.DELETE_ROUTER_INTERFACE.format(
commands.DELETE_ROUTER_INTERFACE.format(
vlan_id=vlan_id))
self.send_exit(1)
LOG.debug(("DeviceConnector:delete_l3_router:Deleted router interface"
" %(vlan_id)s from vlan %(vlan_id)s on device %(host)s"),
{'vlan_id': vlan_id, 'host': self.host})
self.exit_from_device()
return self.read_response()

@ -45,6 +45,10 @@ ML2_BROCADE = [cfg.StrOpt('address', default='',
help=('OS type of the device. NI is NetIron '
'for MLX switches. FI is FastIron for '
'ICX switches.')),
cfg.StrOpt('enable_username', default='admin',
help=('Username of the enable config prompt')),
cfg.StrOpt('enable_password', default='password', secret=True,
help=('Password of the enable config prompt')),
]
L3_BROCADE = [cfg.StrOpt('address', default='',
help=('The IP address of the MLX switch')),
@ -58,6 +62,10 @@ L3_BROCADE = [cfg.StrOpt('address', default='',
cfg.StrOpt('ports', default='',
help=('Ports to be tagged in the VLAN being '
'configured on the switch')),
cfg.StrOpt('enable_username', default='admin',
help=('Username of the enable config prompt')),
cfg.StrOpt('enable_password', default='password', secret=True,
help=('Password of the enable config prompt')),
]
cfg.CONF.register_opts(SWITCHES, 'ml2_brocade_fi_ni')
cfg.CONF.register_opts(SWITCHES, 'l3_brocade_mlx')

@ -15,19 +15,29 @@
""" Implementation of SSH Connector"""
import networking_brocade.mlx.ml2.commands as Commands
from networking_brocade.mlx.ml2.device_connector import (
DeviceConnector as DevConn)
from networking_brocade.mlx.ml2 import device_connector as dev_conn
from neutron.i18n import _LE
from oslo_log import log as logging
import paramiko
import time
LOG = logging.getLogger(__name__)
WRITE = "wb"
READ = "rb"
PROMPT = '>'
ENABLE_PROMPT = '#'
ENABLE_USERNAME_PROMPT = 'User Name:'
ENABLE_PASSWORD_PROMPT = 'Password:'
CONFIG_MODE = '(config)#'
CONFIG_COMMAND = "conf t\n"
ENABLE_COMMAND = "en\n"
NEW_LINE = "\n"
TIMEOUT = 30.0
SLEEP_TIME = 1
class SSHConnector(DevConn):
class SSHConnector(dev_conn.DeviceConnector):
"""
Uses SSH to connect to device
@ -46,10 +56,10 @@ class SSHConnector(DevConn):
username=self.username,
password=self.password)
channel = self.connector.invoke_shell()
self.stdin_stream = channel.makefile(WRITE)
self.stdout_stream = channel.makefile(READ)
self.stderr_stream = channel.makefile(READ)
self.channel = self.connector.invoke_shell()
self.channel.settimeout(TIMEOUT)
self.channel_data = str()
self._enter_prompt(False)
except Exception as e:
LOG.exception(_LE("Connect failed to switch %(host)s with error"
@ -57,45 +67,75 @@ class SSHConnector(DevConn):
{'host': self.host, 'error': e.args})
raise Exception(_("Connection Failed"))
def _send_command(self, prompt, command):
"""
Executes the command passed, if the response matches the prompt
"""
execution_state = False
if self.channel_data.endswith(prompt):
self.channel.send(command)
execution_state = True
return execution_state
def _enter_prompt(self, is_config_mode):
"""
Enters enable prompt mode or config mode based on the parameter
param:is_config_mode: if this is True, will enter config mode, else
it will make sure it is in enable prompt.
"""
commands = []
prompt_command = {}
if is_config_mode:
prompts = [ENABLE_PROMPT, CONFIG_MODE]
prompt_command = {ENABLE_PROMPT: CONFIG_COMMAND,
CONFIG_MODE: NEW_LINE}
else:
prompts = [PROMPT, ENABLE_USERNAME_PROMPT,
ENABLE_PASSWORD_PROMPT, ENABLE_PROMPT]
prompt_command = {PROMPT: ENABLE_COMMAND,
ENABLE_USERNAME_PROMPT: self.enable_username +
NEW_LINE,
ENABLE_PASSWORD_PROMPT: self.enable_password +
NEW_LINE,
ENABLE_PROMPT: NEW_LINE}
cmd_executed = True
# Send new line so that channel will have something to read for the
# first time.
self.channel.send(NEW_LINE)
index = 0
while index < len(commands):
prompt = prompts[index]
if cmd_executed:
self.channel_data += self.channel.recv(9999)
command = prompt_command.get(prompt)
cmd_executed = self._send_command(prompt, command)
index += 1
time.sleep(SLEEP_TIME)
def enter_configuration_mode(self):
"""
This method will ensure the session is in configuration mode
"""
self._enter_prompt(True)
def write(self, command):
"""
Write from input stream to device
:param:command: Command to be executed on the device
"""
self.stdin_stream.write(command)
self.stdin_stream.flush()
self.channel.send(command)
time.sleep(SLEEP_TIME)
self.channel_data += self.channel.recv(9999)
def read_response(self, read_lines=True):
"""Read the response from the output stream.
:param:read_lines: Boolean value which indicated to read multiple line
or single line. It is true by default.
:returns: Response from the device as list when read_lines is True or
string when read_lines is false.
"""
response = None
if read_lines:
response = self.stdout_stream.readlines()
else:
response = self.stdout_stream.read()
return response
def send_exit(self, count):
"""Send Exit command.
:param:count: Indicates number of times to execute exit command
"""
index = 0
while index < count:
self.stdin_stream.write(Commands.EXIT)
self.stdin_stream.flush()
index += 1
return self.channel_data
def close_session(self):
"""Close SSH session."""
if self.connector:
self.stdin_stream.close()
self.stdout_stream.close()
self.stderr_stream.close()
self.channel.close()
self.connector.close()

@ -16,9 +16,9 @@
""" Implementation of Telnet Connector """
import telnetlib
import time
from networking_brocade.mlx.ml2.device_connector import (
DeviceConnector as DevConn)
from networking_brocade.mlx.ml2 import device_connector as dev_conn
from neutron.i18n import _LE
from oslo_log import log as logging
@ -32,15 +32,23 @@ SUPER_USER_AUTH = '^Password\\:$'
TERMINAL_LENGTH = "terminal length 0"
END_OF_LINE = "\r"
TELNET_TERMINAL = ">"
PROMPT = ">"
ENABLE_PROMPT = '#'
CONFIGURE_TERMINAL = "#"
ENABLE_USERNAME_PROMPT = 'User Name:'
ENABLE_PASSWORD_PROMPT = 'Password:'
CONFIG_MODE = "(config)#"
RETURN_COMMAND = "\r"
ENABLE_COMMAND = "en\r"
CONFIG_COMMAND = "conf t\r"
MIN_TIMEOUT = 2
AVG_TIMEOUT = 4
MAX_TIMEOUT = 8
class TelnetConnector(DevConn):
class TelnetConnector(dev_conn.DeviceConnector):
"""
Uses Telnet to connect to device
@ -56,13 +64,66 @@ class TelnetConnector(DevConn):
self.connector.write(self.username + END_OF_LINE)
self.connector.read_until(LOGIN_PASS_TOKEN, AVG_TIMEOUT)
self.connector.write(self.password + END_OF_LINE)
self.connector.read_until(TELNET_TERMINAL, MAX_TIMEOUT)
self.response = str()
self._enter_prompt(False)
except Exception as e:
LOG.exception(_LE("Connect failed to switch %(host)s with error"
" %(error)s"),
{'host': self.host, 'error': e.args})
raise Exception(_("Connection Failed"))
def _send_command(self, prompt, command):
"""
Executes the command passed, if the response matches the prompt
"""
execution_state = False
if self.response.endswith(prompt):
self.connector.write(command)
execution_state = True
return execution_state
def _enter_prompt(self, is_config_mode):
"""
Enters enable prompt mode or config mode based on the parameter
param:is_config_mode: if this is True, will enter config mode, else
it will make sure it is in enable prompt.
"""
commands = []
prompt_command = {}
if is_config_mode:
prompts = [ENABLE_PROMPT, CONFIG_MODE]
prompt_command = {ENABLE_PROMPT: CONFIG_COMMAND,
CONFIG_MODE: RETURN_COMMAND}
else:
prompts = [PROMPT, ENABLE_USERNAME_PROMPT,
ENABLE_PASSWORD_PROMPT, ENABLE_PROMPT]
prompt_command = {PROMPT: ENABLE_COMMAND,
ENABLE_USERNAME_PROMPT: self.enable_username +
RETURN_COMMAND,
ENABLE_PASSWORD_PROMPT: self.enable_password +
RETURN_COMMAND,
ENABLE_PROMPT: RETURN_COMMAND}
cmd_executed = True
# Send new line so that channel will have something to read for the
# first time.
self.connector.write(RETURN_COMMAND)
index = 0
while index < len(commands):
prompt = prompts[index]
if cmd_executed:
self.response += self.connector.read_until(prompt, AVG_TIMEOUT)
command = prompt_command.get(prompt)
cmd_executed = self._send_command(prompt, command)
index += 1
time.sleep(MIN_TIMEOUT)
def enter_configuration_mode(self):
"""
This method will ensure the session is in configuration mode
"""
self._enter_prompt(True)
def write(self, command):
"""
Write from input stream to device
@ -70,21 +131,17 @@ class TelnetConnector(DevConn):
:param:command: Command to be executed on the device
"""
self.connector.write(command)
self.response += self.connector.read_until(PROMPT, MAX_TIMEOUT)
def read_response(self, read_lines=True):
def read_response(self):
"""Read the response from the output stream.
:param:read_lines: This is used only by the SSH connector.
:returns: Response from the device as list.
:returns: Response from the device as string.
"""
return self.connector.read_until(CONFIGURE_TERMINAL, MIN_TIMEOUT)
return self.response
def close_session(self):
"""Close TELNET session."""
if self.connector:
self.connector.close()
self.connector = None
def send_exit(self, count):
"""No operation. Used by SSH connector only."""
pass

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

Loading…
Cancel
Save