]>
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) | |
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) |