3 # Copyright (c) 2008,2009 Citrix Systems, Inc. All rights reserved.
4 # Copyright (c) 2009 Nicira Networks.
8 %(command-name)s <PIF> up
9 %(command-name)s <PIF> down
10 %(command-name)s [<PIF>] rewrite
11 %(command-name)s --force <BRIDGE> up
12 %(command-name)s --force <BRIDGE> down
13 %(command-name)s --force <BRIDGE> rewrite --device=<INTERFACE> <CONFIG>
14 %(command-name)s --force all down
16 where <PIF> is one of:
17 --session <SESSION-REF> --pif <PIF-REF>
19 and <CONFIG> is one of:
21 --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>]
24 --session A session reference to use to access the xapi DB
25 --pif A PIF reference within the session.
26 --pif-uuid The UUID of a PIF.
27 --force An interface name.
31 # Undocumented parameters for test & dev:
33 # --output-directory=<DIR> Write configuration to <DIR>. Also disables actually
34 # raising/lowering the interfaces
39 # 1. Every pif belongs to exactly one network
40 # 2. Every network has zero or one pifs
41 # 3. A network may have an associated bridge, allowing vifs to be attached
42 # 4. A network may be bridgeless (there's no point having a bridge over a storage pif)
45 import os
, sys
, getopt
, time
, signal
51 from xml
.dom
.minidom
import getDOMImplementation
52 from xml
.dom
.minidom
import parse
as parseXML
54 output_directory
= None
59 vswitch_state_dir
= "/var/lib/openvswitch/"
60 dbcache_file
= vswitch_state_dir
+ "dbcache"
62 class Usage(Exception):
63 def __init__(self
, msg
):
64 Exception.__init
__(self
)
67 class Error(Exception):
68 def __init__(self
, msg
):
69 Exception.__init
__(self
)
72 class ConfigurationFile(object):
73 """Write a file, tracking old and new versions.
75 Supports writing a new version of a file and applying and
76 reverting those changes.
79 __STATE
= {"OPEN":"OPEN",
80 "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
81 "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
83 def __init__(self
, fname
, path
="/etc/sysconfig/network-scripts"):
85 self
.__state
= self
.__STATE
['OPEN']
90 dirname
= output_directory
94 self
.__path
= os
.path
.join(dirname
, fname
)
95 self
.__oldpath
= os
.path
.join(dirname
, "." + fname
+ ".xapi-old")
96 self
.__newpath
= os
.path
.join(dirname
, "." + fname
+ ".xapi-new")
99 self
.__f
= open(self
.__newpath
, "w")
101 def attach_child(self
, child
):
102 self
.__children
.append(child
)
109 return open(self
.path()).readlines()
113 def write(self
, args
):
114 if self
.__state
!= self
.__STATE
['OPEN']:
115 raise Error("Attempt to write to file in state %s" % self
.__state
)
119 if self
.__state
!= self
.__STATE
['OPEN']:
120 raise Error("Attempt to unlink file in state %s" % self
.__state
)
123 self
.__state
= self
.__STATE
['NOT-APPLIED']
126 if self
.__state
!= self
.__STATE
['OPEN']:
127 raise Error("Attempt to close file in state %s" % self
.__state
)
130 self
.__state
= self
.__STATE
['NOT-APPLIED']
133 if self
.__state
!= self
.__STATE
['NOT-APPLIED']:
134 raise Error("Attempt to compare file in state %s" % self
.__state
)
139 if self
.__state
!= self
.__STATE
['NOT-APPLIED']:
140 raise Error("Attempt to apply configuration from state %s" % self
.__state
)
142 for child
in self
.__children
:
145 log("Applying changes to %s configuration" % self
.__fname
)
147 # Remove previous backup.
148 if os
.access(self
.__oldpath
, os
.F_OK
):
149 os
.unlink(self
.__oldpath
)
151 # Save current configuration.
152 if os
.access(self
.__path
, os
.F_OK
):
153 os
.link(self
.__path
, self
.__oldpath
)
154 os
.unlink(self
.__path
)
156 # Apply new configuration.
157 assert(os
.path
.exists(self
.__newpath
))
158 if not self
.__unlink
:
159 os
.link(self
.__newpath
, self
.__path
)
161 pass # implicit unlink of original file
163 # Remove temporary file.
164 os
.unlink(self
.__newpath
)
166 self
.__state
= self
.__STATE
['APPLIED']
169 if self
.__state
!= self
.__STATE
['APPLIED']:
170 raise Error("Attempt to revert configuration from state %s" % self
.__state
)
172 for child
in self
.__children
:
175 log("Reverting changes to %s configuration" % self
.__fname
)
177 # Remove existing new configuration
178 if os
.access(self
.__newpath
, os
.F_OK
):
179 os
.unlink(self
.__newpath
)
181 # Revert new configuration.
182 if os
.access(self
.__path
, os
.F_OK
):
183 os
.link(self
.__path
, self
.__newpath
)
184 os
.unlink(self
.__path
)
186 # Revert to old configuration.
187 if os
.access(self
.__oldpath
, os
.F_OK
):
188 os
.link(self
.__oldpath
, self
.__path
)
189 os
.unlink(self
.__oldpath
)
191 # Leave .*.xapi-new as an aid to debugging.
193 self
.__state
= self
.__STATE
['REVERTED']
196 if self
.__state
!= self
.__STATE
['APPLIED']:
197 raise Error("Attempt to commit configuration from state %s" % self
.__state
)
199 for child
in self
.__children
:
202 log("Committing changes to %s configuration" % self
.__fname
)
204 if os
.access(self
.__oldpath
, os
.F_OK
):
205 os
.unlink(self
.__oldpath
)
206 if os
.access(self
.__newpath
, os
.F_OK
):
207 os
.unlink(self
.__newpath
)
209 self
.__state
= self
.__STATE
['COMMITTED']
212 return output_directory
is not None
216 print >>sys
.stderr
, s
220 def check_allowed(pif
):
221 pifrec
= db
.get_pif_record(pif
)
223 f
= open("/proc/ardence")
224 macline
= filter(lambda x
: x
.startswith("HWaddr:"), f
.readlines())
226 if len(macline
) == 1:
227 p
= re
.compile(".*\s%(MAC)s\s.*" % pifrec
, re
.IGNORECASE
)
228 if p
.match(macline
[0]):
229 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec
)
235 def interface_exists(i
):
236 return os
.path
.exists("/sys/class/net/" + i
)
238 def get_netdev_mac(device
):
240 return read_first_line_of_file("/sys/class/net/%s/address" % device
)
242 # Probably no such device.
245 def get_netdev_tx_queue_len(device
):
247 return int(read_first_line_of_file("/sys/class/net/%s/tx_queue_len"
250 # Probably no such device.
253 def get_netdev_by_mac(mac
):
254 for device
in os
.listdir("/sys/class/net"):
255 dev_mac
= get_netdev_mac(device
)
256 if (dev_mac
and mac
.lower() == dev_mac
.lower() and
257 get_netdev_tx_queue_len(device
)):
262 # Helper functions for encoding/decoding database attributes to/from XML.
264 def str_to_xml(xml
, parent
, tag
, val
):
265 e
= xml
.createElement(tag
)
266 parent
.appendChild(e
)
267 v
= xml
.createTextNode(val
)
270 def getText(nodelist
):
272 for node
in nodelist
:
273 if node
.nodeType
== node
.TEXT_NODE
:
276 return getText(n
.childNodes
).strip()
279 def bool_to_xml(xml
, parent
, tag
, val
):
281 str_to_xml(xml
, parent
, tag
, "True")
283 str_to_xml(xml
, parent
, tag
, "False")
284 def bool_from_xml(n
):
291 raise Error("Unknown boolean value %s" % s
);
293 def strlist_to_xml(xml
, parent
, ltag
, itag
, val
):
294 e
= xml
.createElement(ltag
)
295 parent
.appendChild(e
)
297 c
= xml
.createElement(itag
)
299 cv
= xml
.createTextNode(v
)
301 def strlist_from_xml(n
, ltag
, itag
):
303 for n
in n
.childNodes
:
304 if n
.nodeName
== itag
:
305 ret
.append(str_from_xml(n
))
308 def otherconfig_to_xml(xml
, parent
, val
, attrs
):
309 otherconfig
= xml
.createElement("other_config")
310 parent
.appendChild(otherconfig
)
311 for n
,v
in val
.items():
313 raise Error("Unknown other-config attribute: %s" % n
)
314 str_to_xml(xml
, otherconfig
, n
, v
)
315 def otherconfig_from_xml(n
, attrs
):
317 for n
in n
.childNodes
:
318 if n
.nodeName
in attrs
:
319 ret
[n
.nodeName
] = str_from_xml(n
)
323 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
325 # Each object is defined by a dictionary mapping an attribute name in
326 # the xapi database to a tuple containing two items:
327 # - a function which takes this attribute and encodes it as XML.
328 # - a function which takes XML and decocdes it into a value.
330 # other-config attributes are specified as a simple array of strings
333 VLAN_XML_TAG
= "vlan"
334 BOND_XML_TAG
= "bond"
335 NETWORK_XML_TAG
= "network"
337 ETHTOOL_OTHERCONFIG_ATTRS
= ['ethtool-%s' % x
for x
in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
339 PIF_ATTRS
= { 'uuid': (str_to_xml
,str_from_xml
),
340 'management': (bool_to_xml
,bool_from_xml
),
341 'network': (str_to_xml
,str_from_xml
),
342 'device': (str_to_xml
,str_from_xml
),
343 'bond_master_of': (lambda x
, p
, t
, v
: strlist_to_xml(x
, p
, 'bond_master_of', 'slave', v
),
344 lambda n
: strlist_from_xml(n
, 'bond_master_of', 'slave')),
345 'bond_slave_of': (str_to_xml
,str_from_xml
),
346 'VLAN': (str_to_xml
,str_from_xml
),
347 'VLAN_master_of': (str_to_xml
,str_from_xml
),
348 'VLAN_slave_of': (lambda x
, p
, t
, v
: strlist_to_xml(x
, p
, 'VLAN_slave_of', 'master', v
),
349 lambda n
: strlist_from_xml(n
, 'VLAN_slave_Of', 'master')),
350 'ip_configuration_mode': (str_to_xml
,str_from_xml
),
351 'IP': (str_to_xml
,str_from_xml
),
352 'netmask': (str_to_xml
,str_from_xml
),
353 'gateway': (str_to_xml
,str_from_xml
),
354 'DNS': (str_to_xml
,str_from_xml
),
355 'MAC': (str_to_xml
,str_from_xml
),
356 'other_config': (lambda x
, p
, t
, v
: otherconfig_to_xml(x
, p
, v
, PIF_OTHERCONFIG_ATTRS
),
357 lambda n
: otherconfig_from_xml(n
, PIF_OTHERCONFIG_ATTRS
)),
359 # Special case: We write the current value
360 # PIF.currently-attached to the cache but since it will
361 # not be valid when we come to use the cache later
362 # (i.e. after a reboot) we always read it as False.
363 'currently_attached': (bool_to_xml
, lambda n
: False),
366 PIF_OTHERCONFIG_ATTRS
= [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
367 [ 'bond-%s' % x
for x
in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
368 ETHTOOL_OTHERCONFIG_ATTRS
370 VLAN_ATTRS
= { 'uuid': (str_to_xml
,str_from_xml
),
371 'tagged_PIF': (str_to_xml
,str_from_xml
),
372 'untagged_PIF': (str_to_xml
,str_from_xml
),
375 BOND_ATTRS
= { 'uuid': (str_to_xml
,str_from_xml
),
376 'master': (str_to_xml
,str_from_xml
),
377 'slaves': (lambda x
, p
, t
, v
: strlist_to_xml(x
, p
, 'slaves', 'slave', v
),
378 lambda n
: strlist_from_xml(n
, 'slaves', 'slave')),
381 NETWORK_ATTRS
= { 'uuid': (str_to_xml
,str_from_xml
),
382 'bridge': (str_to_xml
,str_from_xml
),
383 'PIFs': (lambda x
, p
, t
, v
: strlist_to_xml(x
, p
, 'PIFs', 'PIF', v
),
384 lambda n
: strlist_from_xml(n
, 'PIFs', 'PIF')),
385 'other_config': (lambda x
, p
, t
, v
: otherconfig_to_xml(x
, p
, v
, NETWORK_OTHERCONFIG_ATTRS
),
386 lambda n
: otherconfig_from_xml(n
, NETWORK_OTHERCONFIG_ATTRS
)),
389 NETWORK_OTHERCONFIG_ATTRS
= [ 'mtu', 'static-routes' ] + ETHTOOL_OTHERCONFIG_ATTRS
391 class DatabaseCache(object):
392 def __read_xensource_inventory(self
):
393 filename
= "/etc/xensource-inventory"
394 f
= open(filename
, "r")
395 lines
= [x
.strip("\n") for x
in f
.readlines()]
398 defs
= [ (l
[:l
.find("=")], l
[(l
.find("=") + 1):]) for l
in lines
]
399 defs
= [ (a
, b
.strip("'")) for (a
,b
) in defs
]
402 def __pif_on_host(self
,pif
):
403 return self
.__pifs
.has_key(pif
)
405 def __get_pif_records_from_xapi(self
, session
, host
):
407 for (p
,rec
) in session
.xenapi
.PIF
.get_all_records().items():
408 if rec
['host'] != host
:
412 self
.__pifs
[p
][f
] = rec
[f
]
413 self
.__pifs
[p
]['other_config'] = {}
414 for f
in PIF_OTHERCONFIG_ATTRS
:
415 if not rec
['other_config'].has_key(f
): continue
416 self
.__pifs
[p
]['other_config'][f
] = rec
['other_config'][f
]
418 def __get_vlan_records_from_xapi(self
, session
):
420 for v
in session
.xenapi
.VLAN
.get_all():
421 rec
= session
.xenapi
.VLAN
.get_record(v
)
422 if not self
.__pif
_on
_host
(rec
['untagged_PIF']):
426 self
.__vlans
[v
][f
] = rec
[f
]
428 def __get_bond_records_from_xapi(self
, session
):
430 for b
in session
.xenapi
.Bond
.get_all():
431 rec
= session
.xenapi
.Bond
.get_record(b
)
432 if not self
.__pif
_on
_host
(rec
['master']):
436 self
.__bonds
[b
][f
] = rec
[f
]
438 def __get_network_records_from_xapi(self
, session
):
440 for n
in session
.xenapi
.network
.get_all():
441 rec
= session
.xenapi
.network
.get_record(n
)
442 self
.__networks
[n
] = {}
443 for f
in NETWORK_ATTRS
:
445 # drop PIFs on other hosts
446 self
.__networks
[n
][f
] = [p
for p
in rec
[f
] if self
.__pif
_on
_host
(p
)]
448 self
.__networks
[n
][f
] = rec
[f
]
449 self
.__networks
[n
]['other_config'] = {}
450 for f
in NETWORK_OTHERCONFIG_ATTRS
:
451 if not rec
['other_config'].has_key(f
): continue
452 self
.__networks
[n
]['other_config'][f
] = rec
['other_config'][f
]
454 def __to_xml(self
, xml
, parent
, key
, ref
, rec
, attrs
):
455 """Encode a database object as XML"""
456 e
= xml
.createElement(key
)
457 parent
.appendChild(e
)
459 e
.setAttribute('ref', ref
)
461 for n
,v
in rec
.items():
466 raise Error("Unknown attribute %s" % n
)
467 def __from_xml(self
, e
, attrs
):
468 """Decode a database object from XML"""
469 ref
= e
.attributes
['ref'].value
471 for n
in e
.childNodes
:
472 if n
.nodeName
in attrs
:
473 _
,h
= attrs
[n
.nodeName
]
474 rec
[n
.nodeName
] = h(n
)
477 def __init__(self
, session_ref
=None, cache_file
=None):
478 if session_ref
and cache_file
:
479 raise Error("can't specify session reference and cache file")
480 if cache_file
== None:
481 session
= XenAPI
.xapi_local()
484 log("No session ref given on command line, logging in.")
485 session
.xenapi
.login_with_password("root", "")
487 session
._session
= session_ref
491 inventory
= self
.__read
_xensource
_inventory
()
492 assert(inventory
.has_key('INSTALLATION_UUID'))
493 log("host uuid is %s" % inventory
['INSTALLATION_UUID'])
495 host
= session
.xenapi
.host
.get_by_uuid(inventory
['INSTALLATION_UUID'])
497 self
.__get
_pif
_records
_from
_xapi
(session
, host
)
499 self
.__get
_vlan
_records
_from
_xapi
(session
)
500 self
.__get
_bond
_records
_from
_xapi
(session
)
501 self
.__get
_network
_records
_from
_xapi
(session
)
504 session
.xenapi
.session
.logout()
506 log("Loading xapi database cache from %s" % cache_file
)
508 xml
= parseXML(cache_file
)
515 assert(len(xml
.childNodes
) == 1)
516 toplevel
= xml
.childNodes
[0]
518 assert(toplevel
.nodeName
== "xenserver-network-configuration")
520 for n
in toplevel
.childNodes
:
521 if n
.nodeName
== "#text":
523 elif n
.nodeName
== PIF_XML_TAG
:
524 (ref
,rec
) = self
.__from
_xml
(n
, PIF_ATTRS
)
525 self
.__pifs
[ref
] = rec
526 elif n
.nodeName
== BOND_XML_TAG
:
527 (ref
,rec
) = self
.__from
_xml
(n
, BOND_ATTRS
)
528 self
.__bonds
[ref
] = rec
529 elif n
.nodeName
== VLAN_XML_TAG
:
530 (ref
,rec
) = self
.__from
_xml
(n
, VLAN_ATTRS
)
531 self
.__vlans
[ref
] = rec
532 elif n
.nodeName
== NETWORK_XML_TAG
:
533 (ref
,rec
) = self
.__from
_xml
(n
, NETWORK_ATTRS
)
534 self
.__networks
[ref
] = rec
536 raise Error("Unknown XML element %s" % n
.nodeName
)
538 def save(self
, cache_file
):
540 xml
= getDOMImplementation().createDocument(
541 None, "xenserver-network-configuration", None)
542 for (ref
,rec
) in self
.__pifs
.items():
543 self
.__to
_xml
(xml
, xml
.documentElement
, PIF_XML_TAG
, ref
, rec
, PIF_ATTRS
)
544 for (ref
,rec
) in self
.__bonds
.items():
545 self
.__to
_xml
(xml
, xml
.documentElement
, BOND_XML_TAG
, ref
, rec
, BOND_ATTRS
)
546 for (ref
,rec
) in self
.__vlans
.items():
547 self
.__to
_xml
(xml
, xml
.documentElement
, VLAN_XML_TAG
, ref
, rec
, VLAN_ATTRS
)
548 for (ref
,rec
) in self
.__networks
.items():
549 self
.__to
_xml
(xml
, xml
.documentElement
, NETWORK_XML_TAG
, ref
, rec
,
552 f
= open(cache_file
, 'w')
553 f
.write(xml
.toprettyxml())
556 def get_pif_by_uuid(self
, uuid
):
557 pifs
= map(lambda (ref
,rec
): ref
,
558 filter(lambda (ref
,rec
): uuid
== rec
['uuid'],
559 self
.__pifs
.items()))
561 raise Error("Unknown PIF \"%s\"" % uuid
)
563 raise Error("Non-unique PIF \"%s\"" % uuid
)
567 def get_pifs_by_device(self
, device
):
568 return map(lambda (ref
,rec
): ref
,
569 filter(lambda (ref
,rec
): rec
['device'] == device
,
570 self
.__pifs
.items()))
572 def get_pif_by_bridge(self
, bridge
):
573 networks
= map(lambda (ref
,rec
): ref
,
574 filter(lambda (ref
,rec
): rec
['bridge'] == bridge
,
575 self
.__networks
.items()))
576 if len(networks
) == 0:
577 raise Error("No matching network \"%s\"")
580 for network
in networks
:
581 nwrec
= self
.get_network_record(network
)
582 for pif
in nwrec
['PIFs']:
583 pifrec
= self
.get_pif_record(pif
)
585 raise Error("Multiple PIFs on host for network %s" % (bridge
))
588 raise Error("No PIF on host for network %s" % (bridge
))
591 def get_pif_record(self
, pif
):
592 if self
.__pifs
.has_key(pif
):
593 return self
.__pifs
[pif
]
594 raise Error("Unknown PIF \"%s\" (get_pif_record)" % pif
)
595 def get_all_pifs(self
):
597 def pif_exists(self
, pif
):
598 return self
.__pifs
.has_key(pif
)
600 def get_management_pif(self
):
601 """ Returns the management pif on host
603 all
= self
.get_all_pifs()
605 pifrec
= self
.get_pif_record(pif
)
606 if pifrec
['management']: return pif
609 def get_network_record(self
, network
):
610 if self
.__networks
.has_key(network
):
611 return self
.__networks
[network
]
612 raise Error("Unknown network \"%s\"" % network
)
613 def get_all_networks(self
):
614 return self
.__networks
616 def get_bond_record(self
, bond
):
617 if self
.__bonds
.has_key(bond
):
618 return self
.__bonds
[bond
]
622 def get_vlan_record(self
, vlan
):
623 if self
.__vlans
.has_key(vlan
):
624 return self
.__vlans
[vlan
]
628 def bridge_name(pif
):
629 """Return the bridge name associated with pif, or None if network is bridgeless"""
630 pifrec
= db
.get_pif_record(pif
)
631 nwrec
= db
.get_network_record(pifrec
['network'])
634 # TODO: sanity check that nwrec['bridgeless'] != 'true'
635 return nwrec
['bridge']
637 # TODO: sanity check that nwrec['bridgeless'] == 'true'
640 def interface_name(pif
):
641 """Construct an interface name from the given PIF record."""
643 pifrec
= db
.get_pif_record(pif
)
645 if pifrec
['VLAN'] == '-1':
646 return pifrec
['device']
648 return "%(device)s.%(VLAN)s" % pifrec
650 def datapath_name(pif
):
651 """Return the OpenFlow datapath name associated with pif.
652 For a non-VLAN PIF, the datapath name is the bridge name.
653 For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
654 (xapi will create a datapath named with the bridge name even though we won't
659 pifrec
= db
.get_pif_record(pif
)
661 if pifrec
['VLAN'] == '-1':
662 return bridge_name(pif
)
664 return bridge_name(get_vlan_slave_of_pif(pif
))
667 """Return the the name of the network device that carries the
668 IP configuration (if any) associated with pif.
669 The ipdev name is the same as the bridge name.
672 pifrec
= db
.get_pif_record(pif
)
673 return bridge_name(pif
)
675 def get_physdev_pifs(pif
):
676 """Return the PIFs for the physical network device(s) associated with pif.
677 For a VLAN PIF, this is the VLAN slave's physical device PIF.
678 For a bond master PIF, these are the bond slave PIFs.
679 For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF.
681 pifrec
= db
.get_pif_record(pif
)
683 if pifrec
['VLAN'] != '-1':
684 return get_physdev_pifs(get_vlan_slave_of_pif(pif
))
685 elif len(pifrec
['bond_master_of']) != 0:
686 return get_bond_slaves_of_pif(pif
)
690 def get_physdev_names(pif
):
691 """Return the name(s) of the physical network device(s) associated with pif.
692 For a VLAN PIF, the physical devices are the VLAN slave's physical devices.
693 For a bond master PIF, the physical devices are the bond slaves.
694 For a non-VLAN, non-bond master PIF, the physical device is the PIF itself.
697 return [db
.get_pif_record(phys
)['device'] for phys
in get_physdev_pifs(pif
)]
699 def log_pif_action(action
, pif
):
700 pifrec
= db
.get_pif_record(pif
)
702 rec
['uuid'] = pifrec
['uuid']
703 rec
['ip_configuration_mode'] = pifrec
['ip_configuration_mode']
704 rec
['action'] = action
705 rec
['interface-name'] = interface_name(pif
)
706 rec
['message'] = "Bring %(action)s PIF %(uuid)s" % rec
707 log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % rec
)
709 def get_bond_masters_of_pif(pif
):
710 """Returns a list of PIFs which are bond masters of this PIF"""
712 pifrec
= db
.get_pif_record(pif
)
714 bso
= pifrec
['bond_slave_of']
716 # bond-slave-of is currently a single reference but in principle a
717 # PIF could be a member of several bonds which are not
718 # concurrently attached. Be robust to this possibility.
719 if not bso
or bso
== "OpaqueRef:NULL":
721 elif not type(bso
) == list:
724 bondrecs
= [db
.get_bond_record(bond
) for bond
in bso
]
725 bondrecs
= [rec
for rec
in bondrecs
if rec
]
727 return [bond
['master'] for bond
in bondrecs
]
729 def get_bond_slaves_of_pif(pif
):
730 """Returns a list of PIFs which make up the given bonded pif."""
732 pifrec
= db
.get_pif_record(pif
)
734 bmo
= pifrec
['bond_master_of']
736 raise Error("Bond-master-of contains too many elements")
741 bondrec
= db
.get_bond_record(bmo
[0])
743 raise Error("No bond record for bond master PIF")
745 return bondrec
['slaves']
747 def get_vlan_slave_of_pif(pif
):
748 """Find the PIF which is the VLAN slave of pif.
750 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
752 pifrec
= db
.get_pif_record(pif
)
754 vlan
= pifrec
['VLAN_master_of']
755 if not vlan
or vlan
== "OpaqueRef:NULL":
756 raise Error("PIF is not a VLAN master")
758 vlanrec
= db
.get_vlan_record(vlan
)
760 raise Error("No VLAN record found for PIF")
762 return vlanrec
['tagged_PIF']
764 def get_vlan_masters_of_pif(pif
):
765 """Returns a list of PIFs which are VLANs on top of the given pif."""
767 pifrec
= db
.get_pif_record(pif
)
768 vlans
= [db
.get_vlan_record(v
) for v
in pifrec
['VLAN_slave_of']]
769 return [v
['untagged_PIF'] for v
in vlans
if v
and db
.pif_exists(v
['untagged_PIF'])]
771 def interface_deconfigure_commands(interface
):
772 # The use of [!0-9] keeps an interface of 'eth0' from matching
773 # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
775 return ['--del-match=bridge.*.port=%s' % interface
,
776 '--del-match=bonding.%s.[!0-9]*' % interface
,
777 '--del-match=bonding.*.slave=%s' % interface
,
778 '--del-match=vlan.%s.[!0-9]*' % interface
,
779 '--del-match=port.%s.[!0-9]*' % interface
,
780 '--del-match=iface.%s.[!0-9]*' % interface
]
782 def run_command(command
):
783 log("Running command: " + ' '.join(command
))
784 if os
.spawnl(os
.P_WAIT
, command
[0], *command
) != 0:
785 log("Command failed: " + ' '.join(command
))
789 def rename_netdev(old_name
, new_name
):
790 log("Changing the name of %s to %s" % (old_name
, new_name
))
791 run_command(['/sbin/ifconfig', old_name
, 'down'])
792 if not run_command(['/sbin/ip', 'link', 'set', old_name
,
794 raise Error("Could not rename %s to %s" % (old_name
, new_name
))
796 # Check whether 'pif' exists and has the correct MAC.
797 # If not, try to find a device with the correct MAC and rename it.
798 # 'already_renamed' is used to avoid infinite recursion.
799 def remap_pif(pif
, already_renamed
=[]):
800 pifrec
= db
.get_pif_record(pif
)
801 device
= pifrec
['device']
804 # Is there a network device named 'device' at all?
805 device_exists
= interface_exists(device
)
807 # Yes. Does it have MAC 'mac'?
808 found_mac
= get_netdev_mac(device
)
809 if found_mac
and mac
.lower() == found_mac
.lower():
810 # Yes, everything checks out the way we want. Nothing to do.
813 log("No network device %s" % device
)
815 # What device has MAC 'mac'?
816 cur_device
= get_netdev_by_mac(mac
)
818 log("No network device has MAC %s" % mac
)
821 # First rename 'device', if it exists, to get it out of the way
822 # for 'cur_device' to replace it.
824 rename_netdev(device
, "dev%d" % random
.getrandbits(24))
826 # Rename 'cur_device' to 'device'.
827 rename_netdev(cur_device
, device
)
829 def read_first_line_of_file(name
):
832 file = open(name
, 'r')
833 return file.readline().rstrip('\n')
838 def down_netdev(interface
, deconfigure
=True):
839 if not interface_exists(interface
):
840 log("down_netdev: interface %s does not exist, ignoring" % interface
)
844 pidfile_name
= '/var/run/dhclient-%s.pid' % interface
846 os
.kill(int(read_first_line_of_file(pidfile_name
)), signal
.SIGTERM
)
850 # Remove dhclient pidfile.
852 os
.remove(pidfile_name
)
856 run_command(["/sbin/ifconfig", interface
, '0.0.0.0'])
858 run_command(["/sbin/ifconfig", interface
, 'down'])
860 def up_netdev(interface
):
861 run_command(["/sbin/ifconfig", interface
, 'up'])
863 def find_distinguished_pifs(pif
):
864 """Returns the PIFs on host that own DNS and the default route.
865 The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
866 The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
868 Note: we prune out the bond master pif (if it exists).
869 This is because when we are called to bring up an interface with a bond master, it is implicit that
870 we should bring down that master."""
872 pifrec
= db
.get_pif_record(pif
)
874 pifs
= [ __pif
for __pif
in db
.get_all_pifs() if
875 (not __pif
in get_bond_masters_of_pif(pif
)) ]
878 defaultroute_pif
= None
880 # loop through all the pifs on this host looking for one with
881 # other-config:peerdns = true, and one with
882 # other-config:default-route=true
884 __pifrec
= db
.get_pif_record(__pif
)
885 __oc
= __pifrec
['other_config']
886 if __oc
.has_key('peerdns') and __oc
['peerdns'] == 'true':
887 if peerdns_pif
== None:
890 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
891 (db
.get_pif_record(peerdns_pif
)['device'], __pifrec
['device']))
892 if __oc
.has_key('defaultroute') and __oc
['defaultroute'] == 'true':
893 if defaultroute_pif
== None:
894 defaultroute_pif
= __pif
896 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
897 (db
.get_pif_record(defaultroute_pif
)['device'], __pifrec
['device']))
899 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
900 if peerdns_pif
== None:
901 peerdns_pif
= management_pif
902 if defaultroute_pif
== None:
903 defaultroute_pif
= management_pif
905 return peerdns_pif
, defaultroute_pif
907 def run_ethtool(device
, oc
):
908 # Run "ethtool -s" if there are any settings.
910 if oc
.has_key('ethtool-speed'):
911 val
= oc
['ethtool-speed']
912 if val
in ["10", "100", "1000"]:
913 settings
+= ['speed', val
]
915 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val
)
916 if oc
.has_key('ethtool-duplex'):
917 val
= oc
['ethtool-duplex']
918 if val
in ["10", "100", "1000"]:
919 settings
+= ['duplex', 'val']
921 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val
)
922 if oc
.has_key('ethtool-autoneg'):
923 val
= oc
['ethtool-autoneg']
924 if val
in ["true", "on"]:
925 settings
+= ['autoneg', 'on']
926 elif val
in ["false", "off"]:
927 settings
+= ['autoneg', 'off']
929 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val
)
931 run_command(['/sbin/ethtool', '-s', device
] + settings
)
933 # Run "ethtool -K" if there are any offload settings.
935 for opt
in ("rx", "tx", "sg", "tso", "ufo", "gso"):
936 if oc
.has_key("ethtool-" + opt
):
937 val
= oc
["ethtool-" + opt
]
938 if val
in ["true", "on"]:
939 offload
+= [opt
, 'on']
940 elif val
in ["false", "off"]:
941 offload
+= [opt
, 'off']
943 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt
, val
))
945 run_command(['/sbin/ethtool', '-K', device
] + offload
)
948 if oc
.has_key('mtu'):
950 int(oc
['mtu']) # Check that the value is an integer
951 return ['mtu', oc
['mtu']]
952 except ValueError, x
:
953 log("Invalid value for mtu = %s" % mtu
)
956 def configure_local_port(pif
):
957 pifrec
= db
.get_pif_record(pif
)
958 datapath
= datapath_name(pif
)
959 ipdev
= ipdev_name(pif
)
961 nw
= pifrec
['network']
962 nwrec
= db
.get_network_record(nw
)
964 pif_oc
= pifrec
['other_config']
965 nw_oc
= nwrec
['other_config']
967 # IP (except DHCP) and MTU.
968 ifconfig_argv
= ['/sbin/ifconfig', ipdev
, 'up']
970 if pifrec
['ip_configuration_mode'] == "DHCP":
972 elif pifrec
['ip_configuration_mode'] == "Static":
973 ifconfig_argv
+= [pifrec
['IP']]
974 ifconfig_argv
+= ['netmask', pifrec
['netmask']]
975 gateway
= pifrec
['gateway']
976 elif pifrec
['ip_configuration_mode'] == "None":
980 raise Error("Unknown IP-configuration-mode %s" % pifrec
['ip_configuration_mode'])
981 ifconfig_argv
+= mtu_setting(nw_oc
)
982 run_command(ifconfig_argv
)
984 (peerdns_pif
, defaultroute_pif
) = find_distinguished_pifs(pif
)
987 if peerdns_pif
== pif
:
988 f
= ConfigurationFile('resolv.conf', "/etc")
989 if pif_oc
.has_key('domain'):
990 f
.write("search %s\n" % pif_oc
['domain'])
991 for dns
in pifrec
['DNS'].split(","):
992 f
.write("nameserver %s\n" % dns
)
998 if defaultroute_pif
== pif
and gateway
!= '':
999 run_command(['/sbin/ip', 'route', 'replace', 'default',
1000 'via', gateway
, 'dev', ipdev
])
1001 if nw_oc
.has_key('static-routes'):
1002 for line
in nw_oc
['static-routes'].split(','):
1003 network
, masklen
, gateway
= line
.split('/')
1004 run_command(['/sbin/ip', 'route', 'add',
1005 '%s/%s' % (network
, masklen
), 'via', gateway
,
1009 run_ethtool(ipdev
, nw_oc
)
1012 if pifrec
['ip_configuration_mode'] == "DHCP":
1014 print "Determining IP information for %s..." % ipdev
,
1015 argv
= ['/sbin/dhclient', '-q',
1016 '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev
,
1017 '-pf', '/var/run/dhclient-%s.pid' % ipdev
,
1019 if run_command(argv
):
1024 def configure_physdev(pif
):
1025 pifrec
= db
.get_pif_record(pif
)
1026 device
= pifrec
['device']
1027 oc
= pifrec
['other_config']
1029 run_command(['/sbin/ifconfig', device
, 'up'] + mtu_setting(oc
))
1030 run_ethtool(device
, oc
)
1032 def modify_config(commands
):
1033 run_command(['/usr/bin/ovs-cfg-mod', '-vANY:console:emer',
1034 '-F', '/etc/ovs-vswitchd.conf']
1035 + commands
+ ['-c'])
1036 run_command(['/sbin/service', 'vswitch', 'reload'])
1038 def is_bond_pif(pif
):
1039 pifrec
= db
.get_pif_record(pif
)
1040 return len(pifrec
['bond_master_of']) != 0
1042 def configure_bond(pif
):
1043 pifrec
= db
.get_pif_record(pif
)
1044 interface
= interface_name(pif
)
1045 ipdev
= ipdev_name(pif
)
1046 datapath
= datapath_name(pif
)
1047 physdev_names
= get_physdev_names(pif
)
1049 argv
= ['--del-match=bonding.%s.[!0-9]*' % interface
]
1050 argv
+= ["--add=bonding.%s.slave=%s" % (interface
, slave
)
1051 for slave
in physdev_names
]
1052 argv
+= ['--add=bonding.%s.fake-iface=true' % interface
]
1054 if pifrec
['MAC'] != "":
1055 argv
+= ['--add=port.%s.mac=%s' % (interface
, pifrec
['MAC'])]
1059 "mode": "balance-slb",
1065 # override defaults with values from other-config whose keys
1066 # being with "bond-"
1067 oc
= pifrec
['other_config']
1068 overrides
= filter(lambda (key
,val
):
1069 key
.startswith("bond-"), oc
.items())
1070 overrides
= map(lambda (key
,val
): (key
[5:], val
), overrides
)
1071 bond_options
.update(overrides
)
1072 for (name
,val
) in bond_options
.items():
1073 argv
+= ["--add=bonding.%s.%s=%s" % (interface
, name
, val
)]
1077 pifrec
= db
.get_pif_record(pif
)
1079 bridge
= bridge_name(pif
)
1080 interface
= interface_name(pif
)
1081 ipdev
= ipdev_name(pif
)
1082 datapath
= datapath_name(pif
)
1083 physdev_pifs
= get_physdev_pifs(pif
)
1084 physdev_names
= get_physdev_names(pif
)
1086 if pifrec
['VLAN'] != '-1':
1087 vlan_slave
= get_vlan_slave_of_pif(pif
)
1088 if vlan_slave
and is_bond_pif(vlan_slave
):
1089 bond_master
= vlan_slave
1090 elif is_bond_pif(pif
):
1095 bond_slaves
= get_bond_slaves_of_pif(bond_master
)
1098 bond_masters
= get_bond_masters_of_pif(pif
)
1100 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1101 # files up-to-date, even though we don't use them or need them.
1102 f
= configure_pif(pif
)
1103 mode
= pifrec
['ip_configuration_mode']
1105 log("Configuring %s using %s configuration" % (bridge
, mode
))
1106 br
= open_network_ifcfg(pif
)
1107 configure_network(pif
, br
)
1111 log("Configuring %s using %s configuration" % (interface
, mode
))
1112 configure_network(pif
, f
)
1114 for master
in bond_masters
:
1115 master_bridge
= bridge_name(master
)
1116 removed
= unconfigure_pif(master
)
1117 f
.attach_child(removed
)
1119 removed
= open_network_ifcfg(master
)
1120 log("Unlinking stale file %s" % removed
.path())
1122 f
.attach_child(removed
)
1124 # /etc/xensource/scripts/vif needs to know where to add VIFs.
1126 if not os
.path
.exists(vswitch_state_dir
):
1127 os
.mkdir(vswitch_state_dir
)
1128 br
= ConfigurationFile("br-%s" % bridge
, vswitch_state_dir
)
1129 br
.write("VLAN_SLAVE=%s\n" % datapath
)
1130 br
.write("VLAN_VID=%s\n" % pifrec
['VLAN'])
1134 # Update all configuration files (both ours and Centos's).
1138 # Check the MAC address of each network device and remap if
1139 # necessary to make names match our expectations.
1140 for physdev_pif
in physdev_pifs
:
1141 remap_pif(physdev_pif
)
1143 # "ifconfig down" the network device and delete its IP address, etc.
1145 for physdev_name
in physdev_names
:
1146 down_netdev(physdev_name
)
1148 # If we are bringing up a bond, remove IP addresses from the
1149 # slaves (because we are implicitly being asked to take them down).
1151 # Conversely, if we are bringing up an interface that has bond
1152 # masters, remove IP addresses from the bond master (because we
1153 # are implicitly being asked to take it down).
1154 for bond_pif
in bond_slaves
+ bond_masters
:
1155 run_command(["/sbin/ifconfig", ipdev_name(bond_pif
), '0.0.0.0'])
1157 # Remove all keys related to pif and any bond masters linked to PIF.
1158 del_ports
= [ipdev
] + physdev_names
+ bond_masters
1159 if vlan_slave
and bond_master
:
1160 del_ports
+= [interface_name(bond_master
)]
1162 # What ports do we need to add to the datapath?
1164 # We definitely need the ipdev, and ordinarily we want the
1165 # physical devices too, but for bonds we need the bond as bridge
1167 add_ports
= [ipdev
, datapath
]
1169 add_ports
+= physdev_names
1171 add_ports
+= [interface_name(bond_master
)]
1173 # What ports do we need to delete?
1175 # - All the ports that we add, to avoid duplication and to drop
1176 # them from another datapath in case they're misassigned.
1178 # - The physical devices, since they will either be in add_ports
1179 # or added to the bonding device (see below).
1181 # - The bond masters for pif. (Ordinarily pif shouldn't have any
1182 # bond masters. If it does then interface-reconfigure is
1183 # implicitly being asked to take them down.)
1184 del_ports
= add_ports
+ physdev_names
+ bond_masters
1186 # What networks does this datapath carry?
1188 # - The network corresponding to the datapath's PIF.
1190 # - The networks corresponding to any VLANs attached to the
1193 for nwpif
in db
.get_pifs_by_device(pifrec
['device']):
1194 net
= db
.get_pif_record(nwpif
)['network']
1195 network_uuids
+= [db
.get_network_record(net
)['uuid']]
1197 # Bring up bond slaves early, because ovs-vswitchd initially
1198 # enables or disables bond slaves based on whether carrier is
1199 # detected when they are added, and a network device that is down
1200 # always reports "no carrier".
1201 bond_slave_physdev_pifs
= []
1202 for slave
in bond_slaves
:
1203 bond_slave_physdev_pifs
+= get_physdev_pifs(slave
)
1204 for slave_physdev_pif
in set(bond_slave_physdev_pifs
):
1205 configure_physdev(slave_physdev_pif
)
1207 # Now modify the ovs-vswitchd config file.
1209 for port
in set(del_ports
):
1210 argv
+= interface_deconfigure_commands(port
)
1211 for port
in set(add_ports
):
1212 argv
+= ['--add=bridge.%s.port=%s' % (datapath
, port
)]
1214 argv
+= ['--add=vlan.%s.tag=%s' % (ipdev
, pifrec
['VLAN'])]
1215 argv
+= ['--add=iface.%s.internal=true' % (ipdev
)]
1217 # xapi creates a bridge by the name of the ipdev and requires
1218 # that the IP address will be on it. We need to delete this
1219 # bridge because we need that device to be a member of our
1221 argv
+= ['--del-match=bridge.%s.[!0-9]*' % ipdev
]
1223 # xapi insists that its attempts to create the bridge succeed,
1224 # so force that to happen.
1225 argv
+= ['--add=iface.%s.fake-bridge=true' % (ipdev
)]
1228 os
.unlink("%s/br-%s" % (vswitch_state_dir
, bridge
))
1231 argv
+= ['--del-match=bridge.%s.xs-network-uuids=*' % datapath
]
1232 argv
+= ['--add=bridge.%s.xs-network-uuids=%s' % (datapath
, uuid
)
1233 for uuid
in set(network_uuids
)]
1235 argv
+= configure_bond(bond_master
)
1238 # Bring up VLAN slave, plus physical devices other than bond
1239 # slaves (which we brought up earlier).
1240 # XXX need to bring up bond itself.
1241 # XXX need to set MAC address on fake bridge
1243 up_netdev(ipdev_name(vlan_slave
))
1244 for physdev_pif
in set(physdev_pifs
) - set(bond_slave_physdev_pifs
):
1245 configure_physdev(physdev_pif
)
1247 # Configure network device for local port.
1248 configure_local_port(pif
)
1250 # Update /etc/issue (which contains the IP address of the management interface)
1251 os
.system("/sbin/update-issue")
1254 # There seems to be a race somewhere: without this sleep, using
1255 # XenCenter to create a bond that becomes the management interface
1256 # fails with "The underlying connection was closed: A connection that
1257 # was expected to be kept alive was closed by the server." on every
1258 # second or third try, even though /var/log/messages doesn't show
1261 # The race is probably present even without vswitch, but bringing up a
1262 # bond without vswitch involves a built-in pause of 10 seconds or more
1263 # to wait for the bond to transition from learning to forwarding state.
1266 def action_down(pif
):
1267 rec
= db
.get_pif_record(pif
)
1268 interface
= interface_name(pif
)
1269 bridge
= bridge_name(pif
)
1270 ipdev
= ipdev_name(pif
)
1272 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1273 # files up-to-date, even though we don't use them or need them.
1274 f
= unconfigure_pif(pif
)
1276 br
= open_network_ifcfg(pif
)
1277 log("Unlinking stale file %s" % br
.path())
1284 log("action_down failed to apply changes: %s" % e
.msg
)
1289 if rec
['VLAN'] != '-1':
1290 # Get rid of the VLAN device itself.
1292 argv
+= interface_deconfigure_commands(ipdev
)
1294 # If the VLAN's slave is attached, stop here.
1295 slave
= get_vlan_slave_of_pif(pif
)
1296 if db
.get_pif_record(slave
)['currently_attached']:
1297 log("VLAN slave is currently attached")
1301 # If the VLAN's slave has other VLANs that are attached, stop here.
1302 masters
= get_vlan_masters_of_pif(slave
)
1304 if m
!= pif
and db
.get_pif_record(m
)['currently_attached']:
1305 log("VLAN slave has other master %s" % interface_naem(m
))
1309 # Otherwise, take down the VLAN's slave too.
1310 log("No more masters, bring down vlan slave %s" % interface_name(slave
))
1313 # Stop here if this PIF has attached VLAN masters.
1314 vlan_masters
= get_vlan_masters_of_pif(pif
)
1315 log("VLAN masters of %s - %s" % (rec
['device'], [interface_name(m
) for m
in vlan_masters
]))
1316 for m
in vlan_masters
:
1317 if db
.get_pif_record(m
)['currently_attached']:
1318 # XXX remove IP address
1319 log("Leaving %s up due to currently attached VLAN master %s" % (interface
, interface_name(m
)))
1322 # pif is now either a bond or a physical device which needs to be
1323 # brought down. pif might have changed so re-check all its attributes.
1324 rec
= db
.get_pif_record(pif
)
1325 interface
= interface_name(pif
)
1326 bridge
= bridge_name(pif
)
1327 ipdev
= ipdev_name(pif
)
1330 bond_slaves
= get_bond_slaves_of_pif(pif
)
1331 log("bond slaves of %s - %s" % (rec
['device'], [interface_name(s
) for s
in bond_slaves
]))
1332 for slave
in bond_slaves
:
1333 slave_interface
= interface_name(slave
)
1334 log("bring down bond slave %s" % slave_interface
)
1335 argv
+= interface_deconfigure_commands(slave_interface
)
1336 down_netdev(slave_interface
)
1338 argv
+= interface_deconfigure_commands(ipdev
)
1341 argv
+= ['--del-match', 'bridge.%s.*' % datapath_name(pif
)]
1342 argv
+= ['--del-match', 'bonding.%s.[!0-9]*' % interface
]
1345 def action_rewrite(pif
):
1346 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1347 # files up-to-date, even though we don't use them or need them.
1348 pifrec
= db
.get_pif_record(pif
)
1349 f
= configure_pif(pif
)
1350 interface
= interface_name(pif
)
1351 bridge
= bridge_name(pif
)
1352 mode
= pifrec
['ip_configuration_mode']
1354 log("Configuring %s using %s configuration" % (bridge
, mode
))
1355 br
= open_network_ifcfg(pif
)
1356 configure_network(pif
, br
)
1360 log("Configuring %s using %s configuration" % (interface
, mode
))
1361 configure_network(pif
, f
)
1367 log("failed to apply changes: %s" % e
.msg
)
1371 # We have no code of our own to run here.
1374 def action_force_rewrite(interface
, config
):
1375 raise Error("Force rewrite is not implemented yet.")
1377 def main(argv
=None):
1378 global output_directory
, management_pif
1384 force_interface
= None
1385 force_management
= False
1393 longops
= [ "output-directory=",
1394 "pif=", "pif-uuid=",
1399 "device=", "mode=", "ip=", "netmask=", "gateway=",
1401 arglist
, args
= getopt
.gnu_getopt(argv
[1:], shortops
, longops
)
1402 except getopt
.GetoptError
, msg
:
1405 force_rewrite_config
= {}
1408 if o
== "--output-directory":
1409 output_directory
= a
1412 elif o
== "--pif-uuid":
1414 elif o
== "--session":
1416 elif o
== "--force-interface" or o
== "--force":
1418 elif o
== "--management":
1419 force_management
= True
1420 elif o
in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1421 force_rewrite_config
[o
[2:]] = a
1422 elif o
== "-h" or o
== "--help":
1423 print __doc__
% {'command-name': os
.path
.basename(argv
[0])}
1426 if not debug_mode():
1427 syslog
.openlog(os
.path
.basename(argv
[0]))
1428 log("Called as " + str.join(" ", argv
))
1430 raise Usage("Required option <action> not present")
1432 raise Usage("Too many arguments")
1436 if not action
in ["up", "down", "rewrite", "rewrite-configuration"]:
1437 raise Usage("Unknown action \"%s\"" % action
)
1439 # backwards compatibility
1440 if action
== "rewrite-configuration": action
= "rewrite"
1442 if output_directory
and ( session
or pif
):
1443 raise Usage("--session/--pif cannot be used with --output-directory")
1444 if ( session
or pif
) and pif_uuid
:
1445 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1446 if ( session
and not pif
) or ( not session
and pif
):
1447 raise Usage("--session and --pif must be used together.")
1448 if force_interface
and ( session
or pif
or pif_uuid
):
1449 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1450 if force_interface
== "all" and action
!= "down":
1451 raise Usage("\"--force all\" only valid for down action")
1452 if len(force_rewrite_config
) and not (force_interface
and action
== "rewrite"):
1453 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1457 log("Force interface %s %s" % (force_interface
, action
))
1459 if action
== "rewrite":
1460 action_force_rewrite(force_interface
, force_rewrite_config
)
1461 elif action
in ["up", "down"]:
1462 if action
== "down" and force_interface
== "all":
1463 raise Error("Force all interfaces down not implemented yet")
1465 db
= DatabaseCache(cache_file
=dbcache_file
)
1466 pif
= db
.get_pif_by_bridge(force_interface
)
1467 management_pif
= db
.get_management_pif()
1471 elif action
== "down":
1474 raise Error("Unknown action %s" % action
)
1476 db
= DatabaseCache(session_ref
=session
)
1479 pif
= db
.get_pif_by_uuid(pif_uuid
)
1481 if action
== "rewrite" and not pif
:
1485 raise Usage("No PIF given")
1487 if force_management
:
1488 # pif is going to be the management pif
1489 management_pif
= pif
1491 # pif is not going to be the management pif.
1492 # Search DB cache for pif on same host with management=true
1493 pifrec
= db
.get_pif_record(pif
)
1494 management_pif
= db
.get_management_pif()
1496 log_pif_action(action
, pif
)
1498 if not check_allowed(pif
):
1503 elif action
== "down":
1505 elif action
== "rewrite":
1508 raise Error("Unknown action %s" % action
)
1511 db
.save(dbcache_file
)
1514 print >>sys
.stderr
, err
.msg
1515 print >>sys
.stderr
, "For help use --help."
1523 # The following code allows interface-reconfigure to keep Centos
1524 # network configuration files up-to-date, even though the vswitch
1525 # never uses them. In turn, that means that "rpm -e vswitch" does not
1526 # have to update any configuration files.
1528 def configure_ethtool(oc
, f
):
1529 # Options for "ethtool -s"
1531 setting_opts
= ["autoneg", "speed", "duplex"]
1532 # Options for "ethtool -K"
1534 offload_opts
= ["rx", "tx", "sg", "tso", "ufo", "gso"]
1536 for opt
in [opt
for opt
in setting_opts
+ offload_opts
if oc
.has_key("ethtool-" + opt
)]:
1537 val
= oc
["ethtool-" + opt
]
1539 if opt
in ["speed"]:
1540 if val
in ["10", "100", "1000"]:
1541 val
= "speed " + val
1543 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val
)
1545 elif opt
in ["duplex"]:
1546 if val
in ["half", "full"]:
1547 val
= "duplex " + val
1549 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val
)
1551 elif opt
in ["autoneg"] + offload_opts
:
1552 if val
in ["true", "on"]:
1554 elif val
in ["false", "off"]:
1557 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt
, val
))
1560 if opt
in setting_opts
:
1561 if val
and settings
:
1562 settings
= settings
+ " " + val
1565 elif opt
in offload_opts
:
1567 offload
= offload
+ " " + val
1572 f
.write("ETHTOOL_OPTS=\"%s\"\n" % settings
)
1574 f
.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload
)
1576 def configure_mtu(oc
, f
):
1577 if not oc
.has_key('mtu'):
1581 mtu
= int(oc
['mtu'])
1582 f
.write("MTU=%d\n" % mtu
)
1583 except ValueError, x
:
1584 log("Invalid value for mtu = %s" % mtu
)
1586 def configure_static_routes(interface
, oc
, f
):
1587 """Open a route-<interface> file for static routes.
1589 Opens the static routes configuration file for interface and writes one
1590 line for each route specified in the network's other config "static-routes" value.
1592 interface ( RO): xenbr1
1593 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1595 Then route-xenbr1 should be
1596 172.16.0.0/15 via 192.168.0.3 dev xenbr1
1597 172.18.0.0/16 via 192.168.0.4 dev xenbr1
1599 fname
= "route-%s" % interface
1600 if oc
.has_key('static-routes'):
1601 # The key is present - extract comma seperates entries
1602 lines
= oc
['static-routes'].split(',')
1604 # The key is not present, i.e. there are no static routes
1607 child
= ConfigurationFile(fname
)
1608 child
.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1609 (os
.path
.basename(child
.path()), os
.path
.basename(sys
.argv
[0])))
1613 network
, masklen
, gateway
= l
.split('/')
1614 child
.write("%s/%s via %s dev %s\n" % (network
, masklen
, gateway
, interface
))
1616 f
.attach_child(child
)
1619 except ValueError, e
:
1620 log("Error in other-config['static-routes'] format for network %s: %s" % (interface
, e
))
1622 def __open_ifcfg(interface
):
1623 """Open a network interface configuration file.
1625 Opens the configuration file for interface, writes a header and
1626 common options and returns the file object.
1628 fname
= "ifcfg-%s" % interface
1629 f
= ConfigurationFile(fname
)
1631 f
.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1632 (os
.path
.basename(f
.path()), os
.path
.basename(sys
.argv
[0])))
1633 f
.write("XEMANAGED=yes\n")
1634 f
.write("DEVICE=%s\n" % interface
)
1635 f
.write("ONBOOT=no\n")
1639 def open_network_ifcfg(pif
):
1640 bridge
= bridge_name(pif
)
1641 interface
= interface_name(pif
)
1643 return __open_ifcfg(bridge
)
1645 return __open_ifcfg(interface
)
1648 def open_pif_ifcfg(pif
):
1649 pifrec
= db
.get_pif_record(pif
)
1651 log("Configuring %s (%s)" % (interface_name(pif
), pifrec
['MAC']))
1653 f
= __open_ifcfg(interface_name(pif
))
1655 if pifrec
.has_key('other_config'):
1656 configure_ethtool(pifrec
['other_config'], f
)
1657 configure_mtu(pifrec
['other_config'], f
)
1661 def configure_network(pif
, f
):
1662 """Write the configuration file for a network.
1664 Writes configuration derived from the network object into the relevant
1665 ifcfg file. The configuration file is passed in, but if the network is
1666 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1668 This routine may also write ifcfg files of the networks corresponding to other PIFs
1669 in order to maintain consistency.
1672 pif: Opaque_ref of pif
1673 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1676 pifrec
= db
.get_pif_record(pif
)
1677 nw
= pifrec
['network']
1678 nwrec
= db
.get_network_record(nw
)
1680 bridge
= bridge_name(pif
)
1681 interface
= interface_name(pif
)
1687 if nwrec
.has_key('other_config'):
1688 configure_ethtool(nwrec
['other_config'], f
)
1689 configure_mtu(nwrec
['other_config'], f
)
1690 configure_static_routes(device
, nwrec
['other_config'], f
)
1693 if pifrec
.has_key('other_config'):
1694 oc
= pifrec
['other_config']
1696 if device
== bridge
:
1697 f
.write("TYPE=Bridge\n")
1698 f
.write("DELAY=0\n")
1699 f
.write("STP=off\n")
1700 f
.write("PIFDEV=%s\n" % interface_name(pif
))
1702 if pifrec
['ip_configuration_mode'] == "DHCP":
1703 f
.write("BOOTPROTO=dhcp\n")
1704 f
.write("PERSISTENT_DHCLIENT=yes\n")
1705 elif pifrec
['ip_configuration_mode'] == "Static":
1706 f
.write("BOOTPROTO=none\n")
1707 f
.write("NETMASK=%(netmask)s\n" % pifrec
)
1708 f
.write("IPADDR=%(IP)s\n" % pifrec
)
1709 f
.write("GATEWAY=%(gateway)s\n" % pifrec
)
1710 elif pifrec
['ip_configuration_mode'] == "None":
1711 f
.write("BOOTPROTO=none\n")
1713 raise Error("Unknown ip-configuration-mode %s" % pifrec
['ip_configuration_mode'])
1715 if pifrec
.has_key('DNS') and pifrec
['DNS'] != "":
1716 ServerList
= pifrec
['DNS'].split(",")
1717 for i
in range(len(ServerList
)): f
.write("DNS%d=%s\n" % (i
+1, ServerList
[i
]))
1718 if oc
and oc
.has_key('domain'):
1719 f
.write("DOMAIN='%s'\n" % oc
['domain'].replace(',', ' '))
1721 # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
1722 # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
1723 # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
1725 # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1727 # Note: we prune out the bond master pif (if it exists).
1728 # This is because when we are called to bring up an interface with a bond master, it is implicit that
1729 # we should bring down that master.
1730 pifs_on_host
= [ __pif
for __pif
in db
.get_all_pifs() if
1731 not __pif
in get_bond_masters_of_pif(pif
) ]
1732 other_pifs_on_host
= [ __pif
for __pif
in pifs_on_host
if __pif
!= pif
]
1735 defaultroute_pif
= None
1737 # loop through all the pifs on this host looking for one with
1738 # other-config:peerdns = true, and one with
1739 # other-config:default-route=true
1740 for __pif
in pifs_on_host
:
1741 __pifrec
= db
.get_pif_record(__pif
)
1742 __oc
= __pifrec
['other_config']
1743 if __oc
.has_key('peerdns') and __oc
['peerdns'] == 'true':
1744 if peerdns_pif
== None:
1747 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1748 (db
.get_pif_record(peerdns_pif
)['device'], __pifrec
['device']))
1749 if __oc
.has_key('defaultroute') and __oc
['defaultroute'] == 'true':
1750 if defaultroute_pif
== None:
1751 defaultroute_pif
= __pif
1753 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1754 (db
.get_pif_record(defaultroute_pif
)['device'], __pifrec
['device']))
1756 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1757 if peerdns_pif
== None:
1758 peerdns_pif
= management_pif
1759 if defaultroute_pif
== None:
1760 defaultroute_pif
= management_pif
1762 # Update all the other network's ifcfg files and ensure consistency
1763 for __pif
in other_pifs_on_host
:
1764 __f
= open_network_ifcfg(__pif
)
1765 peerdns_line_wanted
= 'PEERDNS=%s\n' % ((__pif
== peerdns_pif
) and 'yes' or 'no')
1766 lines
= __f
.readlines()
1768 if not peerdns_line_wanted
in lines
:
1769 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1771 if not line
.lstrip().startswith('PEERDNS'):
1773 log("Setting %s in %s" % (peerdns_line_wanted
.strip(), __f
.path()))
1774 __f
.write(peerdns_line_wanted
)
1779 # There is no need to change this ifcfg file. So don't attach_child.
1782 # ... and for this pif too
1783 f
.write('PEERDNS=%s\n' % ((pif
== peerdns_pif
) and 'yes' or 'no'))
1786 fnetwork
= ConfigurationFile("network", "/etc/sysconfig")
1787 for line
in fnetwork
.readlines():
1788 if line
.lstrip().startswith('GATEWAY') :
1790 fnetwork
.write(line
)
1791 if defaultroute_pif
:
1792 gatewaydev
= bridge_name(defaultroute_pif
)
1794 gatewaydev
= interface_name(defaultroute_pif
)
1795 fnetwork
.write('GATEWAYDEV=%s\n' % gatewaydev
)
1797 f
.attach_child(fnetwork
)
1802 def configure_physical_interface(pif
):
1803 """Write the configuration for a physical interface.
1805 Writes the configuration file for the physical interface described by
1808 Returns the open file handle for the interface configuration file.
1811 pifrec
= db
.get_pif_record(pif
)
1813 f
= open_pif_ifcfg(pif
)
1815 f
.write("TYPE=Ethernet\n")
1816 f
.write("HWADDR=%(MAC)s\n" % pifrec
)
1820 def configure_bond_interface(pif
):
1821 """Write the configuration for a bond interface.
1823 Writes the configuration file for the bond interface described by
1824 the pif object. Handles writing the configuration for the slave
1827 Returns the open file handle for the bond interface configuration
1831 pifrec
= db
.get_pif_record(pif
)
1832 oc
= pifrec
['other_config']
1833 f
= open_pif_ifcfg(pif
)
1835 if pifrec
['MAC'] != "":
1836 f
.write("MACADDR=%s\n" % pifrec
['MAC'])
1838 for slave
in get_bond_slaves_of_pif(pif
):
1839 s
= configure_physical_interface(slave
)
1840 s
.write("MASTER=%(device)s\n" % pifrec
)
1841 s
.write("SLAVE=yes\n")
1845 # The bond option defaults
1847 "mode": "balance-slb",
1854 # override defaults with values from other-config whose keys being with "bond-"
1855 overrides
= filter(lambda (key
,val
): key
.startswith("bond-"), oc
.items())
1856 overrides
= map(lambda (key
,val
): (key
[5:], val
), overrides
)
1857 bond_options
.update(overrides
)
1859 # write the bond options to ifcfg-bondX
1860 f
.write('BONDING_OPTS="')
1861 for (name
,val
) in bond_options
.items():
1862 f
.write("%s=%s " % (name
,val
))
1866 def configure_vlan_interface(pif
):
1867 """Write the configuration for a VLAN interface.
1869 Writes the configuration file for the VLAN interface described by
1870 the pif object. Handles writing the configuration for the master
1871 interface if necessary.
1873 Returns the open file handle for the VLAN interface configuration
1877 slave
= configure_pif(get_vlan_slave_of_pif(pif
))
1880 f
= open_pif_ifcfg(pif
)
1881 f
.write("VLAN=yes\n")
1882 f
.attach_child(slave
)
1886 def configure_pif(pif
):
1887 """Write the configuration for a PIF object.
1889 Writes the configuration file the PIF and all dependent
1890 interfaces (bond slaves and VLAN masters etc).
1892 Returns the open file handle for the interface configuration file.
1895 pifrec
= db
.get_pif_record(pif
)
1897 if pifrec
['VLAN'] != '-1':
1898 f
= configure_vlan_interface(pif
)
1899 elif len(pifrec
['bond_master_of']) != 0:
1900 f
= configure_bond_interface(pif
)
1902 f
= configure_physical_interface(pif
)
1904 bridge
= bridge_name(pif
)
1906 f
.write("BRIDGE=%s\n" % bridge
)
1910 def unconfigure_pif(pif
):
1911 """Clear up the files created by configure_pif"""
1912 f
= open_pif_ifcfg(pif
)
1913 log("Unlinking stale file %s" % f
.path())
1917 if __name__
== "__main__":
1923 err
= traceback
.format_exception(*ex
)
1927 if not debug_mode():