]>
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 |
3e8ee54f | 14 | from iface import * |
a6f80f0e | 15 | |
16 | class 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) |