]>
git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown/networkinterfaces.py
3 # Copyright 2014 Cumulus Networks, Inc. All rights reserved.
4 # Author: Roopa Prabhu, roopa@cumulusnetworks.com
7 # ifupdown network interfaces file parser
16 from utils
import utils
18 from template
import templateEngine
20 whitespaces
= '\n\t\r '
22 class networkInterfaces():
23 """ debian ifupdown /etc/network/interfaces file parser """
30 _addrfams
= {'inet' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6', 'tunnel'],
31 'inet6' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6', 'tunnel']}
33 def __init__(self
, interfacesfile
='/etc/network/interfaces',
34 interfacesfileiobuf
=None, interfacesfileformat
='native',
35 template_enable
='0', template_engine
=None,
36 template_lookuppath
=None):
37 """This member function initializes the networkinterfaces parser object.
40 **interfacesfile** (str): path to the interfaces file (default is /etc/network/interfaces)
42 **interfacesfileiobuf** (object): interfaces file io stream
44 **interfacesfileformat** (str): format of interfaces file (choices are 'native' and 'json'. 'native' being the default)
46 **template_engine** (str): template engine name
48 **template_lookuppath** (str): template lookup path
51 AttributeError, KeyError """
53 self
.logger
= logging
.getLogger('ifupdown.' +
54 self
.__class
__.__name
__)
55 self
.callbacks
= {'iface_found' : None,
56 'validateifaceattr' : None,
57 'validateifaceobj' : None}
58 self
.allow_classes
= {}
59 self
.interfacesfile
= interfacesfile
60 self
.interfacesfileiobuf
= interfacesfileiobuf
61 self
.interfacesfileformat
= interfacesfileformat
62 self
._filestack
= [self
.interfacesfile
]
64 self
._template
_engine
= None
65 self
._template
_enable
= template_enable
66 self
._template
_engine
_name
= template_engine
67 self
._template
_engine
_path
= template_lookuppath
69 self
._currentfile
_has
_template
= False
70 self
._ws
_split
_regex
= re
.compile(r
'[\s\t]\s*')
76 def _currentfile(self
):
78 return self
._filestack
[-1]
80 return self
.interfacesfile
82 def _parse_error(self
, filename
, lineno
, msg
):
83 if lineno
== -1 or self
._currentfile
_has
_template
:
84 self
.logger
.error('%s: %s' %(filename
, msg
))
86 self
.logger
.error('%s: line%d: %s' %(filename
, lineno
, msg
))
89 def _parse_warn(self
, filename
, lineno
, msg
):
90 if lineno
== -1 or self
._currentfile
_has
_template
:
91 self
.logger
.warn('%s: %s' %(filename
, msg
))
93 self
.logger
.warn('%s: line%d: %s' %(filename
, lineno
, msg
))
96 def _validate_addr_family(self
, ifaceobj
, lineno
=-1):
97 for family
in ifaceobj
.addr_family
:
98 if not self
._addrfams
.get(family
):
99 self
._parse
_error
(self
._currentfile
, lineno
,
100 'iface %s: unsupported address family \'%s\''
101 % (ifaceobj
.name
, family
))
102 ifaceobj
.addr_family
= []
103 ifaceobj
.addr_method
= None
105 if ifaceobj
.addr_method
:
106 if ifaceobj
.addr_method
not in self
._addrfams
.get(family
):
107 self
._parse
_error
(self
._currentfile
, lineno
,
108 'iface %s: unsupported '
109 'address method \'%s\''
110 % (ifaceobj
.name
, ifaceobj
.addr_method
))
112 ifaceobj
.addr_method
= 'static'
114 def subscribe(self
, callback_name
, callback_func
):
115 """This member function registers callback functions.
118 **callback_name** (str): callback function name (supported names: 'iface_found', 'validateifaceattr', 'validateifaceobj')
120 **callback_func** (function pointer): callback function pointer
125 if callback_name
not in self
.callbacks
.keys():
126 print 'warning: invalid callback ' + callback_name
129 self
.callbacks
[callback_name
] = callback_func
131 def ignore_line(self
, line
):
132 l
= line
.strip(whitespaces
)
133 if not l
or l
[0] == '#':
137 def process_allow(self
, lines
, cur_idx
, lineno
):
138 allow_line
= lines
[cur_idx
]
140 words
= re
.split(self
._ws
_split
_regex
, allow_line
)
142 raise Exception('invalid allow line \'%s\' at line %d'
143 %(allow_line
, lineno
))
145 allow_class
= words
[0].split('-')[1]
146 ifacenames
= words
[1:]
148 if self
.allow_classes
.get(allow_class
):
150 self
.allow_classes
[allow_class
].append(i
)
152 self
.allow_classes
[allow_class
] = ifacenames
155 def process_source(self
, lines
, cur_idx
, lineno
):
157 self
.logger
.debug('processing sourced line ..\'%s\'' %lines
[cur_idx
])
158 sourced_file
= re
.split(self
._ws
_split
_regex
, lines
[cur_idx
], 2)[1]
160 filenames
= glob
.glob(sourced_file
)
162 if '*' not in sourced_file
:
163 self
._parse
_warn
(self
._currentfile
, lineno
,
164 'cannot find source file %s' %sourced_file
)
169 self
._parse
_error
(self
._currentfile
, lineno
,
170 'unable to read source line')
173 def process_auto(self
, lines
, cur_idx
, lineno
):
174 auto_ifaces
= re
.split(self
._ws
_split
_regex
, lines
[cur_idx
])[1:]
176 self
._parse
_error
(self
._currentfile
, lineno
,
177 'invalid auto line \'%s\''%lines
[cur_idx
])
179 for a
in auto_ifaces
:
183 r
= utils
.parse_iface_range(a
)
186 # eg swp1.[2-4], r = "swp1.", 2, 4)
187 for i
in range(r
[1], r
[2]+1):
188 self
.auto_ifaces
.append('%s%d' %(r
[0], i
))
190 for i
in range(r
[1], r
[2]+1):
191 # eg swp[2-4].100, r = ("swp", 2, 4, ".100")
192 self
.auto_ifaces
.append('%s%d%s' %(r
[0], i
, r
[3]))
193 self
.auto_ifaces
.append(a
)
196 def _add_to_iface_config(self
, ifacename
, iface_config
, attrname
,
198 newattrname
= attrname
.replace("_", "-")
200 if not self
.callbacks
.get('validateifaceattr')(newattrname
,
202 self
._parse
_error
(self
._currentfile
, lineno
,
203 'iface %s: unsupported keyword (%s)'
204 %(ifacename
, attrname
))
208 attrvallist
= iface_config
.get(newattrname
, [])
209 if newattrname
in ['scope', 'netmask', 'broadcast', 'preferred-lifetime']:
210 # For attributes that are related and that can have multiple
211 # entries, store them at the same index as their parent attribute.
212 # The example of such attributes is 'address' and its related
213 # attributes. since the related attributes can be optional,
214 # we add null string '' in places where they are optional.
215 # XXX: this introduces awareness of attribute names in
216 # this class which is a violation.
218 # get the index corresponding to the 'address'
219 addrlist
= iface_config
.get('address')
221 # find the index of last address element
222 for i
in range(0, len(addrlist
) - len(attrvallist
) -1):
223 attrvallist
.append('')
224 attrvallist
.append(attrval
)
225 iface_config
[newattrname
] = attrvallist
226 elif not attrvallist
:
227 iface_config
[newattrname
] = [attrval
]
229 iface_config
[newattrname
].append(attrval
)
231 def parse_iface(self
, lines
, cur_idx
, lineno
, ifaceobj
):
235 iface_line
= lines
[cur_idx
].strip(whitespaces
)
236 iface_attrs
= re
.split(self
._ws
_split
_regex
, iface_line
)
237 ifacename
= iface_attrs
[1]
239 if (not utils
.is_ifname_range(ifacename
) and
240 utils
.check_ifname_size_invalid(ifacename
)):
241 self
._parse
_warn
(self
._currentfile
, lineno
,
242 '%s: interface name too long' %ifacename
)
244 # in cases where mako is unable to render the template
245 # or incorrectly renders it due to user template
246 # errors, we maybe left with interface names with
247 # mako variables in them. There is no easy way to
248 # recognize and warn about these. In the below check
249 # we try to warn the user of such cases by looking for
250 # variable patterns ('$') in interface names.
252 self
._parse
_warn
(self
._currentfile
, lineno
,
253 '%s: unexpected characters in interface name' %ifacename
)
255 ifaceobj
.raw_config
.append(iface_line
)
256 iface_config
= collections
.OrderedDict()
257 for line_idx
in range(cur_idx
+ 1, len(lines
)):
258 l
= lines
[line_idx
].strip(whitespaces
)
259 if self
.ignore_line(l
) == 1:
261 attrs
= re
.split(self
._ws
_split
_regex
, l
, 1)
262 if self
._is
_keyword
(attrs
[0]):
265 # if not a keyword, every line must have at least a key and value
267 self
._parse
_error
(self
._currentfile
, line_idx
,
268 'iface %s: invalid syntax \'%s\'' %(ifacename
, l
))
270 ifaceobj
.raw_config
.append(l
)
272 # preprocess vars (XXX: only preprocesses $IFACE for now)
273 attrval
= re
.sub(r
'\$IFACE', ifacename
, attrs
[1])
274 self
._add
_to
_iface
_config
(ifacename
, iface_config
, attrname
,
276 lines_consumed
= line_idx
- cur_idx
278 # Create iface object
279 if ifacename
.find(':') != -1:
280 ifaceobj
.name
= ifacename
.split(':')[0]
282 ifaceobj
.name
= ifacename
284 ifaceobj
.config
= iface_config
285 ifaceobj
.generate_env()
289 ifaceobj
.addr_family
.append(iface_attrs
[2])
290 ifaceobj
.addr_method
= iface_attrs
[3]
294 self
._validate
_addr
_family
(ifaceobj
, lineno
)
296 if self
.auto_all
or (ifaceobj
.name
in self
.auto_ifaces
):
299 classes
= self
.get_allow_classes_for_iface(ifaceobj
.name
)
301 [ifaceobj
.set_class(c
) for c
in classes
]
303 return lines_consumed
# Return next index
305 def _create_ifaceobj_clone(self
, ifaceobj
, newifaceobjname
,
306 newifaceobjtype
, newifaceobjflags
):
307 ifaceobj_new
= copy
.deepcopy(ifaceobj
)
308 ifaceobj_new
.realname
= '%s' %ifaceobj
.name
309 ifaceobj_new
.name
= newifaceobjname
310 ifaceobj_new
.type = newifaceobjtype
311 ifaceobj_new
.flags
= newifaceobjflags
315 def process_iface(self
, lines
, cur_idx
, lineno
):
317 lines_consumed
= self
.parse_iface(lines
, cur_idx
, lineno
, ifaceobj
)
319 range_val
= utils
.parse_iface_range(ifaceobj
.name
)
321 if len(range_val
) == 3:
322 for v
in range(range_val
[1], range_val
[2]+1):
323 ifacename
= '%s%d' %(range_val
[0], v
)
324 if utils
.check_ifname_size_invalid(ifacename
):
325 self
._parse
_warn
(self
._currentfile
, lineno
,
326 '%s: interface name too long' %ifacename
)
327 flags
= iface
.IFACERANGE_ENTRY
328 if v
== range_val
[1]:
329 flags |
= iface
.IFACERANGE_START
330 ifaceobj_new
= self
._create
_ifaceobj
_clone
(ifaceobj
,
331 ifacename
, ifaceobj
.type, flags
)
332 self
.callbacks
.get('iface_found')(ifaceobj_new
)
333 elif len(range_val
) == 4:
334 for v
in range(range_val
[1], range_val
[2]+1):
335 ifacename
= '%s%d%s' %(range_val
[0], v
, range_val
[3])
336 if utils
.check_ifname_size_invalid(ifacename
):
337 self
._parse
_warn
(self
._currentfile
, lineno
,
338 '%s: interface name too long' %ifacename
)
339 flags
= iface
.IFACERANGE_ENTRY
340 if v
== range_val
[1]:
341 flags |
= iface
.IFACERANGE_START
342 ifaceobj_new
= self
._create
_ifaceobj
_clone
(ifaceobj
,
343 ifacename
, ifaceobj
.type, flags
)
344 self
.callbacks
.get('iface_found')(ifaceobj_new
)
346 self
.callbacks
.get('iface_found')(ifaceobj
)
348 return lines_consumed
# Return next index
350 def process_vlan(self
, lines
, cur_idx
, lineno
):
352 lines_consumed
= self
.parse_iface(lines
, cur_idx
, lineno
, ifaceobj
)
354 range_val
= utils
.parse_iface_range(ifaceobj
.name
)
356 if len(range_val
) == 3:
357 for v
in range(range_val
[1], range_val
[2]+1):
358 flags
= iface
.IFACERANGE_ENTRY
359 if v
== range_val
[1]:
360 flags |
= iface
.IFACERANGE_START
361 ifaceobj_new
= self
._create
_ifaceobj
_clone
(ifaceobj
,
362 '%s%d' %(range_val
[0], v
),
363 ifaceType
.BRIDGE_VLAN
, flags
)
364 self
.callbacks
.get('iface_found')(ifaceobj_new
)
365 elif len(range_val
) == 4:
366 for v
in range(range_val
[1], range_val
[2]+1):
367 flags
= iface
.IFACERANGE_ENTRY
368 if v
== range_val
[1]:
369 flags |
= iface
.IFACERANGE_START
370 ifaceobj_new
= self
._create
_ifaceobj
_clone
(ifaceobj
,
371 '%s%d%s' %(range_val
[0], v
, range_val
[3]),
372 ifaceType
.BRIDGE_VLAN
,
374 self
.callbacks
.get('iface_found')(ifaceobj_new
)
376 ifaceobj
.type = ifaceType
.BRIDGE_VLAN
377 self
.callbacks
.get('iface_found')(ifaceobj
)
379 return lines_consumed
# Return next index
381 network_elems
= { 'source' : process_source
,
382 'allow' : process_allow
,
383 'auto' : process_auto
,
384 'iface' : process_iface
,
385 'vlan' : process_vlan
}
387 def _is_keyword(self
, str):
388 # The additional split here is for allow- keyword
389 if (str in self
.network_elems
.keys() or
390 str.split('-')[0] == 'allow'):
394 def _get_keyword_func(self
, str):
395 tmp_str
= str.split('-')[0]
396 return self
.network_elems
.get(tmp_str
)
398 def get_allow_classes_for_iface(self
, ifacename
):
400 for class_name
, ifacenames
in self
.allow_classes
.items():
401 if ifacename
in ifacenames
:
402 classes
.append(class_name
)
405 def process_interfaces(self
, filedata
):
407 # process line continuations
408 filedata
= ' '.join(d
.strip() for d
in filedata
.split('\\'))
412 raw_config
= filedata
.split('\n')
413 lines
= [l
.strip(whitespaces
) for l
in raw_config
]
414 while (line_idx
< len(lines
)):
415 if self
.ignore_line(lines
[line_idx
]):
418 words
= re
.split(self
._ws
_split
_regex
, lines
[line_idx
])
422 # Check if first element is a supported keyword
423 if self
._is
_keyword
(words
[0]):
424 keyword_func
= self
._get
_keyword
_func
(words
[0])
425 lines_consumed
= keyword_func(self
, lines
, line_idx
, line_idx
+1)
426 line_idx
+= lines_consumed
428 self
._parse
_error
(self
._currentfile
, line_idx
+ 1,
429 'error processing line \'%s\'' %lines
[line_idx
])
433 def read_filedata(self
, filedata
):
434 self
._currentfile
_has
_template
= False
435 # run through template engine
436 if filedata
and '%' in filedata
:
438 if not self
._template
_engine
:
439 self
._template
_engine
= templateEngine(
440 template_engine
=self
._template
_engine
_name
,
441 template_enable
=self
._template
_enable
,
442 template_lookuppath
=self
._template
_engine
_path
)
443 rendered_filedata
= self
._template
_engine
.render(filedata
)
444 if rendered_filedata
is filedata
:
445 self
._currentfile
_has
_template
= False
447 self
._currentfile
_has
_template
= True
449 self
._parse
_error
(self
._currentfile
, -1,
450 'failed to render template (%s). Continue without template rendering ...'
452 rendered_filedata
= None
453 if rendered_filedata
:
454 self
.process_interfaces(rendered_filedata
)
456 self
.process_interfaces(filedata
)
458 def read_file(self
, filename
, fileiobuf
=None):
460 self
.read_filedata(fileiobuf
)
462 self
._filestack
.append(filename
)
463 self
.logger
.info('processing interfaces file %s' %filename
)
465 with
open(filename
) as f
:
468 self
.logger
.warn('error processing file %s (%s)',
471 self
.read_filedata(filedata
)
472 self
._filestack
.pop()
474 def read_file_json(self
, filename
, fileiobuf
=None):
476 ifacedicts
= json
.loads(fileiobuf
, encoding
="utf-8")
477 #object_hook=ifaceJsonDecoder.json_object_hook)
479 self
.logger
.info('processing interfaces file %s' %filename
)
480 with
open(filename
) as fp
:
481 ifacedicts
= json
.load(fp
)
482 #object_hook=ifaceJsonDecoder.json_object_hook)
484 # we need to handle both lists and non lists formats (e.g. {{}})
485 if not isinstance(ifacedicts
,list):
486 ifacedicts
= [ifacedicts
]
489 for ifacedict
in ifacedicts
:
490 ifaceobj
= ifaceJsonDecoder
.json_to_ifaceobj(ifacedict
)
492 self
._validate
_addr
_family
(ifaceobj
)
493 if not self
.callbacks
.get('validateifaceobj')(ifaceobj
):
495 self
.callbacks
.get('iface_found')(ifaceobj
)
496 self
.errors
+= errors
499 """ This member function loads the networkinterfaces file.
501 Assumes networkinterfaces parser object is initialized with the
504 if not self
.interfacesfile
and not self
.interfacesfileiobuf
:
505 self
.logger
.warn('no terminal line stdin used or ')
506 self
.logger
.warn('no network interfaces file defined.')
510 if self
.interfacesfileformat
== 'json':
511 return self
.read_file_json(self
.interfacesfile
,
512 self
.interfacesfileiobuf
)
513 return self
.read_file(self
.interfacesfile
,
514 self
.interfacesfileiobuf
)