]>
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'],
31 'inet6' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6']}
33 def __init__(self
, interfacesfile
='/etc/network/interfaces',
34 interfacesfileiobuf
=None, interfacesfileformat
='native',
35 template_engine
=None, template_lookuppath
=None):
36 """This member function initializes the networkinterfaces parser object.
39 **interfacesfile** (str): path to the interfaces file (default is /etc/network/interfaces)
41 **interfacesfileiobuf** (object): interfaces file io stream
43 **interfacesfileformat** (str): format of interfaces file (choices are 'native' and 'json'. 'native' being the default)
45 **template_engine** (str): template engine name
47 **template_lookuppath** (str): template lookup path
50 AttributeError, KeyError """
52 self
.logger
= logging
.getLogger('ifupdown.' +
53 self
.__class
__.__name
__)
54 self
.callbacks
= {'iface_found' : None,
55 'validateifaceattr' : None,
56 'validateifaceobj' : None}
57 self
.allow_classes
= {}
58 self
.interfacesfile
= interfacesfile
59 self
.interfacesfileiobuf
= interfacesfileiobuf
60 self
.interfacesfileformat
= interfacesfileformat
61 self
._filestack
= [self
.interfacesfile
]
62 self
._template
_engine
= templateEngine(template_engine
,
64 self
._currentfile
_has
_template
= False
65 self
._ws
_split
_regex
= re
.compile(r
'[\s\t]\s*')
68 def _currentfile(self
):
70 return self
._filestack
[-1]
72 return self
.interfacesfile
74 def _parse_error(self
, filename
, lineno
, msg
):
75 if lineno
== -1 or self
._currentfile
_has
_template
:
76 self
.logger
.error('%s: %s' %(filename
, msg
))
78 self
.logger
.error('%s: line%d: %s' %(filename
, lineno
, msg
))
80 def _parse_warn(self
, filename
, lineno
, msg
):
81 if lineno
== -1 or self
._currentfile
_has
_template
:
82 self
.logger
.warn('%s: %s' %(filename
, msg
))
84 self
.logger
.warn('%s: line%d: %s' %(filename
, lineno
, msg
))
86 def _validate_addr_family(self
, ifaceobj
, lineno
=-1):
87 if ifaceobj
.addr_family
:
88 if not self
._addrfams
.get(ifaceobj
.addr_family
):
89 self
._parse
_error
(self
._currentfile
, lineno
,
90 'iface %s: unsupported address family \'%s\''
91 %(ifaceobj
.name
, ifaceobj
.addr_family
))
92 ifaceobj
.addr_family
= None
93 ifaceobj
.addr_method
= None
95 if ifaceobj
.addr_method
:
96 if (ifaceobj
.addr_method
not in
97 self
._addrfams
.get(ifaceobj
.addr_family
)):
98 self
._parse
_error
(self
._currentfile
, lineno
,
99 'iface %s: unsupported address method \'%s\''
100 %(ifaceobj
.name
, ifaceobj
.addr_method
))
102 ifaceobj
.addr_method
= 'static'
104 def subscribe(self
, callback_name
, callback_func
):
105 """This member function registers callback functions.
108 **callback_name** (str): callback function name (supported names: 'iface_found', 'validateifaceattr', 'validateifaceobj')
110 **callback_func** (function pointer): callback function pointer
115 if callback_name
not in self
.callbacks
.keys():
116 print 'warning: invalid callback ' + callback_name
119 self
.callbacks
[callback_name
] = callback_func
121 def ignore_line(self
, line
):
122 l
= line
.strip(whitespaces
)
123 if not l
or l
[0] == '#':
127 def process_allow(self
, lines
, cur_idx
, lineno
):
128 allow_line
= lines
[cur_idx
]
130 words
= re
.split(self
._ws
_split
_regex
, allow_line
)
132 raise Exception('invalid allow line \'%s\' at line %d'
133 %(allow_line
, lineno
))
135 allow_class
= words
[0].split('-')[1]
136 ifacenames
= words
[1:]
138 if self
.allow_classes
.get(allow_class
):
140 self
.allow_classes
[allow_class
].append(i
)
142 self
.allow_classes
[allow_class
] = ifacenames
145 def process_source(self
, lines
, cur_idx
, lineno
):
147 self
.logger
.debug('processing sourced line ..\'%s\'' %lines
[cur_idx
])
148 sourced_file
= re
.split(self
._ws
_split
_regex
, lines
[cur_idx
], 2)[1]
150 filenames
= glob
.glob(sourced_file
)
152 self
._parse
_warn
(self
._currentfile
, lineno
,
153 'cannot find source file %s' %sourced_file
)
158 self
._parse
_error
(self
._currentfile
, lineno
,
159 'unable to read source line')
162 def process_auto(self
, lines
, cur_idx
, lineno
):
163 auto_ifaces
= re
.split(self
._ws
_split
_regex
, lines
[cur_idx
])[1:]
165 self
._parse
_error
(self
._currentfile
, lineno
,
166 'invalid auto line \'%s\''%lines
[cur_idx
])
168 for a
in auto_ifaces
:
172 r
= utils
.parse_iface_range(a
)
174 for i
in range(r
[1], r
[2]):
175 self
.auto_ifaces
.append('%s-%d' %(r
[0], i
))
176 self
.auto_ifaces
.append(a
)
179 def _add_to_iface_config(self
, ifacename
, iface_config
, attrname
,
181 newattrname
= attrname
.replace("_", "-")
183 if not self
.callbacks
.get('validateifaceattr')(newattrname
,
185 self
._parse
_error
(self
._currentfile
, lineno
,
186 'iface %s: unsupported keyword (%s)'
187 %(ifacename
, attrname
))
191 attrvallist
= iface_config
.get(newattrname
, [])
192 if newattrname
in ['scope', 'netmask', 'broadcast', 'preferred-lifetime']:
193 # For attributes that are related and that can have multiple
194 # entries, store them at the same index as their parent attribute.
195 # The example of such attributes is 'address' and its related
196 # attributes. since the related attributes can be optional,
197 # we add null string '' in places where they are optional.
198 # XXX: this introduces awareness of attribute names in
199 # this class which is a violation.
201 # get the index corresponding to the 'address'
202 addrlist
= iface_config
.get('address')
204 # find the index of last address element
205 for i
in range(0, len(addrlist
) - len(attrvallist
) -1):
206 attrvallist
.append('')
207 attrvallist
.append(attrval
)
208 iface_config
[newattrname
] = attrvallist
209 elif not attrvallist
:
210 iface_config
[newattrname
] = [attrval
]
212 iface_config
[newattrname
].append(attrval
)
214 def parse_iface(self
, lines
, cur_idx
, lineno
, ifaceobj
):
218 iface_line
= lines
[cur_idx
].strip(whitespaces
)
219 iface_attrs
= re
.split(self
._ws
_split
_regex
, iface_line
)
220 ifacename
= iface_attrs
[1]
222 if utils
.check_ifname_size_invalid(ifacename
):
223 self
._parse
_warn
(self
._currentfile
, lineno
,
224 '%s: interface name too long' %ifacename
)
226 # in cases where mako is unable to render the template
227 # or incorrectly renders it due to user template
228 # errors, we maybe left with interface names with
229 # mako variables in them. There is no easy way to
230 # recognize and warn about these. In the below check
231 # we try to warn the user of such cases by looking for
232 # variable patterns ('$') in interface names.
234 self
._parse
_warn
(self
._currentfile
, lineno
,
235 '%s: unexpected characters in interface name' %ifacename
)
237 ifaceobj
.raw_config
.append(iface_line
)
238 iface_config
= collections
.OrderedDict()
239 for line_idx
in range(cur_idx
+ 1, len(lines
)):
240 l
= lines
[line_idx
].strip(whitespaces
)
241 if self
.ignore_line(l
) == 1:
243 attrs
= re
.split(self
._ws
_split
_regex
, l
, 1)
244 if self
._is
_keyword
(attrs
[0]):
247 # if not a keyword, every line must have at least a key and value
249 self
._parse
_error
(self
._currentfile
, line_idx
,
250 'iface %s: invalid syntax \'%s\'' %(ifacename
, l
))
252 ifaceobj
.raw_config
.append(l
)
254 # preprocess vars (XXX: only preprocesses $IFACE for now)
255 attrval
= re
.sub(r
'\$IFACE', ifacename
, attrs
[1])
256 self
._add
_to
_iface
_config
(ifacename
, iface_config
, attrname
,
258 lines_consumed
= line_idx
- cur_idx
260 # Create iface object
261 if ifacename
.find(':') != -1:
262 ifaceobj
.name
= ifacename
.split(':')[0]
264 ifaceobj
.name
= ifacename
266 ifaceobj
.config
= iface_config
267 ifaceobj
.generate_env()
270 ifaceobj
.addr_family
= iface_attrs
[2]
271 ifaceobj
.addr_method
= iface_attrs
[3]
275 self
._validate
_addr
_family
(ifaceobj
, lineno
)
277 if self
.auto_all
or (ifaceobj
.name
in self
.auto_ifaces
):
280 classes
= self
.get_allow_classes_for_iface(ifaceobj
.name
)
282 [ifaceobj
.set_class(c
) for c
in classes
]
284 return lines_consumed
# Return next index
286 def process_iface(self
, lines
, cur_idx
, lineno
):
288 lines_consumed
= self
.parse_iface(lines
, cur_idx
, lineno
, ifaceobj
)
290 range_val
= utils
.parse_iface_range(ifaceobj
.name
)
292 for v
in range(range_val
[1], range_val
[2]):
293 ifaceobj_new
= copy
.deepcopy(ifaceobj
)
294 ifaceobj_new
.realname
= '%s' %ifaceobj
.name
295 ifaceobj_new
.name
= '%s%d' %(range_val
[0], v
)
296 ifaceobj_new
.flags
= iface
.IFACERANGE_ENTRY
297 if v
== range_val
[1]:
298 ifaceobj_new
.flags |
= iface
.IFACERANGE_START
299 self
.callbacks
.get('iface_found')(ifaceobj_new
)
301 self
.callbacks
.get('iface_found')(ifaceobj
)
303 return lines_consumed
# Return next index
305 def process_vlan(self
, lines
, cur_idx
, lineno
):
307 lines_consumed
= self
.parse_iface(lines
, cur_idx
, lineno
, ifaceobj
)
309 range_val
= utils
.parse_iface_range(ifaceobj
.name
)
311 for v
in range(range_val
[1], range_val
[2]):
312 ifaceobj_new
= copy
.deepcopy(ifaceobj
)
313 ifaceobj_new
.realname
= '%s' %ifaceobj
.name
314 ifaceobj_new
.name
= '%s%d' %(range_val
[0], v
)
315 ifaceobj_new
.type = ifaceType
.BRIDGE_VLAN
316 ifaceobj_new
.flags
= iface
.IFACERANGE_ENTRY
317 if v
== range_val
[1]:
318 ifaceobj_new
.flags |
= iface
.IFACERANGE_START
319 self
.callbacks
.get('iface_found')(ifaceobj_new
)
321 ifaceobj
.type = ifaceType
.BRIDGE_VLAN
322 self
.callbacks
.get('iface_found')(ifaceobj
)
324 return lines_consumed
# Return next index
326 network_elems
= { 'source' : process_source
,
327 'allow' : process_allow
,
328 'auto' : process_auto
,
329 'iface' : process_iface
,
330 'vlan' : process_vlan
}
332 def _is_keyword(self
, str):
333 # The additional split here is for allow- keyword
334 tmp_str
= str.split('-')[0]
335 if tmp_str
in self
.network_elems
.keys():
339 def _get_keyword_func(self
, str):
340 tmp_str
= str.split('-')[0]
341 return self
.network_elems
.get(tmp_str
)
343 def get_allow_classes_for_iface(self
, ifacename
):
345 for class_name
, ifacenames
in self
.allow_classes
.items():
346 if ifacename
in ifacenames
:
347 classes
.append(class_name
)
350 def process_interfaces(self
, filedata
):
352 # process line continuations
353 filedata
= ' '.join(d
.strip() for d
in filedata
.split('\\'))
357 raw_config
= filedata
.split('\n')
358 lines
= [l
.strip(whitespaces
) for l
in raw_config
]
359 while (line_idx
< len(lines
)):
360 if self
.ignore_line(lines
[line_idx
]):
363 words
= re
.split(self
._ws
_split
_regex
, lines
[line_idx
])
367 # Check if first element is a supported keyword
368 if self
._is
_keyword
(words
[0]):
369 keyword_func
= self
._get
_keyword
_func
(words
[0])
370 lines_consumed
= keyword_func(self
, lines
, line_idx
, line_idx
+1)
371 line_idx
+= lines_consumed
373 self
._parse
_error
(self
._currentfile
, line_idx
+ 1,
374 'error processing line \'%s\'' %lines
[line_idx
])
378 def read_filedata(self
, filedata
):
379 self
._currentfile
_has
_template
= False
380 # run through template engine
382 rendered_filedata
= self
._template
_engine
.render(filedata
)
383 if rendered_filedata
is filedata
:
384 self
._currentfile
_has
_template
= False
386 self
._currentfile
_has
_template
= True
388 self
._parse
_error
(self
._currentfile
, -1,
389 'failed to render template (%s). ' %str
(e
) +
390 'Continue without template rendering ...')
391 rendered_filedata
= None
393 if rendered_filedata
:
394 self
.process_interfaces(rendered_filedata
)
396 self
.process_interfaces(filedata
)
398 def read_file(self
, filename
, fileiobuf
=None):
400 self
.read_filedata(fileiobuf
)
402 self
._filestack
.append(filename
)
403 self
.logger
.info('processing interfaces file %s' %filename
)
407 self
.read_filedata(filedata
)
408 self
._filestack
.pop()
410 def read_file_json(self
, filename
, fileiobuf
=None):
412 ifacedicts
= json
.loads(fileiobuf
, encoding
="utf-8")
413 #object_hook=ifaceJsonDecoder.json_object_hook)
415 self
.logger
.info('processing interfaces file %s' %filename
)
417 ifacedicts
= json
.load(fp
)
418 #object_hook=ifaceJsonDecoder.json_object_hook)
420 # we need to handle both lists and non lists formats (e.g. {{}})
421 if not isinstance(ifacedicts
,list):
422 ifacedicts
= [ifacedicts
]
424 for ifacedict
in ifacedicts
:
425 ifaceobj
= ifaceJsonDecoder
.json_to_ifaceobj(ifacedict
)
427 self
._validate
_addr
_family
(ifaceobj
)
428 self
.callbacks
.get('validateifaceobj')(ifaceobj
)
429 self
.callbacks
.get('iface_found')(ifaceobj
)
432 """ This member function loads the networkinterfaces file.
434 Assumes networkinterfaces parser object is initialized with the
437 if self
.interfacesfile
== None:
438 self
.logger
.warn('no network interfaces file defined in ifupdown2.conf')
441 if self
.interfacesfileformat
== 'json':
442 return self
.read_file_json(self
.interfacesfile
,
443 self
.interfacesfileiobuf
)
444 return self
.read_file(self
.interfacesfile
,
445 self
.interfacesfileiobuf
)