
Probably the most common format for documenting arguments is reST field lists [1]. This change updates some docstrings to comply with the field lists syntax. [1] http://sphinx-doc.org/domains.html#info-field-lists Change-Id: I0fe2d2faa7a1abd6d5e84f05e7fdbb13661a94dc
968 lines
41 KiB
Python
968 lines
41 KiB
Python
# (c) Copyright 2016 Brocade Communications Systems Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# 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.
|
|
#
|
|
"""
|
|
Brocade south bound connector to communicate with switch using
|
|
HTTP or HTTPS protocol.
|
|
"""
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_serialization import base64
|
|
from oslo_utils import encodeutils
|
|
import requests
|
|
import six
|
|
import time
|
|
|
|
from cinder import exception
|
|
from cinder.i18n import _
|
|
import cinder.zonemanager.drivers.brocade.fc_zone_constants as zone_constant
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class BrcdHTTPFCZoneClient(object):
|
|
|
|
def __init__(self, ipaddress, username,
|
|
password, port, vfid, protocol):
|
|
"""Initializing the client with the parameters passed.
|
|
|
|
Creates authentication token and authenticate with switch
|
|
to ensure the credentials are correct and change the VF context.
|
|
|
|
:param ipaddress: IP Address of the device.
|
|
:param username: User id to login.
|
|
:param password: User password.
|
|
:param port: Device Communication port
|
|
:param vfid: Virtual Fabric ID.
|
|
:param protocol: Communication Protocol.
|
|
"""
|
|
self.switch_ip = ipaddress
|
|
self.switch_user = username
|
|
self.switch_pwd = password
|
|
self.protocol = protocol
|
|
self.vfid = vfid
|
|
self.cfgs = {}
|
|
self.zones = {}
|
|
self.alias = {}
|
|
self.qlps = {}
|
|
self.ifas = {}
|
|
self.active_cfg = ''
|
|
self.parsed_raw_zoneinfo = ""
|
|
self.random_no = ''
|
|
self.session = None
|
|
|
|
# Create and assign the authentication header based on the credentials
|
|
self.auth_header = self.create_auth_token()
|
|
|
|
# Authenticate with the switch
|
|
# If authenticated successfully, save the auth status and
|
|
# create auth header for future communication with the device.
|
|
self.is_auth, self.auth_header = self.authenticate()
|
|
self.check_change_vf_context()
|
|
|
|
def connect(self, requestType, requestURL, payload='', header=None):
|
|
"""Connect to the switch using HTTP/HTTPS protocol.
|
|
|
|
:param requestType: Connection Request method
|
|
:param requestURL: Connection URL
|
|
:param payload: Data to send with POST request
|
|
:param header: Request Headers
|
|
|
|
:returns: HTTP response data
|
|
:raises BrocadeZoningHttpException:
|
|
"""
|
|
try:
|
|
if header is None:
|
|
header = {}
|
|
header.update({"User-Agent": "OpenStack Zone Driver"})
|
|
|
|
# Ensure only one connection is made throughout the life cycle
|
|
protocol = zone_constant.HTTP
|
|
if self.protocol == zone_constant.PROTOCOL_HTTPS:
|
|
protocol = zone_constant.HTTPS
|
|
if self.session is None:
|
|
self.session = requests.Session()
|
|
adapter = requests.adapters.HTTPAdapter(pool_connections=1,
|
|
pool_maxsize=1)
|
|
self.session.mount(protocol + '://', adapter)
|
|
url = protocol + "://" + self.switch_ip + requestURL
|
|
response = None
|
|
if requestType == zone_constant.GET_METHOD:
|
|
response = self.session.get(url,
|
|
headers=(header),
|
|
verify=False)
|
|
elif requestType == zone_constant.POST_METHOD:
|
|
response = self.session.post(url,
|
|
payload,
|
|
headers=(header),
|
|
verify=False)
|
|
|
|
# Throw exception when response status is not OK
|
|
if response.status_code != zone_constant.STATUS_OK:
|
|
msg = _("Error while querying page %(url)s on the switch, "
|
|
"reason %(error)s.") % {'url': url,
|
|
'error': response.reason}
|
|
raise exception.BrocadeZoningHttpException(msg)
|
|
else:
|
|
return response.text
|
|
except requests.exceptions.ConnectionError as e:
|
|
msg = (_("Error while connecting the switch %(switch_id)s "
|
|
"with protocol %(protocol)s. Error: %(error)s.")
|
|
% {'switch_id': self.switch_ip,
|
|
'protocol': self.protocol,
|
|
'error': six.text_type(e)})
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
except exception.BrocadeZoningHttpException as ex:
|
|
msg = (_("Unexpected status code from the switch %(switch_id)s "
|
|
"with protocol %(protocol)s for url %(page)s. "
|
|
"Error: %(error)s")
|
|
% {'switch_id': self.switch_ip,
|
|
'protocol': self.protocol,
|
|
'page': requestURL,
|
|
'error': six.text_type(ex)})
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
|
|
def create_auth_token(self):
|
|
"""Create the authentication token.
|
|
|
|
Creates the authentication token to use in the authentication header
|
|
return authentication header (Base64(username:password:random no)).
|
|
|
|
:returns: Authentication Header
|
|
:raises BrocadeZoningHttpException:
|
|
"""
|
|
try:
|
|
# Send GET request to secinfo.html to get random number
|
|
response = self.connect(zone_constant.GET_METHOD,
|
|
zone_constant.SECINFO_PAGE)
|
|
parsed_data = self.get_parsed_data(response,
|
|
zone_constant.SECINFO_BEGIN,
|
|
zone_constant.SECINFO_END)
|
|
|
|
# Extract the random no from secinfo.html response
|
|
self.random_no = self.get_nvp_value(parsed_data,
|
|
zone_constant.RANDOM)
|
|
# Form the authentication string
|
|
auth_string = (self.switch_user + ":" + self.switch_pwd +
|
|
":" + self.random_no)
|
|
auth_token = base64.encode_as_text(auth_string).strip()
|
|
auth_header = (zone_constant.AUTH_STRING +
|
|
auth_token) # Build the proper header
|
|
except Exception as e:
|
|
msg = (_("Error while creating authentication token: %s")
|
|
% six.text_type(e))
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
return auth_header
|
|
|
|
def authenticate(self):
|
|
"""Authenticate with the switch.
|
|
|
|
Returns authentication status with modified authentication
|
|
header (Base64(username:xxx:random no)).
|
|
|
|
:returns: Authentication status
|
|
:raises BrocadeZoningHttpException:
|
|
"""
|
|
headers = {zone_constant.AUTH_HEADER: self.auth_header}
|
|
try:
|
|
# GET Request to authenticate.html to verify the credentials
|
|
response = self.connect(zone_constant.GET_METHOD,
|
|
zone_constant.AUTHEN_PAGE,
|
|
header=headers)
|
|
parsed_data = self.get_parsed_data(response,
|
|
zone_constant.AUTHEN_BEGIN,
|
|
zone_constant.AUTHEN_END)
|
|
isauthenticated = self.get_nvp_value(
|
|
parsed_data, zone_constant.AUTHENTICATED)
|
|
if isauthenticated == "yes":
|
|
# Replace password in the authentication string with xxx
|
|
auth_string = (self.switch_user +
|
|
":" + "xxx" + ":" + self.random_no)
|
|
auth_token = base64.encode_as_text(auth_string).strip()
|
|
auth_header = zone_constant.AUTH_STRING + auth_token
|
|
return True, auth_header
|
|
else:
|
|
auth_error_code = self.get_nvp_value(parsed_data, "errCode")
|
|
msg = (_("Authentication failed, verify the switch "
|
|
"credentials, error code %s.") % auth_error_code)
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
except Exception as e:
|
|
msg = (_("Error while authenticating with switch: %s.")
|
|
% six.text_type(e))
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
|
|
def get_session_info(self):
|
|
"""Get the session information from the switch
|
|
|
|
:returns: Connection status information.
|
|
"""
|
|
try:
|
|
headers = {zone_constant.AUTH_HEADER: self.auth_header}
|
|
# GET request to session.html
|
|
response = self.connect(zone_constant.GET_METHOD,
|
|
zone_constant.SESSION_PAGE_ACTION,
|
|
header=headers)
|
|
except Exception as e:
|
|
msg = (_("Error while getting session information %s.")
|
|
% six.text_type(e))
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
return response
|
|
|
|
def get_parsed_data(self, data, delim1, demil2):
|
|
"""Return the sub string between the delimiters.
|
|
|
|
:param data: String to manipulate
|
|
:param delim1: Delimiter 1
|
|
:param delim2: Delimiter 2
|
|
:returns: substring between the delimiters
|
|
"""
|
|
try:
|
|
start = data.index(delim1)
|
|
start = start + len(delim1)
|
|
end = data.index(demil2)
|
|
return data[start:end]
|
|
except ValueError as e:
|
|
msg = (_("Error while parsing the data: %s.") % six.text_type(e))
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
|
|
def get_nvp_value(self, data, keyname):
|
|
"""Get the value for the key passed.
|
|
|
|
:param data: NVP to manipulate
|
|
:param keyname: Key name
|
|
:returns: value for the NVP
|
|
"""
|
|
try:
|
|
start = data.index(keyname)
|
|
start = start + len(keyname)
|
|
temp = data[start:]
|
|
end = temp.index("\n")
|
|
return (temp[:end].lstrip('= '))
|
|
except ValueError as e:
|
|
msg = (_("Error while getting nvp value: %s.") % six.text_type(e))
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
|
|
def get_managable_vf_list(self, session_info):
|
|
"""List of VFIDs that can be managed.
|
|
|
|
:param session_info: Session information from the switch
|
|
:returns: manageable VF list
|
|
:raises BrocadeZoningHttpException:
|
|
"""
|
|
try:
|
|
# Check the value of manageableLFList NVP,
|
|
# throw exception as not supported if the nvp not available
|
|
vf_list = self.get_nvp_value(session_info,
|
|
zone_constant.MANAGEABLE_VF)
|
|
if vf_list:
|
|
vf_list = vf_list.split(",") # convert the string to list
|
|
except exception.BrocadeZoningHttpException as e:
|
|
msg = (_("Error while checking whether "
|
|
"VF is available for management %s.") % six.text_type(e))
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
return vf_list[:-1]
|
|
|
|
def change_vf_context(self, vfid, session_data):
|
|
"""Change the VF context in the session.
|
|
|
|
:param vfid: VFID to which context should be changed.
|
|
:param session_data: Session information from the switch
|
|
:raises BrocadeZoningHttpException:
|
|
"""
|
|
try:
|
|
managable_vf_list = self.get_managable_vf_list(session_data)
|
|
LOG.debug("Manageable VF IDs are %(vflist)s.",
|
|
{'vflist': managable_vf_list})
|
|
# proceed changing the VF context
|
|
# if VF id can be managed if not throw exception
|
|
if vfid in managable_vf_list:
|
|
headers = {zone_constant.AUTH_HEADER: self.auth_header}
|
|
data = zone_constant.CHANGE_VF.format(vfid=vfid)
|
|
response = self.connect(zone_constant.POST_METHOD,
|
|
zone_constant.SESSION_PAGE,
|
|
data,
|
|
headers)
|
|
parsed_info = self.get_parsed_data(response,
|
|
zone_constant.SESSION_BEGIN,
|
|
zone_constant.SESSION_END)
|
|
session_LF_Id = self.get_nvp_value(parsed_info,
|
|
zone_constant.SESSION_LF_ID)
|
|
if session_LF_Id == vfid:
|
|
LOG.info("VF context is changed in the session.")
|
|
else:
|
|
msg = _("Cannot change VF context in the session.")
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
|
|
else:
|
|
msg = (_("Cannot change VF context, "
|
|
"specified VF is not available "
|
|
"in the manageable VF list %(vf_list)s.")
|
|
% {'vf_list': managable_vf_list})
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
except exception.BrocadeZoningHttpException as e:
|
|
msg = (_("Error while changing VF context %s.") % six.text_type(e))
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
|
|
def get_zone_info(self):
|
|
"""Parse all the zone information and store it in the dictionary."""
|
|
|
|
try:
|
|
self.cfgs = {}
|
|
self.zones = {}
|
|
self.active_cfg = ''
|
|
self.alias = {}
|
|
self.qlps = {}
|
|
self.ifas = {}
|
|
headers = {zone_constant.AUTH_HEADER: self.auth_header}
|
|
# GET request to gzoneinfo.htm
|
|
response = self.connect(zone_constant.GET_METHOD,
|
|
zone_constant.ZONE_PAGE,
|
|
header=headers)
|
|
# get the zone string from the response
|
|
self.parsed_raw_zoneinfo = self.get_parsed_data(
|
|
response,
|
|
zone_constant.ZONEINFO_BEGIN,
|
|
zone_constant.ZONEINFO_END).strip("\n")
|
|
LOG.debug("Original zone string from the switch: %(zoneinfo)s",
|
|
{'zoneinfo': self.parsed_raw_zoneinfo})
|
|
# convert the zone string to list
|
|
zoneinfo = self.parsed_raw_zoneinfo.split()
|
|
i = 0
|
|
while i < len(zoneinfo):
|
|
info = zoneinfo[i]
|
|
# check for the cfg delimiter
|
|
if zone_constant.CFG_DELIM in info:
|
|
# extract the cfg name
|
|
cfg_name = info.lstrip(zone_constant.CFG_DELIM)
|
|
# update the dict as
|
|
# self.cfgs={cfg_name:zone_name1;zone_name2}
|
|
self.cfgs.update({cfg_name: zoneinfo[i + 1]})
|
|
i = i + 2
|
|
# check for the zone delimiter
|
|
elif zone_constant.ZONE_DELIM in info:
|
|
# extract the zone name
|
|
zone_name = info.lstrip(zone_constant.ZONE_DELIM)
|
|
# update the dict as
|
|
# self.zones={zone_name:members1;members2}
|
|
self.zones.update({zone_name: zoneinfo[i + 1]})
|
|
i = i + 2
|
|
elif zone_constant.ALIAS_DELIM in info:
|
|
alias_name = info.lstrip(zone_constant.ALIAS_DELIM)
|
|
# update the dict as
|
|
# self.alias={alias_name:members1;members2}
|
|
self.alias.update({alias_name: zoneinfo[i + 1]})
|
|
i = i + 2
|
|
# check for quickloop zones
|
|
elif zone_constant.QLP_DELIM in info:
|
|
qlp_name = info.lstrip(zone_constant.QLP_DELIM)
|
|
# update the map as self.qlps={qlp_name:members1;members2}
|
|
self.qlps.update({qlp_name: zoneinfo[i + 1]})
|
|
i = i + 2
|
|
# check for fabric assist zones
|
|
elif zone_constant.IFA_DELIM in info:
|
|
ifa_name = info.lstrip(zone_constant.IFA_DELIM)
|
|
# update the map as self.ifas={ifa_name:members1;members2}
|
|
self.ifas.update({ifa_name: zoneinfo[i + 1]})
|
|
i = i + 2
|
|
elif zone_constant.ACTIVE_CFG_DELIM in info:
|
|
# update the string self.active_cfg=cfg_name
|
|
self.active_cfg = info.lstrip(
|
|
zone_constant.ACTIVE_CFG_DELIM)
|
|
if self.active_cfg == zone_constant.DEFAULT_CFG:
|
|
self.active_cfg = ""
|
|
i = i + 2
|
|
else:
|
|
i = i + 1
|
|
except Exception as e:
|
|
msg = (_("Error while changing VF context %s.") % six.text_type(e))
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
|
|
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.
|
|
|
|
:returns: True if firmware is supported else False.
|
|
:raises BrocadeZoningHttpException:
|
|
"""
|
|
|
|
isfwsupported = False
|
|
|
|
try:
|
|
headers = {zone_constant.AUTH_HEADER: self.auth_header}
|
|
# GET request to switch.html
|
|
response = self.connect(zone_constant.GET_METHOD,
|
|
zone_constant.SWITCH_PAGE,
|
|
header=headers)
|
|
parsed_data = self.get_parsed_data(response,
|
|
zone_constant.SWITCHINFO_BEGIN,
|
|
zone_constant.SWITCHINFO_END)
|
|
|
|
# get the firmware version nvp value
|
|
fwVersion = self.get_nvp_value(
|
|
parsed_data,
|
|
zone_constant.FIRMWARE_VERSION).lstrip('v')
|
|
|
|
ver = fwVersion.split(".")
|
|
LOG.debug("Firmware version: %(version)s.", {'version': ver})
|
|
if int(ver[0] + ver[1]) > 63:
|
|
isfwsupported = True
|
|
|
|
except Exception as e:
|
|
msg = (_("Error while checking the firmware version %s.")
|
|
% six.text_type(e))
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
return isfwsupported
|
|
|
|
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
|
|
|
|
.. code-block:: python
|
|
|
|
{
|
|
'zones':
|
|
{'openstack50060b0000c26604201900051ee8e329':
|
|
['50060b0000c26604', '201900051ee8e329']
|
|
},
|
|
'active_zone_config': 'OpenStack_Cfg'
|
|
}
|
|
|
|
:raises BrocadeZoningHttpException:
|
|
"""
|
|
active_zone_set = {}
|
|
zones_map = {}
|
|
try:
|
|
self.get_zone_info() # get the zone information of the switch
|
|
if self.active_cfg != '':
|
|
# get the zones list of the active_Cfg
|
|
zones_list = self.cfgs[self.active_cfg].split(";")
|
|
for n in zones_list:
|
|
# build the zones map
|
|
zones_map.update(
|
|
{n: self.zones[n].split(";")})
|
|
# Format map in the correct format
|
|
active_zone_set = {
|
|
"active_zone_config": self.active_cfg, "zones": zones_map}
|
|
return active_zone_set
|
|
except Exception as e:
|
|
msg = (_("Failed getting active zone set from fabric %s.")
|
|
% six.text_type(e))
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
|
|
def add_zones(self, add_zones_info, activate, active_zone_set=None):
|
|
"""Add zone configuration.
|
|
|
|
This method will add the zone configuration passed by user.
|
|
|
|
:param add_zones_info: Zone names mapped to members. Zone members
|
|
are colon separated but case-insensitive
|
|
|
|
.. code-block:: python
|
|
|
|
{ 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']
|
|
}
|
|
|
|
:param activate: True will activate the zone config.
|
|
:param active_zone_set: Active zone set dict retrieved from
|
|
get_active_zone_set method
|
|
:raises BrocadeZoningHttpException:
|
|
"""
|
|
LOG.debug("Add zones - zones passed: %(zones)s.",
|
|
{'zones': add_zones_info})
|
|
cfg_name = zone_constant.CFG_NAME
|
|
cfgs = self.cfgs
|
|
zones = self.zones
|
|
alias = self.alias
|
|
qlps = self.qlps
|
|
ifas = self.ifas
|
|
active_cfg = self.active_cfg
|
|
# update the active_cfg, zones and cfgs map with new information
|
|
zones, cfgs, active_cfg = self.add_zones_cfgs(cfgs,
|
|
zones,
|
|
add_zones_info,
|
|
active_cfg,
|
|
cfg_name)
|
|
# Build the zonestring with updated maps
|
|
data = self.form_zone_string(cfgs,
|
|
active_cfg,
|
|
zones,
|
|
alias,
|
|
qlps,
|
|
ifas,
|
|
activate)
|
|
LOG.debug("Add zones: final zone string after applying "
|
|
"to the switch: %(zonestring)s", {'zonestring': data})
|
|
# Post the zone data to the switch
|
|
error_code, error_msg = self.post_zone_data(data)
|
|
if error_code != "0":
|
|
msg = (_("Applying the zones and cfgs to the switch failed "
|
|
"(error code=%(err_code)s error msg=%(err_msg)s.")
|
|
% {'err_code': error_code, 'err_msg': error_msg})
|
|
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
|
|
def update_zones(self, zone_info, activate, operation,
|
|
active_zone_set=None):
|
|
"""Update zone configuration.
|
|
|
|
This method will update the zone configuration passed by user.
|
|
|
|
:param zone_info: Zone names mapped to members. Zone members
|
|
are colon separated but case-insensitive
|
|
|
|
.. code-block:: python
|
|
|
|
{ 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']
|
|
}
|
|
|
|
:param activate: True will activate the zone config.
|
|
:param operation: ZONE_ADD or ZONE_REMOVE
|
|
:param active_zone_set: Active zone set dict retrieved from
|
|
get_active_zone_set method
|
|
:raises BrocadeZoningHttpException:
|
|
"""
|
|
LOG.debug("Update zones - zones passed: %(zones)s.",
|
|
{'zones': zone_info})
|
|
cfgs = self.cfgs
|
|
zones = self.zones
|
|
alias = self.alias
|
|
qlps = self.qlps
|
|
ifas = self.ifas
|
|
active_cfg = self.active_cfg
|
|
# update the zones with new information
|
|
zones = self._update_zones(zones, zone_info, operation)
|
|
# Build the zonestring with updated maps
|
|
data = self.form_zone_string(cfgs,
|
|
active_cfg,
|
|
zones,
|
|
alias,
|
|
qlps,
|
|
ifas,
|
|
activate)
|
|
LOG.debug("Update zones: final zone string after applying "
|
|
"to the switch: %(zonestring)s", {'zonestring': data})
|
|
# Post the zone data to the switch
|
|
error_code, error_msg = self.post_zone_data(data)
|
|
if error_code != "0":
|
|
msg = (_("Applying the zones and cfgs to the switch failed "
|
|
"(error code=%(err_code)s error msg=%(err_msg)s.")
|
|
% {'err_code': error_code, 'err_msg': error_msg})
|
|
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
|
|
def form_zone_string(self, cfgs, active_cfg,
|
|
zones, alias, qlps, ifas, activate):
|
|
"""Build the zone string in the required format.
|
|
|
|
:param cfgs: cfgs map
|
|
:param active_cfg: Active cfg string
|
|
:param zones: zones map
|
|
:param alias: alias map
|
|
:param qlps: qlps map
|
|
:param ifas: ifas map
|
|
:param activate: True will activate config.
|
|
:returns: zonestring in the required format
|
|
:raises BrocadeZoningHttpException:
|
|
"""
|
|
try:
|
|
zoneString = zone_constant.ZONE_STRING_PREFIX
|
|
|
|
# based on the activate save only will be changed
|
|
saveonly = "false" if activate is True else "true"
|
|
|
|
# Form the zone string based on the dictionary of each items
|
|
for cfg in sorted(cfgs.keys()):
|
|
zoneString += (zone_constant.CFG_DELIM +
|
|
cfg + " " + cfgs.get(cfg) + " ")
|
|
for zone in sorted(zones.keys()):
|
|
zoneString += (zone_constant.ZONE_DELIM +
|
|
zone + " " + zones.get(zone) + " ")
|
|
for al in sorted(alias.keys()):
|
|
zoneString += (zone_constant.ALIAS_DELIM +
|
|
al + " " + alias.get(al) + " ")
|
|
for qlp in sorted(qlps.keys()):
|
|
zoneString += (zone_constant.QLP_DELIM +
|
|
qlp + " " + qlps.get(qlp) + " ")
|
|
for ifa in sorted(ifas.keys()):
|
|
zoneString += (zone_constant.IFA_DELIM +
|
|
ifa + " " + ifas.get(ifa) + " ")
|
|
# append the active_cfg string only if it is not null and activate
|
|
# is true
|
|
if active_cfg != "" and activate:
|
|
zoneString += (zone_constant.ACTIVE_CFG_DELIM +
|
|
active_cfg + " null ")
|
|
# Build the final zone string
|
|
zoneString += zone_constant.ZONE_END_DELIM + saveonly
|
|
except Exception as e:
|
|
msg = (_("Exception while forming the zone string: %s.")
|
|
% six.text_type(e))
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
# Reconstruct the zoneString to type base string for OpenSSL
|
|
return encodeutils.safe_encode(zoneString)
|
|
|
|
def add_zones_cfgs(self, cfgs, zones, add_zones_info,
|
|
active_cfg, cfg_name):
|
|
"""Add the zones and cfgs map based on the new zones info.
|
|
|
|
This method will return the updated zones,cfgs and active_cfg
|
|
|
|
:param cfgs: Existing cfgs map
|
|
:param active_cfg: Existing Active cfg string
|
|
:param zones: Existing zones map
|
|
:param add_zones_info: Zones map to add
|
|
:param active_cfg: Existing active cfg
|
|
:param cfg_name: New cfg name
|
|
:returns: updated zones, zone configs map, and active_cfg
|
|
"""
|
|
cfg_string = ""
|
|
delimiter = ""
|
|
zones_in_active_cfg = ""
|
|
try:
|
|
if active_cfg:
|
|
zones_in_active_cfg = cfgs.get(active_cfg)
|
|
for zone_name, members in add_zones_info.items():
|
|
# if new zone is not active_cfg, build the cfg string with the
|
|
# new zones
|
|
if zone_name not in zones_in_active_cfg:
|
|
cfg_string += delimiter + zone_name
|
|
delimiter = ";"
|
|
# add a new zone with the members
|
|
zones.update({zone_name: ";".join(members)})
|
|
# update cfg string
|
|
if active_cfg:
|
|
if cfg_string:
|
|
# update the existing active cfg map with cfgs string
|
|
cfgs.update(
|
|
{active_cfg: cfg_string + ";" + cfgs.get(active_cfg)})
|
|
else:
|
|
# create new cfg and update that cfgs map with the new cfg
|
|
active_cfg = cfg_name
|
|
cfgs.update({cfg_name: cfg_string})
|
|
except Exception as e:
|
|
msg = (_("Error while updating the new zones and cfgs "
|
|
"in the zone string. Error %(description)s.")
|
|
% {'description': six.text_type(e)})
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
return zones, cfgs, active_cfg
|
|
|
|
def _update_zones(self, zones, updated_zones_info, operation):
|
|
"""Update the zones based on the updated zones info.
|
|
|
|
This method will return the updated zones
|
|
|
|
:param zones: Existing zones map
|
|
:param updated_zones_info: Zones map to update
|
|
:param operation: ZONE_ADD or ZONE_REMOVE
|
|
:returns: updated zones
|
|
"""
|
|
try:
|
|
for zone_name in updated_zones_info:
|
|
members = updated_zones_info[zone_name]
|
|
# update the zone string
|
|
# if zone name already exists and dont have the new members
|
|
# already
|
|
current_members = zones.get(zone_name).split(";")
|
|
if operation == zone_constant.ZONE_ADD:
|
|
new_members = set(members).difference(set(current_members))
|
|
if new_members:
|
|
# update the existing zone with new members
|
|
zones.update({zone_name: (";".join(new_members) +
|
|
";" + zones.get(zone_name))})
|
|
else:
|
|
new_members = set(current_members).difference(set(members))
|
|
if new_members:
|
|
zones.pop(zone_name)
|
|
zones.update({zone_name: ";".join(new_members)})
|
|
except Exception as e:
|
|
msg = (_("Error while updating the zones "
|
|
"in the zone string. Error %(description)s.")
|
|
% {'description': six.text_type(e)})
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
return zones
|
|
|
|
def is_vf_enabled(self):
|
|
"""To check whether VF is enabled or not.
|
|
|
|
:returns: boolean to indicate VF enabled and session information
|
|
"""
|
|
session_info = self.get_session_info()
|
|
parsed_data = self.get_parsed_data(session_info,
|
|
zone_constant.SESSION_BEGIN,
|
|
zone_constant.SESSION_END)
|
|
try:
|
|
is_vf_enabled = bool(self.get_nvp_value(
|
|
parsed_data, zone_constant.VF_ENABLED))
|
|
except exception.BrocadeZoningHttpException:
|
|
is_vf_enabled = False
|
|
parsed_data = None
|
|
return is_vf_enabled, parsed_data
|
|
|
|
def get_nameserver_info(self):
|
|
"""Get name server data from fabric.
|
|
|
|
Return the connected node port wwn list(local
|
|
and remote) for the given switch fabric.
|
|
|
|
:returns: name server information.
|
|
"""
|
|
nsinfo = []
|
|
headers = {zone_constant.AUTH_HEADER: self.auth_header}
|
|
response = self.connect(zone_constant.GET_METHOD,
|
|
zone_constant.NS_PAGE,
|
|
header=headers) # GET request to nsinfo.html
|
|
parsed_raw_zoneinfo = self.get_parsed_data(
|
|
response,
|
|
zone_constant.NSINFO_BEGIN,
|
|
zone_constant.NSINFO_END).strip("\t\n\r")
|
|
# build the name server information in the correct format
|
|
for line in parsed_raw_zoneinfo.splitlines():
|
|
start_index = line.find(zone_constant.NS_DELIM) + 7
|
|
if start_index != -1:
|
|
nsinfo.extend([line[start_index:start_index + 23].strip()])
|
|
return nsinfo
|
|
|
|
def delete_zones_cfgs(
|
|
self, cfgs, zones,
|
|
delete_zones_info, active_cfg):
|
|
"""Delete the zones and cfgs map based on the new zones info.
|
|
|
|
Return the updated zones, cfgs and active_cfg after deleting the
|
|
required items.
|
|
|
|
:param cfgs: Existing cfgs map
|
|
:param active_cfg: Existing Active cfg string
|
|
:param zones: Existing zones map
|
|
:param delete_zones_info: Zones map to add
|
|
:param active_cfg: Existing active cfg
|
|
:returns: updated zones, zone config sets, and active zone config
|
|
:raises BrocadeZoningHttpException:
|
|
"""
|
|
try:
|
|
delete_zones_info = delete_zones_info.split(";")
|
|
for zone in delete_zones_info:
|
|
# remove the zones from the zone map
|
|
zones.pop(zone)
|
|
# iterated all the cfgs, but need to check since in SSH only
|
|
# active cfg is iterated
|
|
for k, v in cfgs.items():
|
|
v = v.split(";")
|
|
if zone in v:
|
|
# remove the zone from the cfg string
|
|
v.remove(zone)
|
|
# if all the zones are removed, remove the cfg from the
|
|
# cfg map
|
|
if not v:
|
|
cfgs.pop(k)
|
|
# update the original cfg with the updated string
|
|
else:
|
|
cfgs[k] = ";".join(v)
|
|
|
|
# if all the zones are removed in the active_cfg, update it with
|
|
# empty string
|
|
if active_cfg not in cfgs:
|
|
active_cfg = ""
|
|
except KeyError as e:
|
|
msg = (_("Error while removing the zones and cfgs "
|
|
"in the zone string: %(description)s.")
|
|
% {'description': six.text_type(e)})
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
return zones, cfgs, active_cfg
|
|
|
|
def delete_zones(self, delete_zones_info, activate, active_zone_set=None):
|
|
"""Delete zones from fabric.
|
|
|
|
Deletes zones in the active zone config.
|
|
|
|
:param zone_names: zoneNames separated by semicolon
|
|
:param activate: True/False
|
|
:param active_zone_set: the active zone set dict retrieved
|
|
from get_active_zone_set method
|
|
"""
|
|
cfgs = self.cfgs
|
|
zones = self.zones
|
|
alias = self.alias
|
|
qlps = self.qlps
|
|
ifas = self.ifas
|
|
active_cfg = self.active_cfg
|
|
# update the active_cfg, zones and cfgs map with required information
|
|
# being removed
|
|
zones, cfgs, active_cfg = self.delete_zones_cfgs(
|
|
cfgs,
|
|
zones,
|
|
delete_zones_info,
|
|
active_cfg)
|
|
# Build the zonestring with updated maps
|
|
data = self.form_zone_string(cfgs,
|
|
active_cfg,
|
|
zones,
|
|
alias,
|
|
qlps,
|
|
ifas,
|
|
activate)
|
|
LOG.debug("Delete zones: final zone string after applying "
|
|
"to the switch: %(zonestring)s", {'zonestring': data})
|
|
error_code, error_msg = self.post_zone_data(data)
|
|
if error_code != "0":
|
|
msg = (_("Applying the zones and cfgs to the switch failed "
|
|
"(error code=%(err_code)s error msg=%(err_msg)s.")
|
|
% {'err_code': error_code, 'err_msg': error_msg})
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
|
|
def post_zone_data(self, data):
|
|
"""Send POST request to the switch with the payload.
|
|
|
|
:param data: payload to be sent to switch
|
|
"""
|
|
|
|
status = "progress"
|
|
parsed_data_txn = ""
|
|
headers = {zone_constant.AUTH_HEADER: self.auth_header}
|
|
|
|
LOG.debug("Requesting the switch with posting the zone string.")
|
|
# POST request to gzoneinfo with zonestring as payload
|
|
response = self.connect(zone_constant.POST_METHOD,
|
|
zone_constant.ZONE_PAGE,
|
|
data,
|
|
headers)
|
|
parsed_data = self.get_parsed_data(response,
|
|
zone_constant.ZONE_TX_BEGIN,
|
|
zone_constant.ZONE_TX_END)
|
|
transID = self.get_nvp_value(parsed_data,
|
|
zone_constant.ZONE_TX_ID)
|
|
transURL = zone_constant.ZONE_TRAN_STATUS.format(txnId=transID)
|
|
timeout = 360
|
|
sleep_time = 3
|
|
time_elapsed = 0
|
|
while(status != "done"):
|
|
txn_response = self.connect(
|
|
zone_constant.GET_METHOD, transURL, "", headers)
|
|
parsed_data_txn = self.get_parsed_data(txn_response,
|
|
zone_constant.ZONE_TX_BEGIN,
|
|
zone_constant.ZONE_TX_END)
|
|
status = self.get_nvp_value(parsed_data_txn,
|
|
zone_constant.ZONE_TX_STATUS)
|
|
time.sleep(sleep_time)
|
|
time_elapsed += sleep_time
|
|
if time_elapsed > timeout:
|
|
break
|
|
if status != "done":
|
|
errorCode = -1
|
|
errorMessage = ("Timed out, waiting for zone transaction on "
|
|
"the switch to complete")
|
|
else:
|
|
errorCode = self.get_nvp_value(parsed_data_txn,
|
|
zone_constant.ZONE_ERROR_CODE)
|
|
errorMessage = self.get_nvp_value(parsed_data_txn,
|
|
zone_constant.ZONE_ERROR_MSG)
|
|
return errorCode, errorMessage
|
|
|
|
def check_change_vf_context(self):
|
|
"""Check whether VF related configurations is valid and proceed."""
|
|
vf_enabled, session_data = self.is_vf_enabled()
|
|
# VF enabled will be false if vf is disable or not supported
|
|
LOG.debug("VF enabled on switch: %(vfenabled)s.",
|
|
{'vfenabled': vf_enabled})
|
|
# Change the VF context in the session
|
|
if vf_enabled:
|
|
if self.vfid is None:
|
|
msg = _("No VF ID is defined in the configuration file.")
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
elif self.vfid != 128:
|
|
self.change_vf_context(self.vfid, session_data)
|
|
else:
|
|
if self.vfid is not None:
|
|
msg = _("VF is not enabled.")
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
|
|
def _disconnect(self):
|
|
"""Disconnect from the switch using HTTP/HTTPS protocol.
|
|
|
|
:raises BrocadeZoningHttpException:
|
|
"""
|
|
try:
|
|
headers = {zone_constant.AUTH_HEADER: self.auth_header}
|
|
response = self.connect(zone_constant.GET_METHOD,
|
|
zone_constant.LOGOUT_PAGE,
|
|
header=headers)
|
|
return response
|
|
except requests.exceptions.ConnectionError as e:
|
|
msg = (_("Error while connecting the switch %(switch_id)s "
|
|
"with protocol %(protocol)s. Error: %(error)s.")
|
|
% {'switch_id': self.switch_ip,
|
|
'protocol': self.protocol,
|
|
'error': six.text_type(e)})
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
except exception.BrocadeZoningHttpException as ex:
|
|
msg = (_("Unexpected status code from the switch %(switch_id)s "
|
|
"with protocol %(protocol)s for url %(page)s. "
|
|
"Error: %(error)s")
|
|
% {'switch_id': self.switch_ip,
|
|
'protocol': self.protocol,
|
|
'page': zone_constant.LOGOUT_PAGE,
|
|
'error': six.text_type(ex)})
|
|
LOG.error(msg)
|
|
raise exception.BrocadeZoningHttpException(reason=msg)
|
|
|
|
def cleanup(self):
|
|
"""Close session."""
|
|
self._disconnect()
|
|
self.session.close()
|