]>
Commit | Line | Data |
---|---|---|
2c8c4ce7 RP |
1 | #!/usr/bin/python |
2 | # | |
3 | # Copyright 2014 Cumulus Networks, Inc. All rights reserved. | |
4 | # Author: Roopa Prabhu, roopa@cumulusnetworks.com | |
5 | # | |
6 | # iface -- | |
7 | # interface object | |
8 | # | |
9 | ||
10 | """ifupdown2 network interface object | |
11 | ||
12 | It is modeled based on the 'iface' section in /etc/network/interfaces | |
13 | file. But can be extended to include any other network interface format | |
14 | """ | |
15 | ||
16 | from collections import OrderedDict | |
17 | import logging | |
18 | import json | |
19 | ||
f82758bf RP |
20 | class ifaceType(): |
21 | UNKNOWN = 0x0 | |
22 | IFACE = 0x1 | |
23 | BRIDGE_VLAN = 0x2 | |
24 | ||
25 | class ifaceLinkKind(): | |
26 | UNKNOWN = 0x0 | |
27 | BRIDGE = 0x1 | |
28 | BOND = 0x2 | |
29 | ||
30 | class ifaceLinkType(): | |
31 | LINK_UNKNOWN = 0x0 | |
32 | LINK_SLAVE = 0x1 | |
33 | LINK_MASTER = 0x2 | |
34 | LINK_NA = 0x3 | |
35 | ||
faaa176e RP |
36 | class ifaceDependencyType(): |
37 | """ Indicates type of dependency. | |
38 | ||
39 | This class enumerates types of dependency relationships | |
40 | between interfaces. | |
41 | ||
42 | iface dependency relationships can be classified | |
43 | into: | |
44 | - link | |
45 | - master/slave | |
46 | ||
47 | In a 'link' dependency relationship, dependency can be shared | |
48 | between interfaces. example: swp1.100 and | |
49 | swp1.200 can both have 'link' swp1. swp1 is also a dependency | |
50 | of swp1.100 and swp1.200. As you can see dependency | |
51 | swp1 is shared between swp1.100 and swp1.200. | |
52 | ||
53 | In a master/slave relationship like bridge and | |
54 | its ports: eg: bridge br0 and its ports swp1 and swp2. | |
55 | dependency swp1 and swp2 cannot be shared with any other | |
56 | interface with the same dependency relationship. | |
57 | ie, swp1 and swp2 cannot be in a slave relationship | |
58 | with another interface. Understanding the dependency type is | |
59 | required for any semantic checks between dependencies. | |
60 | ||
61 | """ | |
62 | UNKNOWN = 0x0 | |
63 | LINK = 0x1 | |
64 | MASTER_SLAVE = 0x2 | |
65 | ||
2c8c4ce7 RP |
66 | class ifaceStatus(): |
67 | """Enumerates iface status """ | |
68 | ||
69 | UNKNOWN = 0x1 | |
70 | SUCCESS = 0x2 | |
71 | ERROR = 0x3 | |
72 | NOTFOUND = 0x4 | |
73 | ||
74 | @classmethod | |
75 | def to_str(cls, state): | |
76 | if state == cls.UNKNOWN: | |
77 | return 'unknown' | |
78 | elif state == cls.SUCCESS: | |
79 | return 'success' | |
80 | elif state == cls.ERROR: | |
81 | return 'error' | |
82 | elif state == cls.NOTFOUND: | |
83 | return 'notfound' | |
84 | ||
85 | @classmethod | |
86 | def from_str(cls, state_str): | |
87 | if state_str == 'unknown': | |
88 | return cls.UNKNOWN | |
89 | elif state_str == 'success': | |
90 | return cls.SUCCESS | |
91 | elif state_str == 'error': | |
92 | return cls.ERROR | |
93 | ||
94 | class ifaceState(): | |
95 | """Enumerates iface state """ | |
96 | ||
97 | UNKNOWN = 0x1 | |
98 | NEW = 0x2 | |
99 | PRE_UP = 0x3 | |
100 | UP = 0x4 | |
101 | POST_UP = 0x5 | |
102 | PRE_DOWN = 0x6 | |
103 | DOWN = 0x7 | |
104 | POST_DOWN = 0x8 | |
105 | ||
106 | # Pseudo states | |
107 | QUERY_CHECKCURR = 0x9 | |
108 | QUERY_RUNNING = 0xa | |
109 | ||
110 | @classmethod | |
111 | def to_str(cls, state): | |
112 | if state == cls.UNKNOWN: | |
113 | return 'unknown' | |
114 | elif state == cls.NEW: | |
115 | return 'new' | |
116 | elif state == cls.PRE_UP: | |
117 | return 'pre-up' | |
118 | elif state == cls.UP: | |
119 | return 'up' | |
120 | elif state == cls.POST_UP: | |
121 | return 'post-up' | |
122 | elif state == cls.PRE_DOWN: | |
123 | return 'pre-down' | |
124 | elif state == cls.DOWN: | |
125 | return 'down' | |
126 | elif state == cls.POST_DOWN: | |
127 | return 'post-down' | |
128 | elif state == cls.QUERY_CHECKCURR: | |
129 | return 'query-checkcurr' | |
130 | elif state == cls.QUERY_RUNNING: | |
131 | return 'query-running' | |
132 | ||
133 | @classmethod | |
134 | def from_str(cls, state_str): | |
135 | if state_str == 'unknown': | |
136 | return cls.UNKNOWN | |
137 | elif state_str == 'new': | |
138 | return cls.NEW | |
139 | elif state_str == 'pre-up': | |
140 | return cls.PRE_UP | |
141 | elif state_str == 'up': | |
142 | return cls.UP | |
143 | elif state_str == 'post-up': | |
144 | return cls.POST_UP | |
145 | elif state_str == 'pre-down': | |
146 | return cls.PRE_DOWN | |
147 | elif state_str == 'down': | |
148 | return cls.DOWN | |
149 | elif state_str == 'post-down': | |
150 | return cls.POST_DOWN | |
151 | elif state_str == 'query-checkcurr': | |
152 | return cls.QUERY_CHECKCURR | |
153 | elif state_str == 'query-running': | |
154 | return cls.QUERY_RUNNING | |
155 | ||
156 | class ifaceJsonEncoder(json.JSONEncoder): | |
157 | def default(self, o): | |
158 | retconfig = {} | |
159 | if o.config: | |
160 | retconfig = dict((k, (v[0] if len(v) == 1 else v)) | |
161 | for k,v in o.config.items()) | |
162 | return OrderedDict({'name' : o.name, | |
163 | 'addr_method' : o.addr_method, | |
164 | 'addr_family' : o.addr_family, | |
165 | 'auto' : o.auto, | |
166 | 'config' : retconfig}) | |
167 | ||
168 | class ifaceJsonDecoder(): | |
169 | @classmethod | |
170 | def json_to_ifaceobj(cls, ifaceattrdict): | |
171 | ifaceattrdict['config'] = OrderedDict([(k, (v if isinstance(v, list) | |
172 | else [v])) | |
173 | for k,v in ifaceattrdict.get('config', | |
174 | OrderedDict()).items()]) | |
175 | return iface(attrsdict=ifaceattrdict) | |
176 | ||
177 | class iface(): | |
178 | """ ifupdown2 iface object class | |
179 | ||
180 | Attributes: | |
181 | **name** Name of the interface | |
182 | ||
183 | **addr_family** Address family eg, inet, inet6. Can be None to | |
184 | indicate both address families | |
185 | ||
186 | **addr_method** Address method eg, static, manual or None for | |
187 | static address method | |
188 | ||
189 | **config** dictionary of config lines for this interface | |
190 | ||
191 | **state** Configuration state of an interface as defined by | |
192 | ifaceState | |
193 | ||
194 | **status** Configuration status of an interface as defined by | |
195 | ifaceStatus | |
196 | ||
197 | **flags** Internal flags used by iface processing | |
198 | ||
199 | **priv_flags** private flags owned by module using this class | |
200 | ||
f7e2962a ST |
201 | **module_flags** module flags owned by module using this class |
202 | ||
2c8c4ce7 RP |
203 | **refcnt** reference count, indicating number of interfaces |
204 | dependent on this iface | |
205 | ||
206 | **lowerifaces** list of interface names lower to this interface or | |
207 | this interface depends on | |
208 | ||
209 | **upperifaces** list of interface names upper to this interface or | |
210 | the interfaces that depend on this interface | |
211 | ||
212 | **auto** True if interface belongs to the auto class | |
213 | ||
214 | **classes** List of classes the interface belongs to | |
215 | ||
216 | **env** shell environment the interface needs during | |
217 | execution | |
218 | ||
219 | **raw_config** raw interface config from file | |
220 | """ | |
221 | ||
222 | # flag to indicate that the object was created from pickled state | |
f82758bf RP |
223 | _PICKLED = 0x00000001 |
224 | HAS_SIBLINGS = 0x00000010 | |
225 | IFACERANGE_ENTRY = 0x00000100 | |
226 | IFACERANGE_START = 0x00001000 | |
2c8c4ce7 RP |
227 | |
228 | version = '0.1' | |
229 | ||
230 | def __init__(self, attrsdict={}): | |
231 | self._set_attrs_from_dict(attrsdict) | |
232 | self._config_status = {} | |
233 | """dict with config status of iface attributes""" | |
234 | self.state = ifaceState.NEW | |
235 | """iface state (of type ifaceState) """ | |
236 | self.status = ifaceStatus.UNKNOWN | |
237 | """iface status (of type ifaceStatus) """ | |
f82758bf RP |
238 | self.status_str = None |
239 | """iface status str (string representing the status) """ | |
2c8c4ce7 RP |
240 | self.flags = 0x0 |
241 | """iface flags """ | |
242 | self.priv_flags = 0x0 | |
f7e2962a ST |
243 | """iface module flags dictionary with module name: flags""" |
244 | self.module_flags = {} | |
2c8c4ce7 RP |
245 | """iface priv flags. can be used by the external object manager """ |
246 | self.refcnt = 0 | |
247 | """iface refcnt (incremented for each dependent this interface has) """ | |
f82758bf | 248 | self.lowerifaces = None |
2c8c4ce7 RP |
249 | """lower iface list (in other words: slaves of this interface """ |
250 | self.upperifaces = None | |
251 | """upper iface list (in other words: master of this interface """ | |
252 | self.classes = [] | |
253 | """interface classes this iface belongs to """ | |
254 | self.env = None | |
255 | """environment variable dict required for this interface to run""" | |
256 | self.raw_config = [] | |
257 | """interface config/attributes in raw format (eg: as it appeared in the interfaces file)""" | |
258 | self.linkstate = None | |
259 | """linkstate of the interface""" | |
f82758bf RP |
260 | self.type = ifaceType.UNKNOWN |
261 | """interface type""" | |
262 | self.priv_data = None | |
263 | self.realname = None | |
264 | self.link_type = ifaceLinkType.LINK_UNKNOWN | |
265 | self.link_kind = ifaceLinkKind.UNKNOWN | |
2c8c4ce7 | 266 | |
faaa176e RP |
267 | # The below attribute is used to disambiguate between various |
268 | # types of dependencies | |
269 | self.dependency_type = ifaceDependencyType.UNKNOWN | |
270 | ||
2c8c4ce7 RP |
271 | def _set_attrs_from_dict(self, attrdict): |
272 | self.auto = attrdict.get('auto', False) | |
273 | self.name = attrdict.get('name') | |
274 | self.addr_family = attrdict.get('addr_family') | |
275 | self.addr_method = attrdict.get('addr_method') | |
276 | self.config = attrdict.get('config', OrderedDict()) | |
277 | ||
278 | def inc_refcnt(self): | |
279 | """ increment refcnt of the interface. Usually used to indicate that | |
280 | it has dependents """ | |
281 | self.refcnt += 1 | |
282 | ||
283 | def dec_refcnt(self): | |
284 | """ decrement refcnt of the interface. Usually used to indicate that | |
285 | it has lost its dependent """ | |
286 | self.refcnt -= 1 | |
287 | ||
288 | def is_config_present(self): | |
289 | """ returns true if the interface has user provided config, | |
290 | false otherwise """ | |
291 | addr_method = self.addr_method | |
292 | if addr_method and addr_method in ['dhcp', 'dhcp6', 'loopback']: | |
293 | return True | |
294 | if not self.config: | |
295 | return False | |
296 | else: | |
297 | return True | |
298 | ||
299 | def set_class(self, classname): | |
300 | """ appends class to the interfaces class list """ | |
301 | self.classes.append(classname) | |
302 | ||
303 | def set_state_n_status(self, state, status): | |
304 | """ sets state and status of an interface """ | |
305 | self.state = state | |
306 | self.status = status | |
307 | ||
308 | def set_flag(self, flag): | |
309 | self.flags |= flag | |
310 | ||
311 | def clear_flag(self, flag): | |
312 | self.flags &= ~flag | |
313 | ||
314 | def add_to_upperifaces(self, upperifacename): | |
315 | """ add to the list of upperifaces """ | |
316 | if self.upperifaces: | |
317 | if upperifacename not in self.upperifaces: | |
318 | self.upperifaces.append(upperifacename) | |
319 | else: | |
320 | self.upperifaces = [upperifacename] | |
321 | ||
322 | def get_attr_value(self, attr_name): | |
323 | """ add to the list of upperifaces """ | |
324 | return self.config.get(attr_name) | |
325 | ||
326 | def get_attr_value_first(self, attr_name): | |
327 | """ get first value of the specified attr name """ | |
328 | attr_value_list = self.config.get(attr_name) | |
329 | if attr_value_list: | |
330 | return attr_value_list[0] | |
331 | return None | |
332 | ||
f82758bf RP |
333 | def get_attrs_value_first(self, attrs): |
334 | """ get first value of the first attr in the list. | |
335 | Useful when you have multiple attrs representing the | |
336 | same thing. | |
337 | """ | |
338 | for attr in attrs: | |
339 | attr_value_list = self.config.get(attr) | |
340 | if attr_value_list: | |
341 | return attr_value_list[0] | |
342 | return None | |
343 | ||
2c8c4ce7 RP |
344 | def get_attr_value_n(self, attr_name, attr_index): |
345 | """ get n'th value of the specified attr name """ | |
346 | attr_value_list = self.config.get(attr_name) | |
347 | if attr_value_list: | |
348 | try: | |
349 | return attr_value_list[attr_index] | |
350 | except: | |
351 | return None | |
352 | return None | |
353 | ||
354 | @property | |
355 | def get_env(self): | |
356 | """ get shell environment variables the interface must execute in """ | |
357 | if not self.env: | |
358 | self.generate_env() | |
359 | return self.env | |
360 | ||
361 | def generate_env(self): | |
362 | """ generate shell environment variables dict interface must execute | |
363 | in. This is used to support legacy ifupdown scripts | |
364 | """ | |
365 | env = {} | |
366 | config = self.config | |
367 | env['IFACE'] = self.name | |
368 | for attr, attr_value in config.items(): | |
369 | attr_env_name = 'IF_%s' %attr.upper() | |
370 | env[attr_env_name] = attr_value[0] | |
371 | if env: | |
372 | self.env = env | |
373 | ||
374 | def update_config(self, attr_name, attr_value): | |
375 | """ add attribute name and value to the interface config """ | |
376 | self.config.setdefault(attr_name, []).append(attr_value) | |
377 | ||
f82758bf RP |
378 | def replace_config(self, attr_name, attr_value): |
379 | """ add attribute name and value to the interface config """ | |
380 | self.config[attr_name] = [attr_value] | |
381 | ||
382 | def delete_config(self, attr_name): | |
383 | """ add attribute name and value to the interface config """ | |
384 | try: | |
385 | del self.config[attr_name] | |
386 | except: | |
387 | pass | |
388 | ||
2c8c4ce7 RP |
389 | def update_config_dict(self, attrdict): |
390 | self.config.update(attrdict) | |
391 | ||
392 | def update_config_with_status(self, attr_name, attr_value, attr_status=0): | |
393 | """ add attribute name and value to the interface config and also | |
394 | update the config_status dict with status of this attribute config """ | |
395 | if not attr_value: | |
396 | attr_value = '' | |
397 | self.config.setdefault(attr_name, []).append(attr_value) | |
398 | self._config_status.setdefault(attr_name, []).append(attr_status) | |
2c8c4ce7 | 399 | # set global iface state |
f82758bf | 400 | if attr_status == 1: |
2c8c4ce7 RP |
401 | self.status = ifaceStatus.ERROR |
402 | elif self.status != ifaceStatus.ERROR: | |
403 | # Not already error, mark success | |
404 | self.status = ifaceStatus.SUCCESS | |
405 | ||
f82758bf RP |
406 | def check_n_update_config_with_status_many(self, ifaceobjorig, attr_names, |
407 | attr_status=0): | |
408 | # set multiple attribute status to zero | |
409 | # also updates status only if the attribute is present | |
410 | for attr_name in attr_names: | |
411 | if not ifaceobjorig.get_attr_value_first(attr_name): | |
412 | continue | |
413 | self.config.setdefault(attr_name, []).append('') | |
414 | self._config_status.setdefault(attr_name, []).append(attr_status) | |
415 | ||
2c8c4ce7 RP |
416 | def get_config_attr_status(self, attr_name, idx=0): |
417 | """ get status of a attribute config on this interface. | |
418 | ||
419 | Looks at the iface _config_status dict""" | |
420 | return self._config_status.get(attr_name, [])[idx] | |
421 | ||
422 | def compare(self, dstiface): | |
423 | """ compares iface object with iface object passed as argument | |
424 | ||
425 | Returns True if object self is same as dstiface and False otherwise """ | |
426 | ||
427 | if self.name != dstiface.name: return False | |
f82758bf | 428 | if self.type != dstiface.type: return False |
2c8c4ce7 RP |
429 | if self.addr_family != dstiface.addr_family: return False |
430 | if self.addr_method != dstiface.addr_method: return False | |
431 | if self.auto != dstiface.auto: return False | |
432 | if self.classes != dstiface.classes: return False | |
f8e2f6e2 RP |
433 | if len(self.config) != len(dstiface.config): |
434 | return False | |
2c8c4ce7 RP |
435 | if any(True for k in self.config if k not in dstiface.config): |
436 | return False | |
437 | if any(True for k,v in self.config.items() | |
438 | if v != dstiface.config.get(k)): return False | |
439 | return True | |
440 | ||
f82758bf | 441 | |
2c8c4ce7 RP |
442 | def __getstate__(self): |
443 | odict = self.__dict__.copy() | |
444 | del odict['state'] | |
445 | del odict['status'] | |
446 | del odict['lowerifaces'] | |
447 | del odict['upperifaces'] | |
448 | del odict['refcnt'] | |
449 | del odict['_config_status'] | |
450 | del odict['flags'] | |
451 | del odict['priv_flags'] | |
f7e2962a | 452 | del odict['module_flags'] |
2c8c4ce7 RP |
453 | del odict['raw_config'] |
454 | del odict['linkstate'] | |
455 | del odict['env'] | |
f82758bf RP |
456 | del odict['link_type'] |
457 | del odict['link_kind'] | |
faaa176e | 458 | del odict['dependency_type'] |
2c8c4ce7 RP |
459 | return odict |
460 | ||
461 | def __setstate__(self, dict): | |
462 | self.__dict__.update(dict) | |
463 | self._config_status = {} | |
464 | self.state = ifaceState.NEW | |
465 | self.status = ifaceStatus.UNKNOWN | |
466 | self.refcnt = 0 | |
467 | self.flags = 0 | |
468 | self.lowerifaces = None | |
469 | self.upperifaces = None | |
470 | self.linkstate = None | |
471 | self.env = None | |
472 | self.priv_flags = 0 | |
f7e2962a | 473 | self.module_flags = {} |
2c8c4ce7 RP |
474 | self.raw_config = [] |
475 | self.flags |= self._PICKLED | |
f82758bf RP |
476 | self.link_type = ifaceLinkType.LINK_NA |
477 | self.link_kind = ifaceLinkKind.UNKNOWN | |
faaa176e | 478 | self.dependency_type = ifaceDependencyType.UNKNOWN |
2c8c4ce7 RP |
479 | |
480 | def dump_raw(self, logger): | |
481 | indent = ' ' | |
482 | if self.auto: | |
483 | print 'auto %s' %self.name | |
484 | print (self.raw_config[0]) | |
485 | for i in range(1, len(self.raw_config)): | |
486 | print(indent + self.raw_config[i]) | |
487 | ||
488 | def dump(self, logger): | |
489 | indent = '\t' | |
490 | logger.info(self.name + ' : {') | |
491 | logger.info(indent + 'family: %s' %self.addr_family) | |
492 | logger.info(indent + 'method: %s' %self.addr_method) | |
493 | logger.info(indent + 'flags: %x' %self.flags) | |
494 | logger.info(indent + 'state: %s' | |
495 | %ifaceState.to_str(self.state)) | |
496 | logger.info(indent + 'status: %s' | |
497 | %ifaceStatus.to_str(self.status)) | |
498 | logger.info(indent + 'refcnt: %d' %self.refcnt) | |
499 | d = self.lowerifaces | |
500 | if d: | |
501 | logger.info(indent + 'lowerdevs: %s' %str(d)) | |
502 | else: | |
503 | logger.info(indent + 'lowerdevs: None') | |
504 | ||
505 | logger.info(indent + 'config: ') | |
506 | config = self.config | |
507 | if config: | |
508 | logger.info(indent + indent + str(config)) | |
509 | logger.info('}') | |
510 | ||
511 | def dump_pretty(self, with_status=False, | |
f82758bf RP |
512 | successstr='success', errorstr='error', |
513 | unknownstr='unknown', use_realname=False): | |
2c8c4ce7 RP |
514 | indent = '\t' |
515 | outbuf = '' | |
f82758bf RP |
516 | if use_realname and self.realname: |
517 | name = '%s' %self.realname | |
518 | else: | |
519 | name = '%s' %self.name | |
2c8c4ce7 | 520 | if self.auto: |
f82758bf RP |
521 | outbuf += 'auto %s\n' %name |
522 | ifaceline = '' | |
523 | if self.type == ifaceType.BRIDGE_VLAN: | |
524 | ifaceline += 'vlan %s' %name | |
525 | else: | |
526 | ifaceline += 'iface %s' %name | |
2c8c4ce7 | 527 | if self.addr_family: |
f82758bf | 528 | ifaceline += ' %s' %self.addr_family |
2c8c4ce7 | 529 | if self.addr_method: |
f82758bf | 530 | ifaceline += ' %s' %self.addr_method |
2c8c4ce7 | 531 | if with_status: |
f82758bf RP |
532 | status_str = None |
533 | if (self.status == ifaceStatus.ERROR or | |
534 | self.status == ifaceStatus.NOTFOUND): | |
535 | if self.status_str: | |
536 | ifaceline += ' (%s)' %self.status_str | |
537 | status_str = errorstr | |
2c8c4ce7 | 538 | elif self.status == ifaceStatus.SUCCESS: |
f82758bf RP |
539 | status_str = successstr |
540 | if status_str: | |
541 | outbuf += '{0:65} {1:>8}'.format(ifaceline, status_str) + '\n' | |
542 | else: | |
543 | outbuf += ifaceline + '\n' | |
2c8c4ce7 | 544 | if self.status == ifaceStatus.NOTFOUND: |
f82758bf RP |
545 | outbuf = (outbuf.encode('utf8') |
546 | if isinstance(outbuf, unicode) else outbuf) | |
2c8c4ce7 RP |
547 | print outbuf + '\n' |
548 | return | |
f82758bf RP |
549 | else: |
550 | outbuf += ifaceline + '\n' | |
2c8c4ce7 RP |
551 | config = self.config |
552 | if config: | |
553 | for cname, cvaluelist in config.items(): | |
554 | idx = 0 | |
555 | for cv in cvaluelist: | |
2c8c4ce7 RP |
556 | if with_status: |
557 | s = self.get_config_attr_status(cname, idx) | |
f82758bf RP |
558 | if s == -1: |
559 | status_str = unknownstr | |
560 | elif s == 1: | |
561 | status_str = errorstr | |
2c8c4ce7 | 562 | elif s == 0: |
f82758bf RP |
563 | status_str = successstr |
564 | outbuf += (indent + '{0:55} {1:>10}'.format( | |
565 | '%s %s' %(cname, cv), status_str)) + '\n' | |
2c8c4ce7 RP |
566 | else: |
567 | outbuf += indent + '%s %s\n' %(cname, cv) | |
568 | idx += 1 | |
569 | if with_status: | |
570 | outbuf = (outbuf.encode('utf8') | |
571 | if isinstance(outbuf, unicode) else outbuf) | |
572 | print outbuf |