# Copyright (c) 2013, Citrix Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Object for manipulating dynamic rules.
The dynamic rules file on disk is a JSON file with optional line comments
beginning with a # character.
"""
from __future__ import unicode_literals
from os.path import exists as pathexists
import json
__version__ = "1.0.0"
__author__ = "Andrew Cooper"
from xcp.compat import open_with_codec_handling
from xcp.logger import LOG
from xcp.net.ifrename.macpci import MACPCI
from xcp.pci import pci_sbdfi_to_nic
SAVE_HEADER = (
"# Automatically adjusted file. Do not edit unless you are "
"certain you know how to\n"
)
[docs]
class DynamicRules(object):
"""
Object for parsing the dynamic rules configuration.
There are two distinct usecases; the installer needs to write the
dynamic rules from scratch, whereas interface-rename.py in dom0 needs
to read and write them.
"""
def __init__(self, path=None, fd=None):
self.path = path
self.fd = fd
self.lastboot = []
self.old = []
self.formulae = {}
self.rules = []
[docs]
def load_and_parse(self):
"""
Parse the dynamic rules file.
Returns boolean indicating success or failure.
"""
fd = None
try:
try:
# If we were given a path, try opening and reading it
if self.path:
if not pathexists(self.path):
LOG.error("Dynamic rule file '%s' does not exist"
% (self.path,))
return False
fd = open_with_codec_handling(self.path, "r")
raw_lines = fd.readlines()
# else if we were given a file descriptor, just read it
elif self.fd:
raw_lines = self.fd.readlines()
# else there is nothing we can do
else:
LOG.error("No source of data to parse")
return False
except IOError as e:
LOG.error("IOError while reading file: %s" % (e,))
return False
finally:
# Ensure we alway close the file descriptor we opened
if fd:
fd.close()
# Strip out line comments
data = "\n".join([ l.strip() for l in raw_lines
if len(l.strip()) and l.strip()[0] != '#' ]
)
# If the data is empty, don't pass it to json
if not len( data.strip() ):
return True
try:
info = json.loads(data)
except ValueError:
LOG.warning("Dynamic rules appear to be corrupt")
return False
if "lastboot" in info:
for entry in info["lastboot"]:
try:
if len(entry) != 3:
raise ValueError("Expected 3 entries")
macpci = MACPCI(entry[0], entry[1], tname=entry[2])
except (TypeError, ValueError) as e:
LOG.warning("Invalid lastboot data entry: %s"
% (e,))
continue
self.lastboot.append(macpci)
if "old" in info:
for entry in info["old"]:
try:
if len(entry) != 3:
raise ValueError("Expected 3 entries")
macpci = MACPCI(entry[0], entry[1], tname=entry[2])
except (TypeError, ValueError) as e:
LOG.warning("Invalid old data entry: %s" % (e,))
continue
self.old.append(macpci)
return True
[docs]
def generate(self, state):
"""
Make rules from the formulae based on global state.
"""
# CA-75599 - check that state has no shared ppns.
# See net.biodevname.has_ppn_quirks() for full reason
ppns = [ x.ppn for x in state if x.ppn is not None ]
ppn_quirks = ( len(ppns) != len(set(ppns)) )
if ppn_quirks:
LOG.warning("Discovered physical policy naming quirks in provided "
"state. Disabling 'method=ppn' generation")
for target, (method, value) in self.formulae.items():
if method == "mac":
for nic in state:
if nic.mac == value:
try:
rule = MACPCI(nic.mac, nic.pci, tname=target)
except Exception as e:
LOG.warning("Error creating rule: %s" % (e,))
continue
self.rules.append(rule)
break
else:
LOG.warning("No NIC found with a MAC address of '%s' for "
"the %s dynamic rule" % (value, target))
continue
if method == "ppn":
if ppn_quirks:
LOG.info("Not considering formula for '%s' due to ppn "
"quirks" % (target,))
continue
for nic in state:
if nic.ppn == value:
try:
rule = MACPCI(nic.mac, nic.pci, tname=target)
except Exception as e:
LOG.warning("Error creating rule: %s" % (e,))
continue
self.rules.append(rule)
break
else:
LOG.warning("No NIC found with a ppn of '%s' for the "
"%s dynamic rule" % (value, target))
continue
if method == "pci":
try:
nic = pci_sbdfi_to_nic(value, state)
rule = MACPCI(nic.mac, nic.pci, tname=target)
except Exception as e:
LOG.warning("Error creating rule: %s" % (e,))
continue
self.rules.append(rule)
continue
if method == "label":
for nic in state:
if nic.label == value:
try:
rule = MACPCI(nic.mac, nic.pci, tname=target)
except Exception as e:
LOG.warning("Error creating rule: %s" % (e,))
continue
self.rules.append(rule)
break
else:
LOG.warning("No NIC found with an SMBios Label of '%s' for "
"the %s dynamic rule" % (value, target))
continue
LOG.critical("Unknown dynamic rule method %s" % method)
[docs]
def write(self, header = True):
"""
Write the dynamic rules to a string
"""
res = ""
if header:
res += SAVE_HEADER
def validate(entry):
try:
# iBFT NICs are ignored so don't have a tname
if entry[2] is None:
return False
MACPCI(entry[0], entry[1], tname=entry[2])
return True
except Exception as e:
LOG.warning("Failed to validate '%s' because '%s'"
% (entry, e))
return False
lastboot = [x for x in self.lastboot if validate(x)]
old = [x for x in self.old if validate(x)]
res += json.dumps({"lastboot": lastboot, "old": old},
indent=4, sort_keys=True)
return res
[docs]
def save(self, header = True):
"""
Save the dynamic rules to a file/path.
Returns boolean indicating success or failure.
"""
fd = None
try:
try:
# If we were given a path, try opening and writing to it
if self.path:
fd = open_with_codec_handling(self.path, "w")
fd.write(self.write(header))
# else if we were given a file descriptor, just write to it
elif self.fd:
self.fd.write(self.write(header))
# else there is nothing we can do
else:
LOG.error("No source of data to parse")
return False
except IOError as e:
LOG.error("IOError while reading file: %s" % (e,))
return False
finally:
# Ensure we alway close the file descriptor we opened
if fd:
fd.close()
return True