309 lines
11 KiB
Python
Executable File
309 lines
11 KiB
Python
Executable File
#!/usr/bin/python
|
|
# Copyright 2015 Mellanox Technologies, Ltd
|
|
#
|
|
# 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.
|
|
|
|
import os
|
|
import sys
|
|
import yaml
|
|
import glob
|
|
import traceback
|
|
|
|
MLNX_SECTION = 'mellanox-plugin'
|
|
SETTINGS_FILE = '/etc/astute.yaml'
|
|
PLUGIN_OVERRIDE_FILE = '/etc/hiera/override/plugins.yaml'
|
|
MLNX_DRIVERS_LIST = ('mlx4_en', 'eth_ipoib')
|
|
ISER_IFC_NAME = 'eth_iser0'
|
|
|
|
|
|
class MellanoxSettingsException(Exception):
|
|
pass
|
|
|
|
class MellanoxSettings(object):
|
|
|
|
data = None
|
|
|
|
@classmethod
|
|
def get_mlnx_section(cls):
|
|
if cls.data is None:
|
|
raise MellanoxSettingsException("No YAML file loaded")
|
|
if MLNX_SECTION not in cls.data:
|
|
raise MellanoxSettingsException(
|
|
"Couldn't find section '{0}'".format(MLNX_SECTION)
|
|
)
|
|
return cls.data[MLNX_SECTION]
|
|
|
|
@classmethod
|
|
def get_bridge_for_network(cls, network):
|
|
network_to_bridge = {
|
|
'private': 'prv',
|
|
'management': 'mgmt',
|
|
'storage': 'storage',
|
|
}
|
|
return 'br-{0}'.format(network_to_bridge[network])
|
|
|
|
@classmethod
|
|
def get_interface_by_network(cls, network):
|
|
if network not in ('management', 'storage', 'private'):
|
|
raise MellanoxSettingsException("Unknown network: {0}".format(network))
|
|
br_name = cls.get_bridge_for_network(network)
|
|
endpoints = cls.get_endpoints_section()
|
|
ifc = endpoints[br_name]['vendor_specific']['phy_interfaces'][0]
|
|
return ifc
|
|
|
|
@classmethod
|
|
def add_driver(cls):
|
|
interfaces = cls.get_interfaces_section()
|
|
mlnx = cls.get_mlnx_section()
|
|
|
|
# validation that no more than 1 mellanox driver is used
|
|
interfaces_drivers = {}
|
|
for ifc in cls.get_physical_interfaces():
|
|
if ('driver' not in interfaces[ifc]['vendor_specific']) :
|
|
raise MellanoxSettingsException(
|
|
"Couldn't find 'driver' for interface '{0}'".format(ifc)
|
|
)
|
|
interfaces_drivers[ifc] = interfaces[ifc]['vendor_specific']['driver']
|
|
mlnx_drivers = dict(
|
|
(ifc, drv) for (ifc, drv) in interfaces_drivers.iteritems()
|
|
if drv in MLNX_DRIVERS_LIST
|
|
)
|
|
if len(set(mlnx_drivers.values())) > 1:
|
|
raise MellanoxSettingsException(
|
|
"Found mismatching Mellanox drivers on different interfaces: "
|
|
"{0}".format(mlnx_drivers)
|
|
)
|
|
|
|
# add the driver to the yaml
|
|
mlnx['driver'] = mlnx_drivers.values().pop()
|
|
|
|
@classmethod
|
|
def add_physical_port(cls):
|
|
interfaces = cls.get_interfaces_section()
|
|
mlnx = cls.get_mlnx_section()
|
|
|
|
private_ifc = cls.get_interface_by_network('private')
|
|
if mlnx['driver'] == 'eth_ipoib':
|
|
if 'bus_info' not in interfaces[private_ifc]['vendor_specific']:
|
|
raise MellanoxSettingsException(
|
|
"Couldn't find 'bus_info' for interface "
|
|
"{0}".format(private_ifc)
|
|
)
|
|
mlnx['physical_port'] = interfaces[private_ifc]['vendor_specific']['bus_info']
|
|
elif mlnx['driver'] == 'mlx4_en':
|
|
mlnx['physical_port'] = private_ifc
|
|
|
|
@classmethod
|
|
def add_storage_vlan(cls):
|
|
mlnx = cls.get_mlnx_section()
|
|
endpoints = cls.get_endpoints_section()
|
|
try:
|
|
vlan = int(endpoints['br-storage']['vendor_specific']['vlans'])
|
|
except ValueError:
|
|
raise MellanoxSettingsException(
|
|
"Failed reading vlan for br-storage"
|
|
)
|
|
mlnx['storage_vlan'] = vlan
|
|
|
|
@classmethod
|
|
def add_storage_parent(cls):
|
|
mlnx = cls.get_mlnx_section()
|
|
storage_ifc = cls.get_interface_by_network('storage')
|
|
mlnx['storage_parent'] = storage_ifc
|
|
|
|
@classmethod
|
|
def add_iser_interface_name(cls):
|
|
mlnx = cls.get_mlnx_section()
|
|
storage_ifc = cls.get_interface_by_network('storage')
|
|
if mlnx['driver'] == 'mlx4_en':
|
|
mlnx['iser_ifc_name'] = ISER_IFC_NAME
|
|
elif mlnx['driver'] == 'eth_ipoib':
|
|
interfaces = cls.get_interfaces_section()
|
|
mlnx['iser_ifc_name'] = interfaces[storage_ifc]['vendor_specific']['bus_info']
|
|
else:
|
|
raise MellanoxSettingsException("Could not find 'driver' in "
|
|
"{0} section".format(MLNX_SECTION))
|
|
|
|
@classmethod
|
|
def set_storage_networking_scheme(cls):
|
|
endpoints = cls.get_endpoints_section()
|
|
interfaces = cls.get_interfaces_section()
|
|
transformations = cls.data['network_scheme']['transformations']
|
|
mlnx = cls.get_mlnx_section()
|
|
|
|
# Handle iSER interface with and w/o vlan tagging
|
|
storage_vlan = mlnx['storage_vlan']
|
|
if storage_vlan:
|
|
vlan_name = "{0}.{1}".format(ISER_IFC_NAME, storage_vlan)
|
|
# Set storage rule to iSER interface vlan interface
|
|
cls.data['network_scheme']['roles']['storage'] = vlan_name
|
|
# Set iSER interface vlan interface
|
|
transf_add = {
|
|
'action': 'add-port',
|
|
'name': vlan_name,
|
|
'vlan_id': int(storage_vlan),
|
|
'vlan_dev': ISER_IFC_NAME
|
|
}
|
|
if transf_add not in transformations:
|
|
transformations.append(transf_add)
|
|
transformations_to_delete = [
|
|
{ 'action': 'add-port',
|
|
'name': "{0}.{1}".format(
|
|
cls.get_interface_by_network('storage'),
|
|
storage_vlan
|
|
),
|
|
'bridge': 'br-storage' } ,
|
|
{ 'action': 'add-br',
|
|
'name': 'br-storage' }
|
|
]
|
|
endpoints['br-storage']['vendor_specific']['phy_interfaces'] = [ ISER_IFC_NAME ]
|
|
endpoints[vlan_name] = (
|
|
endpoints.pop('br-storage', {})
|
|
)
|
|
for transf_del in transformations_to_delete:
|
|
transformations.remove(transf_del)
|
|
else:
|
|
# Set storage rule to iSER port
|
|
cls.data['network_scheme']['roles']['storage'] = ISER_IFC_NAME
|
|
# Set iSER endpoint with br-storage parameters
|
|
endpoints[ISER_IFC_NAME] = (
|
|
endpoints.pop('br-storage', {})
|
|
)
|
|
interfaces[ISER_IFC_NAME] = {}
|
|
|
|
@classmethod
|
|
def get_endpoints_section(cls):
|
|
return cls.data['network_scheme']['endpoints']
|
|
|
|
@classmethod
|
|
def get_physical_interfaces(cls):
|
|
endpoints = cls.get_endpoints_section()
|
|
interfaces = cls.get_interfaces_section()
|
|
mlnx_phys_ifcs = []
|
|
for ep in endpoints:
|
|
# skip non physical interfaces
|
|
if ('vendor_specific' not in endpoints[ep] or
|
|
'phy_interfaces' not in endpoints[ep]['vendor_specific']):
|
|
continue
|
|
phys_ifc = endpoints[ep]['vendor_specific']['phy_interfaces'][0]
|
|
if ('vendor_specific' not in interfaces[phys_ifc] or
|
|
'driver' not in interfaces[phys_ifc]['vendor_specific']):
|
|
raise MellanoxSettingsException(
|
|
"Missing 'vendor_specific' or 'driver' "
|
|
"in {0}".format(phys_ifc)
|
|
)
|
|
if (interfaces[phys_ifc]['vendor_specific']['driver'] in
|
|
MLNX_DRIVERS_LIST):
|
|
mlnx_phys_ifcs.append(phys_ifc)
|
|
return list(set(mlnx_phys_ifcs))
|
|
|
|
@classmethod
|
|
def get_interfaces_section(cls):
|
|
return cls.data['network_scheme']['interfaces']
|
|
|
|
@classmethod
|
|
def is_iser_enabled(cls):
|
|
return cls.get_mlnx_section()['iser']
|
|
|
|
@classmethod
|
|
def update_role_settings(cls):
|
|
# realize the driver in use (eth/ib)
|
|
cls.add_driver()
|
|
# decide the physical function for SR-IOV
|
|
cls.add_physical_port()
|
|
# set iSER parameters
|
|
if cls.is_iser_enabled():
|
|
cls.add_storage_parent()
|
|
cls.add_storage_vlan()
|
|
cls.add_iser_interface_name()
|
|
cls.set_storage_networking_scheme()
|
|
|
|
@classmethod
|
|
def read_from_yaml(cls, settings_file):
|
|
try:
|
|
fd = open(settings_file, 'r')
|
|
except IOError:
|
|
raise MellanoxSettingsException("Given YAML file {0} doesn't "
|
|
"exist".format(settings_file))
|
|
try:
|
|
data = yaml.load(fd)
|
|
except yaml.YAMLError, exc:
|
|
if hasattr(exc, 'problem_mark'):
|
|
mark = exc.problem_mark
|
|
raise MellanoxSettingsException(
|
|
"Faild parsing YAML file {0}: error position "
|
|
"({2},{3})".format(mark.line+1, mark.column+1)
|
|
)
|
|
finally:
|
|
fd.close()
|
|
cls.data = data
|
|
|
|
@classmethod
|
|
def write_to_yaml(cls, settings_file):
|
|
# choose only the edited sections
|
|
data = {}
|
|
data['network_scheme'] = cls.data['network_scheme']
|
|
data[MLNX_SECTION] = cls.data[MLNX_SECTION]
|
|
# create containing adir
|
|
try:
|
|
settings_dir = os.path.dirname(settings_file)
|
|
if not os.path.isdir(settings_dir):
|
|
os.makedirs(settings_dir)
|
|
except OSError:
|
|
raise MellanoxSettingsException(
|
|
"Failed creating directory: {0}".format(settings_dir)
|
|
)
|
|
try:
|
|
fd = open(settings_file, 'w')
|
|
yaml.dump(data, fd, default_flow_style=False)
|
|
except IOError:
|
|
raise MellanoxSettingsException("Failed writing changes to "
|
|
"{0}".format(settings_file))
|
|
finally:
|
|
if fd:
|
|
fd.close()
|
|
|
|
@classmethod
|
|
def update_settings(cls):
|
|
# define input yaml file
|
|
try:
|
|
cls.read_from_yaml(SETTINGS_FILE)
|
|
cls.update_role_settings()
|
|
cls.write_to_yaml(PLUGIN_OVERRIDE_FILE)
|
|
except MellanoxSettingsException, exc:
|
|
sys.stderr.write("Couldn't add Mellanox settings to "
|
|
"{0}: {1}\n".format(settings_file, exc))
|
|
raise MellanoxSettingsException("Failed updating one or more "
|
|
"setting files")
|
|
|
|
def main():
|
|
try:
|
|
settings = MellanoxSettings()
|
|
settings.update_settings()
|
|
except MellanoxSettingsException, exc:
|
|
sys.stderr.write("Failed adding Mellanox settings: {0}\n".format(exc))
|
|
sys.exit(1)
|
|
except Exception, exc:
|
|
sys.stderr.write("An unknown error has occured while adding "
|
|
"Mellanox settings: {0}\n".format(
|
|
traceback.format_exc()
|
|
)
|
|
)
|
|
sys.exit(1)
|
|
sys.stdout.write("Done adding Mellanox settings\n")
|
|
sys.exit(0)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|