]>
git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/ifupdown/networkinterfaces.py
3 # Copyright 2014-2017 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
7 # ifupdown network interfaces file parser
17 from ifupdown2
.ifupdown
.iface
import *
18 from ifupdown2
.ifupdown
.utils
import utils
19 from ifupdown2
.ifupdown
.template
import templateEngine
21 from ifupdown
.iface
import *
22 from ifupdown
.utils
import utils
23 from ifupdown
.template
import templateEngine
26 whitespaces
= '\n\t\r '
28 class networkInterfaces():
29 """ debian ifupdown /etc/network/interfaces file parser """
31 _addrfams
= {'inet' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6', 'ppp', 'tunnel'],
32 'inet6' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6', 'ppp', 'tunnel']}
34 def __init__(self
, interfacesfile
='/etc/network/interfaces',
35 interfacesfileiobuf
=None, interfacesfileformat
='native',
36 template_enable
='0', template_engine
=None,
37 template_lookuppath
=None):
38 """This member function initializes the networkinterfaces parser object.
41 **interfacesfile** (str): path to the interfaces file (default is /etc/network/interfaces)
43 **interfacesfileiobuf** (object): interfaces file io stream
45 **interfacesfileformat** (str): format of interfaces file (choices are 'native' and 'json'. 'native' being the default)
47 **template_engine** (str): template engine name
49 **template_lookuppath** (str): template lookup path
52 AttributeError, KeyError """
58 self
.logger
= logging
.getLogger('ifupdown.' +
59 self
.__class
__.__name
__)
60 self
.callbacks
= {'iface_found' : None,
61 'validateifaceattr' : None,
62 'validateifaceobj' : None}
63 self
.allow_classes
= {}
64 self
.interfacesfile
= interfacesfile
65 self
.interfacesfileiobuf
= interfacesfileiobuf
66 self
.interfacesfileformat
= interfacesfileformat
67 self
._filestack
= [self
.interfacesfile
]
69 self
._template
_engine
= None
70 self
._template
_enable
= template_enable
71 self
._template
_engine
_name
= template_engine
72 self
._template
_engine
_path
= template_lookuppath
74 self
._currentfile
_has
_template
= False
75 self
._ws
_split
_regex
= re
.compile(r
'[\s\t]\s*')
81 def _currentfile(self
):
83 return self
._filestack
[-1]
85 return self
.interfacesfile
87 def _parse_error(self
, filename
, lineno
, msg
):
88 if lineno
== -1 or self
._currentfile
_has
_template
:
89 self
.logger
.error('%s: %s' %(filename
, msg
))
91 self
.logger
.error('%s: line%d: %s' %(filename
, lineno
, msg
))
94 def _parse_warn(self
, filename
, lineno
, msg
):
95 if lineno
== -1 or self
._currentfile
_has
_template
:
96 self
.logger
.warn('%s: %s' %(filename
, msg
))
98 self
.logger
.warn('%s: line%d: %s' %(filename
, lineno
, msg
))
101 def _validate_addr_family(self
, ifaceobj
, lineno
=-1):
102 for family
in ifaceobj
.addr_family
:
103 if not self
._addrfams
.get(family
):
104 self
._parse
_error
(self
._currentfile
, lineno
,
105 'iface %s: unsupported address family \'%s\''
106 % (ifaceobj
.name
, family
))
107 ifaceobj
.addr_family
= []
108 ifaceobj
.addr_method
= None
110 if ifaceobj
.addr_method
:
111 if ifaceobj
.addr_method
not in self
._addrfams
.get(family
):
112 self
._parse
_error
(self
._currentfile
, lineno
,
113 'iface %s: unsupported '
114 'address method \'%s\''
115 % (ifaceobj
.name
, ifaceobj
.addr_method
))
117 ifaceobj
.addr_method
= 'static'
119 def subscribe(self
, callback_name
, callback_func
):
120 """This member function registers callback functions.
123 **callback_name** (str): callback function name (supported names: 'iface_found', 'validateifaceattr', 'validateifaceobj')
125 **callback_func** (function pointer): callback function pointer
130 if callback_name
not in self
.callbacks
.keys():
131 print 'warning: invalid callback ' + callback_name
134 self
.callbacks
[callback_name
] = callback_func
136 def ignore_line(self
, line
):
137 l
= line
.strip(whitespaces
)
138 if not l
or l
[0] == '#':
142 def process_allow(self
, lines
, cur_idx
, lineno
):
143 allow_line
= lines
[cur_idx
]
145 words
= re
.split(self
._ws
_split
_regex
, allow_line
)
147 raise Exception('invalid allow line \'%s\' at line %d'
148 %(allow_line
, lineno
))
150 allow_class
= words
[0].split('-')[1]
151 ifacenames
= words
[1:]
153 if self
.allow_classes
.get(allow_class
):
155 self
.allow_classes
[allow_class
].append(i
)
157 self
.allow_classes
[allow_class
] = ifacenames
160 def process_source(self
, lines
, cur_idx
, lineno
):
162 self
.logger
.debug('processing sourced line ..\'%s\'' %lines
[cur_idx
])
163 sourced_file
= re
.split(self
._ws
_split
_regex
, lines
[cur_idx
], 2)[1]
165 filenames
= glob
.glob(sourced_file
)
167 if '*' not in sourced_file
:
168 self
._parse
_warn
(self
._currentfile
, lineno
,
169 'cannot find source file %s' %sourced_file
)
174 self
._parse
_error
(self
._currentfile
, lineno
,
175 'unable to read source line')
178 def process_auto(self
, lines
, cur_idx
, lineno
):
179 auto_ifaces
= re
.split(self
._ws
_split
_regex
, lines
[cur_idx
])[1:]
181 self
._parse
_error
(self
._currentfile
, lineno
,
182 'invalid auto line \'%s\''%lines
[cur_idx
])
184 for a
in auto_ifaces
:
188 r
= utils
.parse_iface_range(a
)
191 # eg swp1.[2-4], r = "swp1.", 2, 4)
192 for i
in range(r
[1], r
[2]+1):
193 self
.auto_ifaces
.append('%s%d' %(r
[0], i
))
195 for i
in range(r
[1], r
[2]+1):
196 # eg swp[2-4].100, r = ("swp", 2, 4, ".100")
197 self
.auto_ifaces
.append('%s%d%s' %(r
[0], i
, r
[3]))
198 self
.auto_ifaces
.append(a
)
201 def _add_to_iface_config(self
, ifacename
, iface_config
, attrname
,
203 newattrname
= attrname
.replace("_", "-")
205 if not self
.callbacks
.get('validateifaceattr')(newattrname
,
207 self
._parse
_error
(self
._currentfile
, lineno
,
208 'iface %s: unsupported keyword (%s)'
209 %(ifacename
, attrname
))
213 attrvallist
= iface_config
.get(newattrname
, [])
214 if newattrname
in ['scope', 'netmask', 'broadcast', 'preferred-lifetime']:
215 # For attributes that are related and that can have multiple
216 # entries, store them at the same index as their parent attribute.
217 # The example of such attributes is 'address' and its related
218 # attributes. since the related attributes can be optional,
219 # we add null string '' in places where they are optional.
220 # XXX: this introduces awareness of attribute names in
221 # this class which is a violation.
223 # get the index corresponding to the 'address'
224 addrlist
= iface_config
.get('address')
226 # find the index of last address element
227 for i
in range(0, len(addrlist
) - len(attrvallist
) -1):
228 attrvallist
.append('')
229 attrvallist
.append(attrval
)
230 iface_config
[newattrname
] = attrvallist
231 elif not attrvallist
:
232 iface_config
[newattrname
] = [attrval
]
234 iface_config
[newattrname
].append(attrval
)
236 def parse_iface(self
, lines
, cur_idx
, lineno
, ifaceobj
):
240 iface_line
= lines
[cur_idx
].strip(whitespaces
)
241 iface_attrs
= re
.split(self
._ws
_split
_regex
, iface_line
)
242 ifacename
= iface_attrs
[1]
244 if (not utils
.is_ifname_range(ifacename
) and
245 utils
.check_ifname_size_invalid(ifacename
)):
246 self
._parse
_warn
(self
._currentfile
, lineno
,
247 '%s: interface name too long' %ifacename
)
249 # in cases where mako is unable to render the template
250 # or incorrectly renders it due to user template
251 # errors, we maybe left with interface names with
252 # mako variables in them. There is no easy way to
253 # recognize and warn about these. In the below check
254 # we try to warn the user of such cases by looking for
255 # variable patterns ('$') in interface names.
257 self
._parse
_warn
(self
._currentfile
, lineno
,
258 '%s: unexpected characters in interface name' %ifacename
)
260 ifaceobj
.raw_config
.append(iface_line
)
261 iface_config
= collections
.OrderedDict()
262 for line_idx
in range(cur_idx
+ 1, len(lines
)):
263 l
= lines
[line_idx
].strip(whitespaces
)
264 if self
.ignore_line(l
) == 1:
265 ifaceobj
.raw_config
.append(l
)
267 attrs
= re
.split(self
._ws
_split
_regex
, l
, 1)
268 if self
._is
_keyword
(attrs
[0]):
271 # if not a keyword, every line must have at least a key and value
273 self
._parse
_error
(self
._currentfile
, line_idx
,
274 'iface %s: invalid syntax \'%s\'' %(ifacename
, l
))
276 ifaceobj
.raw_config
.append(l
)
278 # preprocess vars (XXX: only preprocesses $IFACE for now)
279 attrval
= re
.sub(r
'\$IFACE', ifacename
, attrs
[1])
280 self
._add
_to
_iface
_config
(ifacename
, iface_config
, attrname
,
282 lines_consumed
= line_idx
- cur_idx
284 # Create iface object
285 if ifacename
.find(':') != -1:
286 ifaceobj
.name
= ifacename
.split(':')[0]
288 ifaceobj
.name
= ifacename
290 ifaceobj
.config
= iface_config
291 ifaceobj
.generate_env()
295 ifaceobj
.addr_family
.append(iface_attrs
[2])
296 ifaceobj
.addr_method
= iface_attrs
[3]
300 self
._validate
_addr
_family
(ifaceobj
, lineno
)
302 if self
.auto_all
or (ifaceobj
.name
in self
.auto_ifaces
):
305 classes
= self
.get_allow_classes_for_iface(ifaceobj
.name
)
307 [ifaceobj
.set_class(c
) for c
in classes
]
309 return lines_consumed
# Return next index
311 def _create_ifaceobj_clone(self
, ifaceobj
, newifaceobjname
,
312 newifaceobjtype
, newifaceobjflags
):
313 ifaceobj_new
= copy
.deepcopy(ifaceobj
)
314 ifaceobj_new
.realname
= '%s' %ifaceobj
.name
315 ifaceobj_new
.name
= newifaceobjname
316 ifaceobj_new
.type = newifaceobjtype
317 ifaceobj_new
.flags
= newifaceobjflags
321 def process_iface(self
, lines
, cur_idx
, lineno
):
323 lines_consumed
= self
.parse_iface(lines
, cur_idx
, lineno
, ifaceobj
)
325 range_val
= utils
.parse_iface_range(ifaceobj
.name
)
327 if len(range_val
) == 3:
328 for v
in range(range_val
[1], range_val
[2]+1):
329 ifacename
= '%s%d' %(range_val
[0], v
)
330 if utils
.check_ifname_size_invalid(ifacename
):
331 self
._parse
_warn
(self
._currentfile
, lineno
,
332 '%s: interface name too long' %ifacename
)
333 flags
= iface
.IFACERANGE_ENTRY
334 if v
== range_val
[1]:
335 flags |
= iface
.IFACERANGE_START
336 ifaceobj_new
= self
._create
_ifaceobj
_clone
(ifaceobj
,
337 ifacename
, ifaceobj
.type, flags
)
338 self
.callbacks
.get('iface_found')(ifaceobj_new
)
339 elif len(range_val
) == 4:
340 for v
in range(range_val
[1], range_val
[2]+1):
341 ifacename
= '%s%d%s' %(range_val
[0], v
, range_val
[3])
342 if utils
.check_ifname_size_invalid(ifacename
):
343 self
._parse
_warn
(self
._currentfile
, lineno
,
344 '%s: interface name too long' %ifacename
)
345 flags
= iface
.IFACERANGE_ENTRY
346 if v
== range_val
[1]:
347 flags |
= iface
.IFACERANGE_START
348 ifaceobj_new
= self
._create
_ifaceobj
_clone
(ifaceobj
,
349 ifacename
, ifaceobj
.type, flags
)
350 self
.callbacks
.get('iface_found')(ifaceobj_new
)
352 self
.callbacks
.get('iface_found')(ifaceobj
)
354 return lines_consumed
# Return next index
356 def process_vlan(self
, lines
, cur_idx
, lineno
):
358 lines_consumed
= self
.parse_iface(lines
, cur_idx
, lineno
, ifaceobj
)
360 range_val
= utils
.parse_iface_range(ifaceobj
.name
)
362 if len(range_val
) == 3:
363 for v
in range(range_val
[1], range_val
[2]+1):
364 flags
= iface
.IFACERANGE_ENTRY
365 if v
== range_val
[1]:
366 flags |
= iface
.IFACERANGE_START
367 ifaceobj_new
= self
._create
_ifaceobj
_clone
(ifaceobj
,
368 '%s%d' %(range_val
[0], v
),
369 ifaceType
.BRIDGE_VLAN
, flags
)
370 self
.callbacks
.get('iface_found')(ifaceobj_new
)
371 elif len(range_val
) == 4:
372 for v
in range(range_val
[1], range_val
[2]+1):
373 flags
= iface
.IFACERANGE_ENTRY
374 if v
== range_val
[1]:
375 flags |
= iface
.IFACERANGE_START
376 ifaceobj_new
= self
._create
_ifaceobj
_clone
(ifaceobj
,
377 '%s%d%s' %(range_val
[0], v
, range_val
[3]),
378 ifaceType
.BRIDGE_VLAN
,
380 self
.callbacks
.get('iface_found')(ifaceobj_new
)
382 ifaceobj
.type = ifaceType
.BRIDGE_VLAN
383 self
.callbacks
.get('iface_found')(ifaceobj
)
385 return lines_consumed
# Return next index
387 network_elems
= { 'source' : process_source
,
388 'allow' : process_allow
,
389 'auto' : process_auto
,
390 'iface' : process_iface
,
391 'vlan' : process_vlan
}
393 def _is_keyword(self
, str):
394 # The additional split here is for allow- keyword
395 if (str in self
.network_elems
.keys() or
396 str.split('-')[0] == 'allow'):
400 def _get_keyword_func(self
, str):
401 tmp_str
= str.split('-')[0]
402 return self
.network_elems
.get(tmp_str
)
404 def get_allow_classes_for_iface(self
, ifacename
):
406 for class_name
, ifacenames
in self
.allow_classes
.items():
407 if ifacename
in ifacenames
:
408 classes
.append(class_name
)
411 def process_interfaces(self
, filedata
):
413 # process line continuations
414 filedata
= ' '.join(d
.strip() for d
in filedata
.split('\\'))
418 raw_config
= filedata
.split('\n')
419 lines
= [l
.strip(whitespaces
) for l
in raw_config
]
420 while (line_idx
< len(lines
)):
421 if self
.ignore_line(lines
[line_idx
]):
424 words
= re
.split(self
._ws
_split
_regex
, lines
[line_idx
])
428 # Check if first element is a supported keyword
429 if self
._is
_keyword
(words
[0]):
430 keyword_func
= self
._get
_keyword
_func
(words
[0])
431 lines_consumed
= keyword_func(self
, lines
, line_idx
, line_idx
+1)
432 line_idx
+= lines_consumed
434 self
._parse
_error
(self
._currentfile
, line_idx
+ 1,
435 'error processing line \'%s\'' %lines
[line_idx
])
439 def read_filedata(self
, filedata
):
440 self
._currentfile
_has
_template
= False
441 # run through template engine
442 if filedata
and '%' in filedata
:
444 if not self
._template
_engine
:
445 self
._template
_engine
= templateEngine(
446 template_engine
=self
._template
_engine
_name
,
447 template_enable
=self
._template
_enable
,
448 template_lookuppath
=self
._template
_engine
_path
)
449 rendered_filedata
= self
._template
_engine
.render(filedata
)
450 if rendered_filedata
is filedata
:
451 self
._currentfile
_has
_template
= False
453 self
._currentfile
_has
_template
= True
455 self
._parse
_error
(self
._currentfile
, -1,
456 'failed to render template (%s). Continue without template rendering ...'
458 rendered_filedata
= None
459 if rendered_filedata
:
460 self
.process_interfaces(rendered_filedata
)
462 self
.process_interfaces(filedata
)
464 def read_file(self
, filename
, fileiobuf
=None):
466 self
.read_filedata(fileiobuf
)
468 self
._filestack
.append(filename
)
469 self
.logger
.info('processing interfaces file %s' %filename
)
471 with
open(filename
) as f
:
474 self
.logger
.warn('error processing file %s (%s)',
477 self
.read_filedata(filedata
)
478 self
._filestack
.pop()
480 def read_file_json(self
, filename
, fileiobuf
=None):
482 ifacedicts
= json
.loads(fileiobuf
, encoding
="utf-8")
483 #object_hook=ifaceJsonDecoder.json_object_hook)
485 self
.logger
.info('processing interfaces file %s' %filename
)
486 with
open(filename
) as fp
:
487 ifacedicts
= json
.load(fp
)
488 #object_hook=ifaceJsonDecoder.json_object_hook)
490 # we need to handle both lists and non lists formats (e.g. {{}})
491 if not isinstance(ifacedicts
,list):
492 ifacedicts
= [ifacedicts
]
495 for ifacedict
in ifacedicts
:
496 ifaceobj
= ifaceJsonDecoder
.json_to_ifaceobj(ifacedict
)
498 self
._validate
_addr
_family
(ifaceobj
)
499 if not self
.callbacks
.get('validateifaceobj')(ifaceobj
):
501 self
.callbacks
.get('iface_found')(ifaceobj
)
502 self
.errors
+= errors
505 """ This member function loads the networkinterfaces file.
507 Assumes networkinterfaces parser object is initialized with the
510 if not self
.interfacesfile
and not self
.interfacesfileiobuf
:
511 self
.logger
.warn('no terminal line stdin used or ')
512 self
.logger
.warn('no network interfaces file defined.')
516 if self
.interfacesfileformat
== 'json':
517 return self
.read_file_json(self
.interfacesfile
,
518 self
.interfacesfileiobuf
)
519 return self
.read_file(self
.interfacesfile
,
520 self
.interfacesfileiobuf
)