#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- mode: python ; coding: utf-8 -*-
#
# Copyright (C) 2015-2015: Alignak team, see AUTHORS.txt file for contributors
#
# This file is part of Alignak.
#
# Alignak is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Alignak is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Alignak. If not, see <http://www.gnu.org/licenses/>.
#
#
# This file incorporates work covered by the following copyright and
# permission notice:
#
# Copyright (C) 2009-2014:
# Hartmut Goebel, h.goebel@goebel-consult.de
# Guillaume Bour, guillaume@bour.cc
# Frédéric Vachon, fredvac@gmail.com
# aviau, alexandre.viau@savoirfairelinux.com
# Nicolas Dupeux, nicolas@dupeux.net
# Grégory Starck, g.starck@gmail.com
# Gerhard Lausser, gerhard.lausser@consol.de
# Sebastien Coavoux, s.coavoux@free.fr
# Christophe Simon, geektophe@gmail.com
# Jean Gabes, naparuba@gmail.com
# Romain Forlot, rforlot@yahoo.com
# Christophe SIMON, christophe.simon@dailymotion.com
# This file is part of Shinken.
#
# Shinken is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Shinken is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Shinken. If not, see <http://www.gnu.org/licenses/>.
"""This module provides property classes.
It is used during configuration parsing to ensure attribute type in objects.
Each class implements a pythonize method that cast data into the wanted type.
"""
import re
from alignak.util import to_float, to_split, to_char, to_int, unique_value, list_split
import logging
__all__ = ('UnusedProp', 'BoolProp', 'IntegerProp', 'FloatProp',
'CharProp', 'StringProp', 'ListProp',
'FULL_STATUS', 'CHECK_RESULT')
# Suggestion
# Is this useful? see above
__author__ = "Hartmut Goebel <h.goebel@goebel-consult.de>"
__copyright__ = "Copyright 2010-2011 by Hartmut Goebel <h.goebel@goebel-consult.de>"
__licence__ = "GNU Affero General Public License version 3 (AGPL v3)"
FULL_STATUS = 'full_status'
CHECK_RESULT = 'check_result'
NONE_OBJECT = object()
class Property(object):
"""Baseclass of all properties.
Same semantic for all subclasses (except UnusedProp): The property
is required if, and only if, the default value is `None`.
"""
def __init__(self, default=NONE_OBJECT, class_inherit=None,
unmanaged=False, _help='', no_slots=False,
fill_brok=None, conf_send_preparation=None,
brok_transformation=None, retention=False,
retention_preparation=None, to_send=False,
override=False, managed=True, split_on_coma=True,
keep_empty=False, merging='uniq'):
"""
`default`: default value to be used if this property is not set.
If default is None, this property is required.
`class_inherit`: List of 2-tuples, (Service, 'blabla'): must
set this property to the Service class with name
blabla. if (Service, None): must set this property
to the Service class with same name
`unmanaged`: ....
`help`: usage text
`no_slots`: do not take this property for __slots__
`fill_brok`: if set, send to broker. There are two categories:
FULL_STATUS for initial and update status,
CHECK_RESULT for check results
`retention`: if set, we will save this property in the retention files
`retention_preparation`: function, if set, will go this function before
being save to the retention data
`split_on_coma`: indicates that list property value should not be
splitted on coma delimiter (values conain comas that
we want to keep).
Only for the initial call:
conf_send_preparation: if set, will pass the property to this
function. It's used to 'flatten' some dangerous
properties like realms that are too 'linked' to
be send like that.
brok_transformation: if set, will call the function with the
value of the property when flattening
data is necessary (like realm_name instead of
the realm object).
override: for scheduler, if the property must override the
value of the configuration we send it
managed: property that is managed in Nagios but not in Alignak
merging: for merging properties, should we take only one or we can
link with ,
"""
self.default = default
self.has_default = (default is not NONE_OBJECT)
self.required = not self.has_default
self.class_inherit = class_inherit or []
self.help = _help or ''
self.unmanaged = unmanaged
self.no_slots = no_slots
self.fill_brok = fill_brok or []
self.conf_send_preparation = conf_send_preparation
self.brok_transformation = brok_transformation
self.retention = retention
self.retention_preparation = retention_preparation
self.to_send = to_send
self.override = override
self.managed = managed
self.unused = False
self.merging = merging
self.split_on_coma = split_on_coma
self.keep_empty = keep_empty
def pythonize(self, val):
"""Generic pythonize method
:param val: value to python
:type val:
:return: the value itself
:rtype:
"""
return val
[docs]class UnusedProp(Property):
"""A unused Property. These are typically used by Nagios but
no longer useful/used by Alignak.
This is just to warn the user that the option he uses is no more used
in Alignak.
"""
def __init__(self, text=None):
"""Create a new Unused property
Since this property is not used, there is no use for other
parameters than 'text'.
'text' a some usage text if present, will print it to explain
why it's no more useful
:param text:
:type text: None | str
:return: None
"""
super(UnusedProp, self).__init__(default=NONE_OBJECT,
class_inherit=[],
managed=True)
if text is None:
text = ("This parameter is no longer useful in the "
"Alignak architecture.")
self.text = text
self.unused = True
[docs]class BoolProp(Property):
"""A Boolean Property.
Boolean values are currently case insensitively defined as 0,
false, no, off for False, and 1, true, yes, on for True).
"""
@staticmethod
[docs] def pythonize(val):
"""Convert value into a boolean
:param val: value to convert
:type val:
:return: boolean corresponding to value ::
{'1': True, 'yes': True, 'true': True, 'on': True,
'0': False, 'no': False, 'false': False, 'off': False}
:rtype: bool
"""
__boolean_states__ = {'1': True, 'yes': True, 'true': True, 'on': True,
'0': False, 'no': False, 'false': False, 'off': False}
if isinstance(val, bool):
return val
val = unique_value(val).lower()
if val in __boolean_states__.keys():
return __boolean_states__[val]
else:
raise PythonizeError("Cannot convert '%s' to a boolean value" % val)
[docs]class IntegerProp(Property):
"""Integer property"""
[docs] def pythonize(self, val):
"""Convert value into an integer::
* If value is a list, try to take the last element
* Then call float(int(val))
:param val: value to convert
:type val:
:return: integer corresponding to value
:rtype: int
"""
val = unique_value(val)
return to_int(val)
[docs]class FloatProp(Property):
"""Float property"""
[docs] def pythonize(self, val):
"""Convert value into a float::
* If value is a list, try to take the last element
* Then call float(val)
:param val: value to convert
:type val:
:return: float corresponding to value
:rtype: float
"""
val = unique_value(val)
return to_float(val)
[docs]class CharProp(Property):
"""One character string property"""
[docs] def pythonize(self, val):
"""Convert value into a char ::
* If value is a list try, to take the last element
* Then take the first char of val (first elem)
:param val: value to convert
:type val:
:return: char corresponding to value
:rtype: str
"""
val = unique_value(val)
return to_char(val)
[docs]class StringProp(Property):
"""String property"""
[docs] def pythonize(self, val):
"""Convert value into a string::
* If value is a list, try to take the last element
:param val: value to convert
:type val:
:return: str corresponding to value
:rtype: str
"""
val = unique_value(val)
return val
class PathProp(StringProp):
""" A string property representing a "running" (== VAR) file path """
class ConfigPathProp(StringProp):
""" A string property representing a config file path """
[docs]class ListProp(Property):
"""List property"""
[docs] def pythonize(self, val):
"""Convert value into a list::
* split value (or each element if value is a list) on coma char
* strip split values
:param val: value to convert
:type val:
:return: list corresponding to value
:rtype: list
"""
if isinstance(val, list):
return [s.strip() for s in list_split(val, self.split_on_coma)
if s.strip() != '' or self.keep_empty]
else:
return [s.strip() for s in to_split(val, self.split_on_coma)
if s.strip() != '' or self.keep_empty]
class LogLevelProp(StringProp):
""" A string property representing a logging level """
def pythonize(self, val):
"""Convert value into a log level property::
* If value is a list, try to take the last element
* get logging level base on the value
:param val: value to convert
:type val:
:return: log level corresponding to value
:rtype: str
"""
val = unique_value(val)
return logging.getLevelName(val)
class DictProp(Property):
"""Dict property
"""
def __init__(self, elts_prop=None, *args, **kwargs):
"""Dictionary of values.
If elts_prop is not None, must be a Property subclass
All dict values will be casted as elts_prop values when pythonized
elts_prop = Property of dict members
"""
super(DictProp, self).__init__(*args, **kwargs)
if elts_prop is not None and not issubclass(elts_prop, Property):
raise TypeError("DictProp constructor only accept Property"
"sub-classes as elts_prop parameter")
if elts_prop is not None:
self.elts_prop = elts_prop()
def pythonize(self, val):
"""Convert value into a dict::
* If value is a list, try to take the last element
* split "key=value" string and convert to { key:value }
:param val: value to convert
:type val:
:return: log level corresponding to value
:rtype: str
"""
val = unique_value(val)
def split(keyval):
"""Split key-value string into (key,value)
:param keyval: key value string
:return: key, value
:rtype: tuple
"""
matches = re.match(r"^\s*([^\s]+)\s*=\s*([^\s]+)\s*$", keyval)
if matches is None:
raise ValueError
return (
matches.group(1),
# >2.4 only. we keep it for later. m.group(2) if self.elts_prop is None
# else self.elts_prop.pythonize(m.group(2))
(self.elts_prop.pythonize(matches.group(2)),
matches.group(2))[self.elts_prop is None]
)
if val is None:
return dict()
if self.elts_prop is None:
return val
# val is in the form "key1=addr:[port],key2=addr:[port],..."
print ">>>", dict([split(kv) for kv in to_split(val)])
return dict([split(kv) for kv in to_split(val)])
class AddrProp(Property):
"""Address property (host + port)"""
def pythonize(self, val):
"""Convert value into a address ip format::
* If value is a list, try to take the last element
* match ip address and port (if available)
:param val: value to convert
:type val:
:return: address/port corresponding to value
:rtype: dict
"""
val = unique_value(val)
matches = re.match(r"^([^:]*)(?::(\d+))?$", val)
if matches is None:
raise ValueError
addr = {'address': matches.group(1)}
if matches.group(2) is not None:
addr['port'] = int(matches.group(2))
return addr
class ToGuessProp(Property):
"""Unknown property encountered while parsing"""
@staticmethod
def pythonize(val):
"""If value is a single list element just return the element
does nothing otherwise
:param val: value to convert
:type val:
:return: converted value
:rtype:
"""
if isinstance(val, list) and len(set(val)) == 1:
# If we have a list with a unique value just use it
return val[0]
else:
# Well, can't choose to remove somthing.
return val
class IntListProp(ListProp):
"""Integer List property"""
def pythonize(self, val):
"""Convert value into a integer list::
* Try to convert into a list
* Convert each element into a int
:param val: value to convert
:type val:
:return: integer list corresponding to value
:rtype: list[int]
"""
val = super(IntListProp, self).pythonize(val)
try:
return [int(e) for e in val]
except ValueError as value_except:
raise PythonizeError(str(value_except))
class PythonizeError(Exception):
"""Simple Exception raise during pythonize call
"""
pass