¡@

Home 

OpenStack Study: brcd_fc_zone_client_cli.py

OpenStack Index

**** CubicPower OpenStack Study ****

# (c) Copyright 2014 Brocade Communications Systems Inc.

# All Rights Reserved.

#

# Copyright 2014 OpenStack Foundation

#

# 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.

#

"""

Script to push the zone configuration to brocade SAN switches.

"""

import random

import re

from eventlet import greenthread

from cinder import exception

from cinder.openstack.common import excutils

from cinder.openstack.common import log as logging

from cinder.openstack.common import processutils

from cinder import utils

import cinder.zonemanager.drivers.brocade.fc_zone_constants as ZoneConstant

LOG = logging.getLogger(__name__)

**** CubicPower OpenStack Study ****

class BrcdFCZoneClientCLI(object):

switch_ip = None

switch_port = '22'

switch_user = 'admin'

switch_pwd = 'none'

patrn = re.compile('[;\s]+')

**** CubicPower OpenStack Study ****

    def __init__(self, ipaddress, username, password, port):

        """initializing the client."""

        self.switch_ip = ipaddress

        self.switch_port = port

        self.switch_user = username

        self.switch_pwd = password

        self.sshpool = None

**** CubicPower OpenStack Study ****

    def get_active_zone_set(self):

        """Return the active zone configuration.

        Return active zoneset from fabric. When none of the configurations

        are active then it will return empty map.

        :returns: Map -- active zone set map in the following format

        {

            'zones':

                {'openstack50060b0000c26604201900051ee8e329':

                    ['50060b0000c26604', '201900051ee8e329']

                },

            'active_zone_config': 'OpenStack_Cfg'

        }

        """

        zone_set = {}

        zone = {}

        zone_member = None

        zone_name = None

        switch_data = None

        zone_set_name = None

        try:

            switch_data = self._get_switch_info(

                [ZoneConstant.GET_ACTIVE_ZONE_CFG])

        except exception.BrocadeZoningCliException:

            with excutils.save_and_reraise_exception():

                LOG.error(_("Failed getting active zone set "

                            "from fabric %s"), self.switch_ip)

        try:

            for line in switch_data:

                line_split = re.split('\\t', line)

                if len(line_split) > 2:

                    line_split = [x.replace(

                        '\n', '') for x in line_split]

                    line_split = [x.replace(

                        ' ',

                        '') for x in line_split]

                    if ZoneConstant.CFG_ZONESET in line_split:

                        zone_set_name = line_split[1]

                        continue

                    if line_split[1]:

                        zone_name = line_split[1]

                        zone[zone_name] = list()

                    if line_split[2]:

                        zone_member = line_split[2]

                    if zone_member:

                        zone_member_list = zone.get(zone_name)

                        zone_member_list.append(zone_member)

            zone_set[ZoneConstant.CFG_ZONES] = zone

            zone_set[ZoneConstant.ACTIVE_ZONE_CONFIG] = zone_set_name

        except Exception as ex:

            # Incase of parsing error here, it should be malformed cli output.

            msg = _("Malformed zone configuration: (switch=%(switch)s "

                    "zone_config=%(zone_config)s)."

                    ) % {'switch': self.switch_ip,

                         'zone_config': switch_data}

            LOG.error(msg)

            LOG.exception(ex)

            raise exception.FCZoneDriverException(reason=msg)

        switch_data = None

        return zone_set

**** CubicPower OpenStack Study ****

    def add_zones(self, zones, activate):

        """Add zone configuration.

        This method will add the zone configuration passed by user.

            input params:

            zones - zone names mapped to members.

            zone members are colon separated but case-insensitive

            {   zonename1:[zonememeber1,zonemember2,...],

                zonename2:[zonemember1, zonemember2,...]...}

            e.g: {'openstack50060b0000c26604201900051ee8e329':

                    ['50:06:0b:00:00:c2:66:04', '20:19:00:05:1e:e8:e3:29']

                }

            activate - True/False

        """

        LOG.debug(_("Add Zones - Zones passed: %s"), zones)

        cfg_name = None

        iterator_count = 0

        zone_with_sep = ''

        active_zone_set = self.get_active_zone_set()

        LOG.debug(_("Active zone set:%s"), active_zone_set)

        zone_list = active_zone_set[ZoneConstant.CFG_ZONES]

        LOG.debug(_("zone list:%s"), zone_list)

        for zone in zones.keys():

            # if zone exists, its an update. Delete & insert

            # TODO(skolathur): This can be optimized to an update call later

            LOG.debug("Update call")

            if (zone in zone_list):

                try:

                    self.delete_zones(zone, activate)

                except exception.BrocadeZoningCliException:

                    with excutils.save_and_reraise_exception():

                        LOG.error(_("Deleting zone failed %s"), zone)

                LOG.debug(_("Deleted Zone before insert : %s"), zone)

            zone_members_with_sep = ';'.join(str(member) for

                                             member in zones[zone])

            LOG.debug(_("Forming command for add zone"))

            cmd = 'zonecreate "%(zone)s", "%(zone_members_with_sep)s"' % {

                'zone': zone,

                'zone_members_with_sep': zone_members_with_sep}

            LOG.debug(_("Adding zone, cmd to run %s"), cmd)

            self.apply_zone_change(cmd.split())

            LOG.debug(_("Created zones on the switch"))

            if(iterator_count > 0):

                zone_with_sep += ';'

            iterator_count += 1

            zone_with_sep += zone

        try:

            cfg_name = active_zone_set[ZoneConstant.ACTIVE_ZONE_CONFIG]

            cmd = None

            if not cfg_name:

                cfg_name = ZoneConstant.OPENSTACK_CFG_NAME

                cmd = 'cfgcreate "%(zoneset)s", "%(zones)s"' \

                    % {'zoneset': cfg_name, 'zones': zone_with_sep}

            else:

                cmd = 'cfgadd "%(zoneset)s", "%(zones)s"' \

                    % {'zoneset': cfg_name, 'zones': zone_with_sep}

            LOG.debug(_("New zone %s"), cmd)

            self.apply_zone_change(cmd.split())

            self._cfg_save()

            if activate:

                self.activate_zoneset(cfg_name)

        except Exception as e:

            self._cfg_trans_abort()

            msg = _("Creating and activating zone set failed: "

                    "(Zone set=%(cfg_name)s error=%(err)s)."

                    ) % {'cfg_name': cfg_name, 'err': e}

            LOG.error(msg)

            raise exception.BrocadeZoningCliException(reason=msg)

**** CubicPower OpenStack Study ****

    def activate_zoneset(self, cfgname):

        """Method to Activate the zone config. Param cfgname - ZonesetName."""

        cmd_list = [ZoneConstant.ACTIVATE_ZONESET, cfgname]

        return self._ssh_execute(cmd_list, True, 1)

**** CubicPower OpenStack Study ****

    def deactivate_zoneset(self):

        """Method to deActivate the zone config."""

        return self._ssh_execute([ZoneConstant.DEACTIVATE_ZONESET], True, 1)

**** CubicPower OpenStack Study ****

    def delete_zones(self, zone_names, activate):

        """Delete zones from fabric.

        Method to delete the active zone config zones

        params zone_names: zoneNames separated by semicolon

        params activate: True/False

        """

        active_zoneset_name = None

        active_zone_set = None

        zone_list = []

        active_zone_set = self.get_active_zone_set()

        active_zoneset_name = active_zone_set[

            ZoneConstant.ACTIVE_ZONE_CONFIG]

        zone_list = active_zone_set[ZoneConstant.CFG_ZONES]

        zones = self.patrn.split(''.join(zone_names))

        cmd = None

        try:

            if len(zones) == len(zone_list):

                self.deactivate_zoneset()

                cmd = 'cfgdelete "%(active_zoneset_name)s"' \

                    % {'active_zoneset_name': active_zoneset_name}

                # Active zoneset is being deleted, hence reset is_active

                activate = False

            else:

                cmd = 'cfgremove "%(active_zoneset_name)s", "%(zone_names)s"' \

                    % {'active_zoneset_name': active_zoneset_name,

                       'zone_names': zone_names

                       }

            LOG.debug(_("Delete zones: Config cmd to run:%s"), cmd)

            self.apply_zone_change(cmd.split())

            for zone in zones:

                self._zone_delete(zone)

            self._cfg_save()

            if activate:

                self.activate_zoneset(active_zoneset_name)

        except Exception as e:

            msg = _("Deleting zones failed: (command=%(cmd)s error=%(err)s)."

                    ) % {'cmd': cmd, 'err': e}

            LOG.error(msg)

            self._cfg_trans_abort()

            raise exception.BrocadeZoningCliException(reason=msg)

**** CubicPower OpenStack Study ****

    def get_nameserver_info(self):

        """Get name server data from fabric.

        This method will return the connected node port wwn list(local

        and remote) for the given switch fabric

        """

        cli_output = None

        return_list = []

        try:

            cli_output = self._get_switch_info([ZoneConstant.NS_SHOW])

        except exception.BrocadeZoningCliException:

            with excutils.save_and_reraise_exception():

                LOG.error(_("Failed collecting nsshow "

                            "info for fabric %s"), self.switch_ip)

        if (cli_output):

            return_list = self._parse_ns_output(cli_output)

        try:

            cli_output = self._get_switch_info([ZoneConstant.NS_CAM_SHOW])

        except exception.BrocadeZoningCliException:

            with excutils.save_and_reraise_exception():

                LOG.error(_("Failed collecting nscamshow "

                            "info for fabric %s"), self.switch_ip)

        if (cli_output):

            return_list.extend(self._parse_ns_output(cli_output))

        cli_output = None

        return return_list

**** CubicPower OpenStack Study ****

    def _cfg_save(self):

        self._ssh_execute([ZoneConstant.CFG_SAVE], True, 1)

**** CubicPower OpenStack Study ****

    def _zone_delete(self, zone_name):

        cmd = 'zonedelete "%(zone_name)s"' % {'zone_name': zone_name}

        self.apply_zone_change(cmd.split())

**** CubicPower OpenStack Study ****

    def _cfg_trans_abort(self):

        is_abortable = self._is_trans_abortable()

        if(is_abortable):

            self.apply_zone_change([ZoneConstant.CFG_ZONE_TRANS_ABORT])

**** CubicPower OpenStack Study ****

    def _is_trans_abortable(self):

        is_abortable = False

        stdout, stderr = None, None

        stdout, stderr = self._run_ssh(

            [ZoneConstant.CFG_SHOW_TRANS], True, 1)

        output = stdout.splitlines()

        is_abortable = False

        for line in output:

            if(ZoneConstant.TRANS_ABORTABLE in line):

                is_abortable = True

                break

        if stderr:

            msg = _("Error while checking transaction status: %s") % stderr

            raise exception.BrocadeZoningCliException(reason=msg)

        else:

            return is_abortable

**** CubicPower OpenStack Study ****

    def apply_zone_change(self, cmd_list):

        """Execute zoning cli with no status update.

        Executes CLI commands such as addZone where status return is

        not expected.

        """

        stdout, stderr = None, None

        LOG.debug(_("Executing command via ssh: %s"), cmd_list)

        stdout, stderr = self._run_ssh(cmd_list, True, 1)

        # no output expected, so output means there is an error

        if stdout:

            msg = _("Error while running zoning CLI: (command=%(cmd)s "

                    "error=%(err)s).") % {'cmd': cmd_list, 'err': stdout}

            LOG.error(msg)

            self._cfg_trans_abort()

            raise exception.BrocadeZoningCliException(reason=msg)

**** CubicPower OpenStack Study ****

    def is_supported_firmware(self):

        """Check firmware version is v6.4 or higher.

        This API checks if the firmware version per the plug-in support level.

        This only checks major and minor version.

        """

        cmd = ['version']

        firmware = 0

        try:

            stdout, stderr = self._execute_shell_cmd(cmd)

            if (stdout):

                for line in stdout:

                    if 'Fabric OS:  v' in line:

                        LOG.debug(_("Firmware version string:%s"), line)

                        ver = line.split('Fabric OS:  v')[1].split('.')

                        if (ver):

                            firmware = int(ver[0] + ver[1])

                return firmware > 63

            else:

                LOG.error(_("No CLI output for firmware version check"))

                return False

        except processutils.ProcessExecutionError as e:

            msg = _("Error while getting data via ssh: (command=%(cmd)s "

                    "error=%(err)s).") % {'cmd': cmd, 'err': e}

            LOG.error(msg)

            raise exception.BrocadeZoningCliException(reason=msg)

**** CubicPower OpenStack Study ****

    def _get_switch_info(self, cmd_list):

        stdout, stderr, sw_data = None, None, None

        try:

            stdout, stderr = self._run_ssh(cmd_list, True, 1)

            if (stdout):

                sw_data = stdout.splitlines()

            return sw_data

        except processutils.ProcessExecutionError as e:

            msg = _("Error while getting data via ssh: (command=%(cmd)s "

                    "error=%(err)s).") % {'cmd': cmd_list,

                                          'err': e}

            LOG.error(msg)

            raise exception.BrocadeZoningCliException(reason=msg)

**** CubicPower OpenStack Study ****

    def _parse_ns_output(self, switch_data):

        """Parses name server data.

        Parses nameserver raw data and adds the device port wwns to the list

        :returns: List -- list of device port wwn from ns info

        """

        return_list = []

        for line in switch_data:

            if not(" NL " in line or " N " in line):

                continue

            linesplit = line.split(';')

            if len(linesplit) > 2:

                node_port_wwn = linesplit[2]

                return_list.append(node_port_wwn)

            else:

                msg = _("Malformed nameserver string: %s") % line

                LOG.error(msg)

                raise exception.InvalidParameterValue(err=msg)

        return return_list

**** CubicPower OpenStack Study ****

    def _run_ssh(self, cmd_list, check_exit_code=True, attempts=1):

        # TODO(skolathur): Need to implement ssh_injection check

        # currently, the check will fail for zonecreate command

        # as zone members are separated by ';'which is a danger char

        command = ' '. join(cmd_list)

        if not self.sshpool:

            self.sshpool = utils.SSHPool(self.switch_ip,

                                         self.switch_port,

                                         None,

                                         self.switch_user,

                                         self.switch_pwd,

                                         min_size=1,

                                         max_size=5)

        last_exception = None

        try:

            with self.sshpool.item() as ssh:

                while attempts > 0:

                    attempts -= 1

                    try:

                        return processutils.ssh_execute(

                            ssh,

                            command,

                            check_exit_code=check_exit_code)

                    except Exception as e:

                        LOG.error(e)

                        last_exception = e

                        greenthread.sleep(random.randint(20, 500) / 100.0)

                try:

                    raise processutils.ProcessExecutionError(

                        exit_code=last_exception.exit_code,

                        stdout=last_exception.stdout,

                        stderr=last_exception.stderr,

                        cmd=last_exception.cmd)

                except AttributeError:

                    raise processutils.ProcessExecutionError(

                        exit_code=-1,

                        stdout="",

                        stderr="Error running SSH command",

                        cmd=command)

        except Exception:

            with excutils.save_and_reraise_exception():

                LOG.error(_("Error running SSH command: %s") % command)

**** CubicPower OpenStack Study ****

    def _ssh_execute(self, cmd_list, check_exit_code=True, attempts=1):

        """Execute cli with status update.

        Executes CLI commands such as cfgsave where status return is expected.

        """

        utils.check_ssh_injection(cmd_list)

        command = ' '. join(cmd_list)

        if not self.sshpool:

            self.sshpool = utils.SSHPool(self.switch_ip,

                                         self.switch_port,

                                         None,

                                         self.switch_user,

                                         self.switch_pwd,

                                         min_size=1,

                                         max_size=5)

        stdin, stdout, stderr = None, None, None

        LOG.debug(_("Executing command via ssh: %s") % command)

        last_exception = None

        try:

            with self.sshpool.item() as ssh:

                while attempts > 0:

                    attempts -= 1

                    try:

                        stdin, stdout, stderr = ssh.exec_command(command)

                        greenthread.sleep(random.randint(20, 500) / 100.0)

                        stdin.write("%s\n" % ZoneConstant.YES)

                        channel = stdout.channel

                        exit_status = channel.recv_exit_status()

                        LOG.debug(_("Exit Status from ssh:%s"), exit_status)

                        # exit_status == -1 if no exit code was returned

                        if exit_status != -1:

                            LOG.debug(_('Result was %s') % exit_status)

                            if check_exit_code and exit_status != 0:

                                raise processutils.ProcessExecutionError(

                                    exit_code=exit_status,

                                    stdout=stdout,

                                    stderr=stderr,

                                    cmd=command)

                            else:

                                return True

                        else:

                            return True

                    except Exception as e:

                        LOG.error(e)

                        last_exception = e

                        greenthread.sleep(random.randint(20, 500) / 100.0)

                LOG.debug(_("Handling error case after "

                            "SSH:%s"), last_exception)

                try:

                    raise processutils.ProcessExecutionError(

                        exit_code=last_exception.exit_code,

                        stdout=last_exception.stdout,

                        stderr=last_exception.stderr,

                        cmd=last_exception.cmd)

                except AttributeError:

                    raise processutils.ProcessExecutionError(

                        exit_code=-1,

                        stdout="",

                        stderr="Error running SSH command",

                        cmd=command)

        except Exception as e:

            with excutils.save_and_reraise_exception():

                LOG.error(_("Error executing command via ssh: %s"), e)

        finally:

            if stdin:

                stdin.flush()

                stdin.close()

            if stdout:

                stdout.close()

            if stderr:

                stderr.close()

**** CubicPower OpenStack Study ****

    def _execute_shell_cmd(self, cmd):

        """Run command over shell for older firmware versions.

        We invoke shell and issue the command and return the output.

        This is primarily used for issuing read commands when we are not sure

        if the firmware supports exec_command.

        """

        utils.check_ssh_injection(cmd)

        command = ' '. join(cmd)

        stdout, stderr = None, None

        if not self.sshpool:

            self.sshpool = utils.SSHPool(self.switch_ip,

                                         self.switch_port,

                                         None,

                                         self.switch_user,

                                         self.switch_pwd,

                                         min_size=1,

                                         max_size=5)

        with self.sshpool.item() as ssh:

            LOG.debug('Running cmd (SSH): %s' % command)

            channel = ssh.invoke_shell()

            stdin_stream = channel.makefile('wb')

            stdout_stream = channel.makefile('rb')

            stderr_stream = channel.makefile('rb')

            stdin_stream.write('''%s

exit

''' % command)

            stdin_stream.flush()

            stdout = stdout_stream.readlines()

            stderr = stderr_stream.readlines()

            stdin_stream.close()

            stdout_stream.close()

            stderr_stream.close()

            exit_status = channel.recv_exit_status()

            # exit_status == -1 if no exit code was returned

            if exit_status != -1:

                LOG.debug('Result was %s' % exit_status)

                if exit_status != 0:

                    msg = "command %s failed" % command

                    LOG.debug(msg)

                    raise processutils.ProcessExecutionError(

                        exit_code=exit_status,

                        stdout=stdout,

                        stderr=stderr,

                        cmd=command)

            try:

                channel.close()

            except Exception as e:

                LOG.exception(e)

            LOG.debug("_execute_cmd: stdout to return:%s" % stdout)

            LOG.debug("_execute_cmd: stderr to return:%s" % stderr)

        return (stdout, stderr)

**** CubicPower OpenStack Study ****

    def cleanup(self):

        self.sshpool = None