]>
Commit | Line | Data |
---|---|---|
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 | |
10 | import collections | |
a6f80f0e | 11 | import logging |
12 | import glob | |
739f665b | 13 | import re |
14dc390d | 14 | import os |
3e8ee54f | 15 | from iface import * |
14dc390d | 16 | from template import templateEngine |
a6f80f0e | 17 | |
18 | class 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) |