]> git.proxmox.com Git - mirror_ifupdown2.git/blame - pkg/networkinterfaces.py
Fixes for some corner cases + cleanup
[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
3e8ee54f 14from iface import *
a6f80f0e 15
16class networkInterfaces():
17
18 hotplugs = {}
19 auto_ifaces = []
20 callbacks = {}
21
22 ifaces_file = "/etc/network/interfaces"
23
24 def __init__(self):
25 self.logger = logging.getLogger('ifupdown.' +
26 self.__class__.__name__)
d08d5f54 27 self.callbacks = {'iface_found' : None,
28 'validate' : None}
a6f80f0e 29 self.allow_classes = {}
30
31
32 def subscribe(self, callback_name, callback_func):
33 if callback_name not in self.callbacks.keys():
34 print 'warning: invalid callback ' + callback_name
35 return -1
36
37 self.callbacks[callback_name] = callback_func
38
39
40 def ignore_line(self, line):
41 l = line.strip('\n ')
42
43 if len(l) == 0 or l[0] == '#':
44 return 1
45
46 return 0
47
48 def process_allow(self, lines, cur_idx, lineno):
3e8ee54f 49 allow_line = lines[cur_idx]
a6f80f0e 50
51 words = allow_line.split()
52 if len(words) <= 1:
be0b20f2 53 raise Exception('invalid allow line \'%s\' at line %d'
54 %(allow_line, lineno))
a6f80f0e 55
56 allow_class = words[0].split('-')[1]
57 ifacenames = words[1:]
58
59 if self.allow_classes.get(allow_class) is not None:
60 for i in ifacenames:
61 self.allow_classes[allow_class].append(i)
62 else:
63 self.allow_classes[allow_class] = ifacenames
a6f80f0e 64 return 0
65
a6f80f0e 66 def process_source(self, lines, cur_idx, lineno):
67 # Support regex
37c0543d 68 self.logger.debug('processing sourced line ..\'%s\'' %lines[cur_idx])
a6f80f0e 69 sourced_file = lines[cur_idx].split(' ', 2)[1]
70 if sourced_file is not None:
71 for f in glob.glob(sourced_file):
eab25b7c 72 self.read_file(f)
a6f80f0e 73 else:
74 self.logger.warn('unable to read source line at %d', lineno)
75
76 return 0
77
78 def process_auto(self, lines, cur_idx, lineno):
be0b20f2 79 for a in lines[cur_idx].split()[1:]:
80 self.auto_ifaces.append(a)
a6f80f0e 81 return 0
82
be0b20f2 83 def _add_to_iface_config(self, iface_config, attrname, attrval):
84 attrvallist = iface_config.get(attrname, [])
85 if attrname in ['scope', 'netmask', 'broadcast', 'preferred-lifetime']:
86 # For attributes that are related and that can have multiple
87 # entries, store them at the same index as their parent attribute.
88 # The example of such attributes is 'address' and its related
89 # attributes. since the related attributes can be optional,
90 # we add null string '' in places where they are optional.
91 # XXX: this introduces awareness of attribute names in
92 # this class which is a violation.
93
94 # get the index corresponding to the 'address'
95 addrlist = iface_config.get('address')
96 if addrlist:
97 # find the index of last address element
98 for i in range(0, len(addrlist) - len(attrvallist) -1):
99 attrvallist.append('')
100 attrvallist.append(attrval)
101 iface_config[attrname] = attrvallist
102 elif not attrvallist:
103 iface_config[attrname] = [attrval]
104 else:
105 iface_config[attrname].append(attrval)
a6f80f0e 106
107 def process_iface(self, lines, cur_idx, lineno):
108 lines_consumed = 0
579b3f25 109 line_idx = cur_idx
a6f80f0e 110
111 ifaceobj = iface()
112
113 iface_line = lines[cur_idx].strip('\n ')
114 iface_attrs = iface_line.split()
739f665b 115 ifacename = iface_attrs[1]
a6f80f0e 116
117 ifaceobj.raw_lines.append(iface_line)
118
119 iface_config = collections.OrderedDict()
120 for line_idx in range(cur_idx + 1, len(lines)):
121 l = lines[line_idx].strip('\n\t ')
122
123 if self.ignore_line(l) == 1:
124 continue
125
a6f80f0e 126 if self.is_keyword(l.split()[0]) == True:
127 line_idx -= 1
128 break
129
eab25b7c 130 ifaceobj.raw_lines.append(l)
131
739f665b 132 # preprocess vars (XXX: only preprocesses $IFACE for now)
133 l = re.sub(r'\$IFACE', ifacename, l)
134
eab25b7c 135 attrs = l.split(' ', 1)
136 if len(attrs) < 2:
be0b20f2 137 self.logger.warn('%s: invalid syntax at line %d'
138 %(ifacename, line_idx + 1))
eab25b7c 139 continue
d08d5f54 140 attrname = attrs[0]
141 attrval = attrs[1].strip(' ')
142 try:
143 if not self.callbacks.get('validate')(attrname, attrval):
144 self.logger.warn('unsupported keyword (%s) at line %d'
145 %(l, line_idx + 1))
146 except:
147 pass
be0b20f2 148 self._add_to_iface_config(iface_config, attrname, attrval)
a6f80f0e 149
150 lines_consumed = line_idx - cur_idx
151
152 # Create iface object
37c0543d 153 if ifacename.find(':') != -1:
154 ifaceobj.set_name(ifacename.split(':')[0])
155 else:
156 ifaceobj.set_name(ifacename)
157
a6f80f0e 158 ifaceobj.set_config(iface_config)
159 ifaceobj.generate_env()
160 if len(iface_attrs) > 2:
161 ifaceobj.set_addr_family(iface_attrs[2])
162 ifaceobj.set_addr_method(iface_attrs[3])
163
164 if ifaceobj.get_name() in self.auto_ifaces:
165 ifaceobj.set_auto()
166
167 classes = ifaceobj.set_classes(
168 self.get_allow_classes_for_iface(ifaceobj.get_name()))
169 if classes is not None and len(classes) > 0:
170 for c in classes:
171 ifaceobj.set_class(c)
172
173 # Call iface found callback
a6f80f0e 174 self.callbacks.get('iface_found')(ifaceobj)
175
176 return lines_consumed # Return next index
177
178
179 network_elems = { 'source' : process_source,
180 'allow' : process_allow,
181 'auto' : process_auto,
182 'iface' : process_iface}
183
184
185 def is_keyword(self, str):
186
187 # The additional split here is for allow- keyword
188 tmp_str = str.split('-')[0]
189 if tmp_str in self.network_elems.keys():
190 return 1
191
192 return 0
193
194 def get_keyword_func(self, str):
195 tmp_str = str.split('-')[0]
196
197 return self.network_elems.get(tmp_str)
198
199 def get_allow_classes_for_iface(self, ifacename):
200 classes = []
201 for class_name, ifacenames in self.allow_classes.items():
202 if ifacename in ifacenames:
203 classes.append(class_name)
204
205 return classes
206
579b3f25 207 def process_filedata(self, filedata):
a6f80f0e 208 lineno = 0
209 line_idx = 0
210 lines_consumed = 0
211
579b3f25 212 raw_lines = filedata.split('\n')
213 lines = [l.strip(' \n') for l in raw_lines]
a6f80f0e 214
579b3f25 215 while (line_idx < len(lines)):
216 lineno = lineno + 1
a6f80f0e 217
579b3f25 218 if self.ignore_line(lines[line_idx]):
219 line_idx += 1
220 continue
221
222 words = lines[line_idx].split()
a6f80f0e 223
579b3f25 224 # Check if first element is a supported keyword
225 if self.is_keyword(words[0]):
226 keyword_func = self.get_keyword_func(words[0])
227 lines_consumed = keyword_func(self, lines, line_idx, lineno)
228 line_idx += lines_consumed
229 else:
230 self.logger.warning('could not process line %s' %l + ' at' +
231 ' lineno %d' %lineno)
a6f80f0e 232
579b3f25 233 line_idx += 1
a6f80f0e 234
579b3f25 235 return 0
a6f80f0e 236
579b3f25 237 def run_template_engine(self, textdata):
238 try:
239 from mako.template import Template
240 except:
37c0543d 241 self.logger.warning('template engine mako not found. ' +
242 'skip template parsing ..');
579b3f25 243 return textdata
a6f80f0e 244
579b3f25 245 t = Template(text=textdata, output_encoding='utf-8')
246 return t.render()
a6f80f0e 247
579b3f25 248 def read_file(self, filename=None):
249 ifaces_file = filename
250 if ifaces_file == None:
251 ifaces_file=self.ifaces_file
252
37c0543d 253 self.logger.debug('reading interfaces file %s' %ifaces_file)
579b3f25 254 f = open(ifaces_file)
255 filedata = f.read()
256 f.close()
257
258 # process line continuations
259 filedata = ' '.join(d.strip() for d in filedata.split('\\'))
260
261 # run through template engine
262 filedata = self.run_template_engine(filedata)
263
264 self.process_filedata(filedata)
a6f80f0e 265
266
267 def load(self, filename=None):
a6f80f0e 268 return self.read_file(filename)