]>
git.proxmox.com Git - mirror_ifupdown2.git/blob - ifupdown2/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 # in cases where mako is unable to render the template
223 # or incorrectly renders it due to user template
224 # errors, we maybe left with interface names with
225 # mako variables in them. There is no easy way to
226 # recognize and warn about these. In the below check
227 # we try to warn the user of such cases by looking for
228 # variable patterns ('$') in interface names.
230 self
._parse
_warn
(self
._currentfile
, lineno
,
231 '%s: unexpected characters in interface name' %ifacename
)
233 ifaceobj
.raw_config
.append(iface_line
)
234 iface_config
= collections
.OrderedDict()
235 for line_idx
in range(cur_idx
+ 1, len(lines
)):
236 l
= lines
[line_idx
].strip(whitespaces
)
237 if self
.ignore_line(l
) == 1:
239 attrs
= re
.split(self
._ws
_split
_regex
, l
, 1)
240 if self
._is
_keyword
(attrs
[0]):
243 # if not a keyword, every line must have at least a key and value
245 self
._parse
_error
(self
._currentfile
, line_idx
,
246 'iface %s: invalid syntax \'%s\'' %(ifacename
, l
))
248 ifaceobj
.raw_config
.append(l
)
250 # preprocess vars (XXX: only preprocesses $IFACE for now)
251 attrval
= re
.sub(r
'\$IFACE', ifacename
, attrs
[1])
252 self
._add
_to
_iface
_config
(ifacename
, iface_config
, attrname
,
254 lines_consumed
= line_idx
- cur_idx
256 # Create iface object
257 if ifacename
.find(':') != -1:
258 ifaceobj
.name
= ifacename
.split(':')[0]
260 ifaceobj
.name
= ifacename
262 ifaceobj
.config
= iface_config
263 ifaceobj
.generate_env()
266 ifaceobj
.addr_family
= iface_attrs
[2]
267 ifaceobj
.addr_method
= iface_attrs
[3]
271 self
._validate
_addr
_family
(ifaceobj
, lineno
)
273 if self
.auto_all
or (ifaceobj
.name
in self
.auto_ifaces
):
276 classes
= self
.get_allow_classes_for_iface(ifaceobj
.name
)
278 [ifaceobj
.set_class(c
) for c
in classes
]
280 return lines_consumed
# Return next index
282 def process_iface(self
, lines
, cur_idx
, lineno
):
284 lines_consumed
= self
.parse_iface(lines
, cur_idx
, lineno
, ifaceobj
)
286 range_val
= utils
.parse_iface_range(ifaceobj
.name
)
288 for v
in range(range_val
[1], range_val
[2]):
289 ifaceobj_new
= copy
.deepcopy(ifaceobj
)
290 ifaceobj_new
.realname
= '%s' %ifaceobj
.name
291 ifaceobj_new
.name
= '%s%d' %(range_val
[0], v
)
292 ifaceobj_new
.flags
= iface
.IFACERANGE_ENTRY
293 if v
== range_val
[1]:
294 ifaceobj_new
.flags |
= iface
.IFACERANGE_START
295 self
.callbacks
.get('iface_found')(ifaceobj_new
)
297 self
.callbacks
.get('iface_found')(ifaceobj
)
299 return lines_consumed
# Return next index
301 def process_vlan(self
, lines
, cur_idx
, lineno
):
303 lines_consumed
= self
.parse_iface(lines
, cur_idx
, lineno
, ifaceobj
)
305 range_val
= utils
.parse_iface_range(ifaceobj
.name
)
307 for v
in range(range_val
[1], range_val
[2]):
308 ifaceobj_new
= copy
.deepcopy(ifaceobj
)
309 ifaceobj_new
.realname
= '%s' %ifaceobj
.name
310 ifaceobj_new
.name
= '%s%d' %(range_val
[0], v
)
311 ifaceobj_new
.type = ifaceType
.BRIDGE_VLAN
312 ifaceobj_new
.flags
= iface
.IFACERANGE_ENTRY
313 if v
== range_val
[1]:
314 ifaceobj_new
.flags |
= iface
.IFACERANGE_START
315 self
.callbacks
.get('iface_found')(ifaceobj_new
)
317 ifaceobj
.type = ifaceType
.BRIDGE_VLAN
318 self
.callbacks
.get('iface_found')(ifaceobj
)
320 return lines_consumed
# Return next index
322 network_elems
= { 'source' : process_source
,
323 'allow' : process_allow
,
324 'auto' : process_auto
,
325 'iface' : process_iface
,
326 'vlan' : process_vlan
}
328 def _is_keyword(self
, str):
329 # The additional split here is for allow- keyword
330 tmp_str
= str.split('-')[0]
331 if tmp_str
in self
.network_elems
.keys():
335 def _get_keyword_func(self
, str):
336 tmp_str
= str.split('-')[0]
337 return self
.network_elems
.get(tmp_str
)
339 def get_allow_classes_for_iface(self
, ifacename
):
341 for class_name
, ifacenames
in self
.allow_classes
.items():
342 if ifacename
in ifacenames
:
343 classes
.append(class_name
)
346 def process_interfaces(self
, filedata
):
348 # process line continuations
349 filedata
= ' '.join(d
.strip() for d
in filedata
.split('\\'))
353 raw_config
= filedata
.split('\n')
354 lines
= [l
.strip(whitespaces
) for l
in raw_config
]
355 while (line_idx
< len(lines
)):
356 if self
.ignore_line(lines
[line_idx
]):
359 words
= re
.split(self
._ws
_split
_regex
, lines
[line_idx
])
363 # Check if first element is a supported keyword
364 if self
._is
_keyword
(words
[0]):
365 keyword_func
= self
._get
_keyword
_func
(words
[0])
366 lines_consumed
= keyword_func(self
, lines
, line_idx
, line_idx
+1)
367 line_idx
+= lines_consumed
369 self
._parse
_error
(self
._currentfile
, line_idx
+ 1,
370 'error processing line \'%s\'' %lines
[line_idx
])
374 def read_filedata(self
, filedata
):
375 self
._currentfile
_has
_template
= False
376 # run through template engine
378 rendered_filedata
= self
._template
_engine
.render(filedata
)
379 if rendered_filedata
is filedata
:
380 self
._currentfile
_has
_template
= False
382 self
._currentfile
_has
_template
= True
384 self
._parse
_error
(self
._currentfile
, -1,
385 'failed to render template (%s). ' %str
(e
) +
386 'Continue without template rendering ...')
387 rendered_filedata
= None
389 if rendered_filedata
:
390 self
.process_interfaces(rendered_filedata
)
392 self
.process_interfaces(filedata
)
394 def read_file(self
, filename
, fileiobuf
=None):
396 self
.read_filedata(fileiobuf
)
398 self
._filestack
.append(filename
)
399 self
.logger
.info('processing interfaces file %s' %filename
)
403 self
.read_filedata(filedata
)
404 self
._filestack
.pop()
406 def read_file_json(self
, filename
, fileiobuf
=None):
408 ifacedicts
= json
.loads(fileiobuf
, encoding
="utf-8")
409 #object_hook=ifaceJsonDecoder.json_object_hook)
411 self
.logger
.info('processing interfaces file %s' %filename
)
413 ifacedicts
= json
.load(fp
)
414 #object_hook=ifaceJsonDecoder.json_object_hook)
416 # we need to handle both lists and non lists formats (e.g. {{}})
417 if not isinstance(ifacedicts
,list):
418 ifacedicts
= [ifacedicts
]
420 for ifacedict
in ifacedicts
:
421 ifaceobj
= ifaceJsonDecoder
.json_to_ifaceobj(ifacedict
)
423 self
._validate
_addr
_family
(ifaceobj
)
424 self
.callbacks
.get('validateifaceobj')(ifaceobj
)
425 self
.callbacks
.get('iface_found')(ifaceobj
)
428 """ This member function loads the networkinterfaces file.
430 Assumes networkinterfaces parser object is initialized with the
433 if self
.interfacesfileformat
== 'json':
434 return self
.read_file_json(self
.interfacesfile
,
435 self
.interfacesfileiobuf
)
436 return self
.read_file(self
.interfacesfile
,
437 self
.interfacesfileiobuf
)