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