]> git.proxmox.com Git - mirror_ifupdown2.git/blame - pkg/networkinterfaces.py
Fix ifupdown2 dpkg-configure error during build
[mirror_ifupdown2.git] / pkg / networkinterfaces.py
CommitLineData
a6f80f0e 1#!/usr/bin/python
3e8ee54f 2#
3# Copyright 2013. Cumulus Networks, Inc.
4# Author: Roopa Prabhu, roopa@cumulusnetworks.com
5#
6# networkInterfaces --
7# ifupdown network interfaces file parser
8#
a6f80f0e 9
10import collections
a6f80f0e 11import logging
12import glob
739f665b 13import re
14dc390d 14import os
3e8ee54f 15from iface import *
14dc390d 16from template import templateEngine
a6f80f0e 17
18class networkInterfaces():
19
20 hotplugs = {}
21 auto_ifaces = []
22 callbacks = {}
23
d913472d 24 _addrfams = {'inet' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6'],
25 'inet6' : ['static', 'manual', 'loopback', 'dhcp', 'dhcp6']}
26
14dc390d 27 def __init__(self, interfacesfile='/etc/network/interfaces',
28 template_engine=None, template_lookuppath=None):
a6f80f0e 29 self.logger = logging.getLogger('ifupdown.' +
30 self.__class__.__name__)
d08d5f54 31 self.callbacks = {'iface_found' : None,
32 'validate' : None}
a6f80f0e 33 self.allow_classes = {}
14dc390d 34 self.interfacesfile = interfacesfile
35 self._filestack = [self.interfacesfile]
36 self._template_engine = templateEngine(template_engine,
37 template_lookuppath)
a6f80f0e 38
9dce3561 39 @property
40 def _currentfile(self):
41 try:
42 return self._filestack[-1]
43 except:
14dc390d 44 return self.interfacesfile
9dce3561 45
46 def _parse_error(self, filename, lineno, msg):
47 if lineno == -1:
48 self.logger.error('%s: %s' %(filename, msg))
49 else:
50 self.logger.error('%s: line%d: %s' %(filename, lineno, msg))
a6f80f0e 51
d913472d 52 def _validate_addr_family(self, ifaceobj, lineno):
53 if ifaceobj.addr_family:
54 if not self._addrfams.get(ifaceobj.addr_family):
55 self._parse_error(self._currentfile, lineno,
56 'iface %s: unsupported address family \'%s\''
57 %(ifaceobj.name, ifaceobj.addr_family))
58 ifaceobj.addr_family = None
59 ifaceobj.addr_method = None
60 return
61 if ifaceobj.addr_method:
62 if (ifaceobj.addr_method not in
63 self._addrfams.get(ifaceobj.addr_family)):
64 self._parse_error(self._currentfile, lineno,
65 'iface %s: unsupported address method \'%s\''
66 %(ifaceobj.name, ifaceobj.addr_method))
67 else:
68 ifaceobj.addr_method = 'static'
69
a6f80f0e 70 def subscribe(self, callback_name, callback_func):
71 if callback_name not in self.callbacks.keys():
72 print 'warning: invalid callback ' + callback_name
73 return -1
74
75 self.callbacks[callback_name] = callback_func
76
a6f80f0e 77 def ignore_line(self, line):
78 l = line.strip('\n ')
fe0a57d3 79 if not l or l[0] == '#':
a6f80f0e 80 return 1
a6f80f0e 81 return 0
82
83 def process_allow(self, lines, cur_idx, lineno):
3e8ee54f 84 allow_line = lines[cur_idx]
a6f80f0e 85
86 words = allow_line.split()
87 if len(words) <= 1:
be0b20f2 88 raise Exception('invalid allow line \'%s\' at line %d'
89 %(allow_line, lineno))
a6f80f0e 90
91 allow_class = words[0].split('-')[1]
92 ifacenames = words[1:]
93
62ddec8b 94 if self.allow_classes.get(allow_class):
a6f80f0e 95 for i in ifacenames:
96 self.allow_classes[allow_class].append(i)
97 else:
98 self.allow_classes[allow_class] = ifacenames
a6f80f0e 99 return 0
100
a6f80f0e 101 def process_source(self, lines, cur_idx, lineno):
102 # Support regex
37c0543d 103 self.logger.debug('processing sourced line ..\'%s\'' %lines[cur_idx])
a6f80f0e 104 sourced_file = lines[cur_idx].split(' ', 2)[1]
62ddec8b 105 if sourced_file:
a6f80f0e 106 for f in glob.glob(sourced_file):
eab25b7c 107 self.read_file(f)
a6f80f0e 108 else:
9dce3561 109 self._parse_error(self._currentfile, lineno,
110 'unable to read source line')
a6f80f0e 111 return 0
112
113 def process_auto(self, lines, cur_idx, lineno):
9dce3561 114 auto_ifaces = lines[cur_idx].split()[1:]
115 if not auto_ifaces:
d913472d 116 self._parse_error(self._currentfile, lineno,
9dce3561 117 'invalid auto line \'%s\''%lines[cur_idx])
118 return 0
119 [self.auto_ifaces.append(a) for a in auto_ifaces]
a6f80f0e 120 return 0
121
14dc390d 122 def _add_to_iface_config(self, ifacename, iface_config, attrname,
123 attrval, lineno):
7a51240e 124 newattrname = attrname.replace("_", "-")
7949b8a5 125 try:
126 if not self.callbacks.get('validate')(newattrname, attrval):
127 self._parse_error(self._currentfile, lineno,
14dc390d 128 'iface %s: unsupported keyword (%s)'
129 %(ifacename, attrname))
7949b8a5 130 return
131 except:
132 pass
7a51240e 133 attrvallist = iface_config.get(newattrname, [])
134 if newattrname in ['scope', 'netmask', 'broadcast', 'preferred-lifetime']:
be0b20f2 135 # For attributes that are related and that can have multiple
136 # entries, store them at the same index as their parent attribute.
137 # The example of such attributes is 'address' and its related
138 # attributes. since the related attributes can be optional,
139 # we add null string '' in places where they are optional.
140 # XXX: this introduces awareness of attribute names in
141 # this class which is a violation.
142
143 # get the index corresponding to the 'address'
144 addrlist = iface_config.get('address')
145 if addrlist:
146 # find the index of last address element
147 for i in range(0, len(addrlist) - len(attrvallist) -1):
148 attrvallist.append('')
149 attrvallist.append(attrval)
7a51240e 150 iface_config[newattrname] = attrvallist
be0b20f2 151 elif not attrvallist:
7a51240e 152 iface_config[newattrname] = [attrval]
be0b20f2 153 else:
7a51240e 154 iface_config[newattrname].append(attrval)
a6f80f0e 155
156 def process_iface(self, lines, cur_idx, lineno):
157 lines_consumed = 0
579b3f25 158 line_idx = cur_idx
a6f80f0e 159
160 ifaceobj = iface()
a6f80f0e 161 iface_line = lines[cur_idx].strip('\n ')
162 iface_attrs = iface_line.split()
739f665b 163 ifacename = iface_attrs[1]
a6f80f0e 164
62ddec8b 165 ifaceobj.raw_config.append(iface_line)
a6f80f0e 166
167 iface_config = collections.OrderedDict()
168 for line_idx in range(cur_idx + 1, len(lines)):
169 l = lines[line_idx].strip('\n\t ')
a6f80f0e 170 if self.ignore_line(l) == 1:
171 continue
9dce3561 172 if self._is_keyword(l.split()[0]):
a6f80f0e 173 line_idx -= 1
174 break
62ddec8b 175 ifaceobj.raw_config.append(l)
739f665b 176 # preprocess vars (XXX: only preprocesses $IFACE for now)
177 l = re.sub(r'\$IFACE', ifacename, l)
eab25b7c 178 attrs = l.split(' ', 1)
179 if len(attrs) < 2:
9dce3561 180 self._parse_error(self._currentfile, line_idx,
14dc390d 181 'iface %s: invalid syntax \'%s\'' %(ifacename, l))
eab25b7c 182 continue
d08d5f54 183 attrname = attrs[0]
184 attrval = attrs[1].strip(' ')
14dc390d 185 self._add_to_iface_config(ifacename, iface_config, attrname,
186 attrval, line_idx+1)
a6f80f0e 187 lines_consumed = line_idx - cur_idx
188
189 # Create iface object
37c0543d 190 if ifacename.find(':') != -1:
62ddec8b 191 ifaceobj.name = ifacename.split(':')[0]
37c0543d 192 else:
62ddec8b 193 ifaceobj.name = ifacename
37c0543d 194
62ddec8b 195 ifaceobj.config = iface_config
a6f80f0e 196 ifaceobj.generate_env()
d913472d 197
198 try:
62ddec8b 199 ifaceobj.addr_family = iface_attrs[2]
200 ifaceobj.addr_method = iface_attrs[3]
d913472d 201 except IndexError:
202 # ignore
203 pass
204 self._validate_addr_family(ifaceobj, lineno)
a6f80f0e 205
62ddec8b 206 if ifaceobj.name in self.auto_ifaces:
207 ifaceobj.auto = True
a6f80f0e 208
62ddec8b 209 classes = self.get_allow_classes_for_iface(ifaceobj.name)
fe0a57d3 210 if classes:
62ddec8b 211 [ifaceobj.set_class(c) for c in classes]
a6f80f0e 212
213 # Call iface found callback
a6f80f0e 214 self.callbacks.get('iface_found')(ifaceobj)
a6f80f0e 215 return lines_consumed # Return next index
216
217
218 network_elems = { 'source' : process_source,
219 'allow' : process_allow,
220 'auto' : process_auto,
221 'iface' : process_iface}
222
9dce3561 223 def _is_keyword(self, str):
a6f80f0e 224 # The additional split here is for allow- keyword
225 tmp_str = str.split('-')[0]
226 if tmp_str in self.network_elems.keys():
227 return 1
a6f80f0e 228 return 0
229
9dce3561 230 def _get_keyword_func(self, str):
a6f80f0e 231 tmp_str = str.split('-')[0]
a6f80f0e 232 return self.network_elems.get(tmp_str)
233
234 def get_allow_classes_for_iface(self, ifacename):
235 classes = []
236 for class_name, ifacenames in self.allow_classes.items():
237 if ifacename in ifacenames:
238 classes.append(class_name)
a6f80f0e 239 return classes
240
579b3f25 241 def process_filedata(self, filedata):
a6f80f0e 242 line_idx = 0
243 lines_consumed = 0
62ddec8b 244 raw_config = filedata.split('\n')
245 lines = [l.strip(' \n') for l in raw_config]
579b3f25 246 while (line_idx < len(lines)):
579b3f25 247 if self.ignore_line(lines[line_idx]):
248 line_idx += 1
249 continue
579b3f25 250 words = lines[line_idx].split()
579b3f25 251 # Check if first element is a supported keyword
9dce3561 252 if self._is_keyword(words[0]):
253 keyword_func = self._get_keyword_func(words[0])
d913472d 254 lines_consumed = keyword_func(self, lines, line_idx, line_idx+1)
579b3f25 255 line_idx += lines_consumed
256 else:
9dce3561 257 self._parse_error(self._currentfile, line_idx + 1,
258 'error processing line \'%s\'' %lines[line_idx])
579b3f25 259 line_idx += 1
579b3f25 260 return 0
a6f80f0e 261
579b3f25 262 def read_file(self, filename=None):
14dc390d 263 interfacesfile = filename
264 if not interfacesfile:
265 interfacesfile=self.interfacesfile
266 self._filestack.append(interfacesfile)
267 self.logger.info('reading interfaces file %s' %interfacesfile)
268 f = open(interfacesfile)
579b3f25 269 filedata = f.read()
270 f.close()
579b3f25 271 # process line continuations
272 filedata = ' '.join(d.strip() for d in filedata.split('\\'))
579b3f25 273 # run through template engine
9dce3561 274 try:
14dc390d 275 rendered_filedata = self._template_engine.render(filedata)
9dce3561 276 except Exception, e:
277 self._parse_error(self._currentfile, -1,
14dc390d 278 'failed to render template (%s). ' %str(e) +
9dce3561 279 'Continue without template rendering ...')
280 rendered_filedata = None
281 pass
14dc390d 282 self.logger.info('parsing interfaces file %s ...' %interfacesfile)
9dce3561 283 if rendered_filedata:
284 self.process_filedata(rendered_filedata)
285 else:
286 self.process_filedata(filedata)
287 self._filestack.pop()
a6f80f0e 288
a6f80f0e 289 def load(self, filename=None):
a6f80f0e 290 return self.read_file(filename)