2016-02-18 02:53:35 +03:00
# Copyright (C) 2015-2016 Red Hat, Inc. All rights reserved.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions
# of the GNU General Public License v.2.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import xml . etree . ElementTree as Et
import sys
import inspect
import ctypes
import os
2016-02-18 03:27:57 +03:00
import string
2016-08-12 23:20:49 +03:00
import datetime
2016-02-18 02:53:35 +03:00
import dbus
import dbus . service
import dbus . mainloop . glib
2016-08-29 22:51:26 +03:00
from lvmdbusd import cfg
2016-02-18 02:53:35 +03:00
STDOUT_TTY = os . isatty ( sys . stdout . fileno ( ) )
def rtype ( dbus_type ) :
"""
Decorator making sure that the decorated function returns a value of
specified type .
: param dbus_type : The specific dbus type to return value as
"""
def decorator ( fn ) :
def decorated ( * args , * * kwargs ) :
return dbus_type ( fn ( * args , * * kwargs ) )
return decorated
return decorator
# Field is expected to be a number, handle the corner cases when parsing
@rtype ( dbus . UInt64 )
def n ( v ) :
if not v :
return 0
return int ( float ( v ) )
@rtype ( dbus . UInt32 )
def n32 ( v ) :
if not v :
return 0
return int ( float ( v ) )
# noinspection PyProtectedMember
def init_class_from_arguments ( obj_instance ) :
for k , v in list ( sys . _getframe ( 1 ) . f_locals . items ( ) ) :
if k != ' self ' :
nt = k
# If the current attribute has a value, but the incoming does
# not, don't overwrite it. Otherwise the default values on the
# property decorator don't work as expected.
cur = getattr ( obj_instance , nt , v )
# print 'Init class %s = %s' % (nt, str(v))
if not ( cur and len ( str ( cur ) ) and ( v is None or len ( str ( v ) ) ) == 0 ) :
setattr ( obj_instance , nt , v )
def get_properties ( f ) :
"""
Walks through an object instance or it ' s parent class(es) and determines
which attributes are properties and if they were created to be used for
dbus .
: param f : Object to inspect
: return : A dictionary of tuples with each tuple being :
0 = An array of dicts with the keys being : p_t , p_name ,
p_access ( type , name , access )
1 = Hash of property names and current value
"""
interfaces = dict ( )
for c in inspect . getmro ( f . __class__ ) :
h = vars ( c )
for p , value in h . items ( ) :
if isinstance ( value , property ) :
# We found a property, see if it has a metadata type
key = attribute_type_name ( p )
if key in h :
interface = h [ key ] [ 1 ]
if interface not in interfaces :
interfaces [ interface ] = ( [ ] , { } )
access = ' '
if getattr ( f . __class__ , p ) . fget :
access + = ' read '
if getattr ( f . __class__ , p ) . fset :
access + = ' write '
interfaces [ interface ] [ 0 ] . append (
dict (
p_t = getattr ( f , key ) [ 0 ] ,
p_name = p ,
p_access = access ) )
interfaces [ interface ] [ 1 ] [ p ] = getattr ( f , p )
return interfaces
def get_object_property_diff ( o_prop , n_prop ) :
"""
Walk through each object properties and report what has changed and with
the new values
: param o_prop : Old keys / values
: param n_prop : New keys / values
: return : hash of properties that have changed and their new value
"""
rc = { }
for intf_k , intf_v in o_prop . items ( ) :
for k , v in list ( intf_v [ 1 ] . items ( ) ) :
# print('Comparing %s:%s to %s:%s' %
# (k, o_prop[intf_k][1][k], k, str(n_prop[intf_k][1][k])))
if o_prop [ intf_k ] [ 1 ] [ k ] != n_prop [ intf_k ] [ 1 ] [ k ] :
new_value = n_prop [ intf_k ] [ 1 ] [ k ]
if intf_k not in rc :
rc [ intf_k ] = dict ( )
rc [ intf_k ] [ k ] = new_value
return rc
def add_properties ( xml , interface , props ) :
"""
Given xml that describes the interface , add property values to the XML
for the specified interface .
: param xml : XML to edit
: param interface : Interface to add the properties too
: param props : Output from get_properties
: return : updated XML string
"""
root = Et . fromstring ( xml )
if props :
for c in root :
# print c.attrib['name']
if c . attrib [ ' name ' ] == interface :
for p in props :
temp = ' <property type= " %s " name= " %s " access= " %s " /> \n ' % \
( p [ ' p_t ' ] , p [ ' p_name ' ] , p [ ' p_access ' ] )
c . append ( Et . fromstring ( temp ) )
return Et . tostring ( root , encoding = ' utf8 ' )
return xml
def attribute_type_name ( name ) :
"""
Given the property name , return string of the attribute type
: param name :
: return :
"""
return " _ %s _meta " % name
_type_map = dict (
s = dbus . String ,
o = dbus . ObjectPath ,
t = dbus . UInt64 ,
x = dbus . Int64 ,
u = dbus . UInt32 ,
i = dbus . Int32 ,
n = dbus . Int16 ,
q = dbus . UInt16 ,
d = dbus . Double ,
y = dbus . Byte ,
b = dbus . Boolean )
def _pass_through ( v ) :
"""
If we have something which is not a simple type we return the original
value un - wrapped .
: param v :
: return :
"""
return v
def _dbus_type ( t , value ) :
return _type_map . get ( t , _pass_through ) ( value )
def dbus_property ( interface_name , name , dbus_type , doc = None ) :
"""
Creates the get / set properties for the given name . It assumes that the
actual attribute is ' _ ' + name and the attribute metadata is stuffed in
_name_type .
There is probably a better way todo this .
: param interface_name : Dbus interface this property is associated with
: param name : Name of property
: param dbus_type : dbus string type eg . s , t , i , x
: param doc : Python __doc__ for the property
: return :
"""
attribute_name = ' _ ' + name
def getter ( self ) :
t = getattr ( self , attribute_name + ' _meta ' ) [ 0 ]
return _dbus_type ( t , getattr ( self . state , attribute_name [ 1 : ] ) )
prop = property ( getter , None , None , doc )
def decorator ( cls ) :
setattr ( cls , attribute_name + ' _meta ' , ( dbus_type , interface_name ) )
setattr ( cls , name , prop )
return cls
return decorator
def parse_tags ( tags ) :
if len ( tags ) :
if ' , ' in tags :
return tags . split ( ' , ' )
2016-08-25 02:31:15 +03:00
return dbus . Array ( sorted ( [ tags ] ) , signature = ' s ' )
2016-02-18 02:53:35 +03:00
return dbus . Array ( [ ] , signature = ' s ' )
def _common_log ( msg , * attributes ) :
cfg . stdout_lock . acquire ( )
tid = ctypes . CDLL ( ' libc.so.6 ' ) . syscall ( 186 )
2016-08-12 23:20:49 +03:00
if STDOUT_TTY :
msg = " %s : %d : %d - %s " % \
( datetime . datetime . now ( ) . strftime ( " % b %d % H: % M: % S. %f " ) ,
os . getpid ( ) , tid , msg )
else :
msg = " %d : %d - %s " % ( os . getpid ( ) , tid , msg )
2016-02-18 02:53:35 +03:00
if STDOUT_TTY and attributes :
print ( color ( msg , * attributes ) )
else :
print ( msg )
cfg . stdout_lock . release ( )
sys . stdout . flush ( )
# Serializes access to stdout to prevent interleaved output
# @param msg Message to output to stdout
# @return None
def log_debug ( msg , * attributes ) :
2016-08-29 22:52:41 +03:00
if cfg . args and cfg . args . debug :
2016-02-18 02:53:35 +03:00
_common_log ( msg , * attributes )
def log_error ( msg , * attributes ) :
_common_log ( msg , * attributes )
# noinspection PyUnusedLocal
def handler ( signum , frame ) :
cfg . run . value = 0
log_debug ( ' Signal handler called with signal %d ' % signum )
if cfg . loop is not None :
cfg . loop . quit ( )
def pv_obj_path_generate ( ) :
return cfg . PV_OBJ_PATH + " / %d " % next ( cfg . pv_id )
def vg_obj_path_generate ( ) :
return cfg . VG_OBJ_PATH + " / %d " % next ( cfg . vg_id )
def lv_object_path_method ( name , meta ) :
if name [ 0 ] == ' [ ' :
return _hidden_lv_obj_path_generate
elif meta [ 0 ] [ 0 ] == ' t ' :
return _thin_pool_obj_path_generate
elif meta [ 0 ] [ 0 ] == ' C ' and ' pool ' in meta [ 1 ] :
return _cache_pool_obj_path_generate
return _lv_obj_path_generate
# Note: None of the individual LV path generate functions should be called
# directly, they should only be dispatched through lv_object_path_method
def _lv_obj_path_generate ( ) :
return cfg . LV_OBJ_PATH + " / %d " % next ( cfg . lv_id )
def _thin_pool_obj_path_generate ( ) :
return cfg . THIN_POOL_PATH + " / %d " % next ( cfg . thin_id )
def _cache_pool_obj_path_generate ( ) :
return cfg . CACHE_POOL_PATH + " / %d " % next ( cfg . cache_pool_id )
def _hidden_lv_obj_path_generate ( ) :
return cfg . HIDDEN_LV_PATH + " / %d " % next ( cfg . hidden_lv )
def job_obj_path_generate ( ) :
return cfg . JOB_OBJ_PATH + " / %d " % next ( cfg . job_id )
def color ( text , * user_styles ) :
styles = {
# styles
' reset ' : ' \033 [0m ' ,
' bold ' : ' \033 [01m ' ,
' disabled ' : ' \033 [02m ' ,
' underline ' : ' \033 [04m ' ,
' reverse ' : ' \033 [07m ' ,
' strike_through ' : ' \033 [09m ' ,
' invisible ' : ' \033 [08m ' ,
# text colors
' fg_black ' : ' \033 [30m ' ,
' fg_red ' : ' \033 [31m ' ,
' fg_green ' : ' \033 [32m ' ,
' fg_orange ' : ' \033 [33m ' ,
' fg_blue ' : ' \033 [34m ' ,
' fg_purple ' : ' \033 [35m ' ,
' fg_cyan ' : ' \033 [36m ' ,
' fg_light_grey ' : ' \033 [37m ' ,
' fg_dark_grey ' : ' \033 [90m ' ,
' fg_light_red ' : ' \033 [91m ' ,
' fg_light_green ' : ' \033 [92m ' ,
' fg_yellow ' : ' \033 [93m ' ,
' fg_light_blue ' : ' \033 [94m ' ,
' fg_pink ' : ' \033 [95m ' ,
' fg_light_cyan ' : ' \033 [96m ' ,
# background colors
' bg_black ' : ' \033 [40m ' ,
' bg_red ' : ' \033 [41m ' ,
' bg_green ' : ' \033 [42m ' ,
' bg_orange ' : ' \033 [43m ' ,
' bg_blue ' : ' \033 [44m ' ,
' bg_purple ' : ' \033 [45m ' ,
' bg_cyan ' : ' \033 [46m ' ,
' bg_light_grey ' : ' \033 [47m '
}
color_text = ' '
for style in user_styles :
try :
color_text + = styles [ style ]
except KeyError :
return ' def color: parameter {} does not exist ' . format ( style )
color_text + = text
return ' \033 [0m {0} \033 [0m ' . format ( color_text )
def pv_range_append ( cmd , device , start , end ) :
if ( start , end ) == ( 0 , 0 ) :
cmd . append ( device )
else :
if start != 0 and end == 0 :
cmd . append ( " %s : %d - " % ( device , start ) )
else :
cmd . append (
" %s : %d - %d " %
( device , start , end ) )
def pv_dest_ranges ( cmd , pv_dest_range_list ) :
if len ( pv_dest_range_list ) :
for i in pv_dest_range_list :
pv_range_append ( cmd , * i )
def round_size ( size_bytes ) :
bs = 512
remainder = size_bytes % bs
if not remainder :
return size_bytes
return size_bytes + bs - remainder
2016-02-18 03:27:57 +03:00
2016-03-24 20:26:28 +03:00
_ALLOWABLE_CH = string . ascii_letters + string . digits + ' #+-.:=@_ \ / % '
2016-02-18 03:27:57 +03:00
_ALLOWABLE_CH_SET = set ( _ALLOWABLE_CH )
_ALLOWABLE_VG_LV_CH = string . ascii_letters + string . digits + ' .-_+ '
_ALLOWABLE_VG_LV_CH_SET = set ( _ALLOWABLE_VG_LV_CH )
_LV_NAME_RESERVED = ( " _cdata " , " _cmeta " , " _corig " , " _mimage " , " _mlog " ,
" _pmspare " , " _rimage " , " _rmeta " , " _tdata " , " _tmeta " , " _vorigin " )
# Tags can have the characters, based on the code
# a-zA-Z0-9._-+/=!:&#
_ALLOWABLE_TAG_CH = string . ascii_letters + string . digits + " ._-+/=!:&# "
_ALLOWABLE_TAG_CH_SET = set ( _ALLOWABLE_TAG_CH )
def _allowable_tag ( tag_name ) :
# LVM should impose a length restriction
return set ( tag_name ) < = _ALLOWABLE_TAG_CH_SET
def _allowable_vg_name ( vg_name ) :
if vg_name is None :
raise ValueError ( " VG name is None or empty " )
vg_len = len ( vg_name )
if vg_len == 0 or vg_len > 127 :
raise ValueError ( " VG name ( %s ) length ( %d ) not in the domain 1..127 " %
( vg_name , vg_len ) )
if not set ( vg_name ) < = _ALLOWABLE_VG_LV_CH_SET :
raise ValueError ( " VG name ( %s ) contains invalid character, "
" allowable set( %s ) " % ( vg_name , _ALLOWABLE_VG_LV_CH ) )
if vg_name == " . " or vg_name == " .. " :
raise ValueError ( ' VG name ( %s ) cannot be " . " or " .. " ' % ( vg_name ) )
def _allowable_lv_name ( vg_name , lv_name ) :
if lv_name is None :
raise ValueError ( " LV name is None or empty " )
lv_len = len ( lv_name )
# This length is derived from empirical testing
if lv_len == 0 or ( len ( vg_name ) + lv_len ) > 125 :
raise ValueError ( " LV name ( %s ) length ( %d ) + VG name length "
" not in the domain 1..125 " % ( lv_name , lv_len ) )
if not set ( lv_name ) < = _ALLOWABLE_VG_LV_CH_SET :
raise ValueError ( " LV name ( %s ) contains invalid character, "
" allowable ( %s ) " % ( lv_name , _ALLOWABLE_VG_LV_CH ) )
if any ( x in lv_name for x in _LV_NAME_RESERVED ) :
raise ValueError ( " LV name ( %s ) contains a reserved word, "
" reserved set( %s ) " % ( lv_name , str ( _LV_NAME_RESERVED ) ) )
if lv_name . startswith ( " snapshot " ) or lv_name . startswith ( " pvmove " ) :
raise ValueError ( " LV name ( %s ) starts with a reserved word, "
" reserved set( %s ) " % ( lv_name , str ( [ " snapshot " , " pvmove " ] ) ) )
if lv_name [ 0 ] == ' - ' :
raise ValueError ( " LV name ( %s ) cannot start with a ' - ' "
" character " % lv_name )
def validate_device_path ( interface , device ) :
if not set ( device ) < = _ALLOWABLE_CH_SET :
raise dbus . exceptions . DBusException (
interface , ' Device path ( %s ) has invalid characters, '
' allowable ( %s ) ' % ( device , _ALLOWABLE_CH ) )
def validate_vg_name ( interface , vg_name ) :
try :
_allowable_vg_name ( vg_name )
except ValueError as ve :
raise dbus . exceptions . DBusException (
interface , str ( ve ) )
def validate_lv_name ( interface , vg_name , lv_name ) :
try :
_allowable_lv_name ( vg_name , lv_name )
except ValueError as ve :
raise dbus . exceptions . DBusException (
interface , str ( ve ) )
def validate_tag ( interface , tag ) :
if not _allowable_tag ( tag ) :
raise dbus . exceptions . DBusException (
interface , ' tag ( %s ) contains invalid character, allowable set( %s ) '
% ( tag , _ALLOWABLE_TAG_CH ) )