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