3 # Copyright (c) 2008,2009 Citrix Systems, Inc. All rights reserved.
4 # Copyright (c) 2009 Nicira Networks.
8 %(command-name)s --session <SESSION-REF> --pif <PIF-REF> [up|down|rewrite]
9 %(command-name)s --force <BRIDGE> [up|down|rewrite <CONFIG>]
10 %(command-name)s --force all down
11 %(command-name)s init-dbcache
14 <CONFIG> = --device=<INTERFACE> --mode=dhcp
15 <CONFIG> = --device=<INTERFACE> --mode=static --ip=<IPADDR> --netmask=<NM> [--gateway=<GW>]
18 --session A session reference to use to access the xapi DB
19 --pif A PIF reference.
20 --force-interface An interface name. Mutually exclusive with --session/--pif.
22 Either both --session and --pif or just --pif-uuid.
24 <ACTION> is either "up" or "down" or "rewrite"
28 # Undocumented parameters for test & dev:
30 # --output-directory=<DIR> Write configuration to <DIR>. Also disables actually
31 # raising/lowering the interfaces
32 # --pif-uuid A PIF UUID, use instead of --session/--pif.
37 # 1. Every pif belongs to exactly one network
38 # 2. Every network has zero or one pifs
39 # 3. A network may have an associated bridge, allowing vifs to be attached
40 # 4. A network may be bridgeless (there's no point having a bridge over a storage pif)
43 import os
, sys
, getopt
, time
, signal
49 from xml
.dom
.minidom
import getDOMImplementation
50 from xml
.dom
.minidom
import parse
as parseXML
52 output_directory
= None
57 vswitch_state_dir
= "/var/lib/openvswitch/"
58 dbcache_file
= vswitch_state_dir
+ "dbcache"
60 class Usage(Exception):
61 def __init__(self
, msg
):
62 Exception.__init
__(self
)
65 class Error(Exception):
66 def __init__(self
, msg
):
67 Exception.__init
__(self
)
70 class ConfigurationFile(object):
71 """Write a file, tracking old and new versions.
73 Supports writing a new version of a file and applying and
74 reverting those changes.
77 __STATE
= {"OPEN":"OPEN",
78 "NOT-APPLIED":"NOT-APPLIED", "APPLIED":"APPLIED",
79 "REVERTED":"REVERTED", "COMMITTED": "COMMITTED"}
81 def __init__(self
, fname
, path
="/etc/sysconfig/network-scripts"):
83 self
.__state
= self
.__STATE
['OPEN']
88 dirname
= output_directory
92 self
.__path
= os
.path
.join(dirname
, fname
)
93 self
.__oldpath
= os
.path
.join(dirname
, "." + fname
+ ".xapi-old")
94 self
.__newpath
= os
.path
.join(dirname
, "." + fname
+ ".xapi-new")
97 self
.__f
= open(self
.__newpath
, "w")
99 def attach_child(self
, child
):
100 self
.__children
.append(child
)
107 return open(self
.path()).readlines()
111 def write(self
, args
):
112 if self
.__state
!= self
.__STATE
['OPEN']:
113 raise Error("Attempt to write to file in state %s" % self
.__state
)
117 if self
.__state
!= self
.__STATE
['OPEN']:
118 raise Error("Attempt to unlink file in state %s" % self
.__state
)
121 self
.__state
= self
.__STATE
['NOT-APPLIED']
124 if self
.__state
!= self
.__STATE
['OPEN']:
125 raise Error("Attempt to close file in state %s" % self
.__state
)
128 self
.__state
= self
.__STATE
['NOT-APPLIED']
131 if self
.__state
!= self
.__STATE
['NOT-APPLIED']:
132 raise Error("Attempt to compare file in state %s" % self
.__state
)
137 if self
.__state
!= self
.__STATE
['NOT-APPLIED']:
138 raise Error("Attempt to apply configuration from state %s" % self
.__state
)
140 for child
in self
.__children
:
143 log("Applying changes to %s configuration" % self
.__fname
)
145 # Remove previous backup.
146 if os
.access(self
.__oldpath
, os
.F_OK
):
147 os
.unlink(self
.__oldpath
)
149 # Save current configuration.
150 if os
.access(self
.__path
, os
.F_OK
):
151 os
.link(self
.__path
, self
.__oldpath
)
152 os
.unlink(self
.__path
)
154 # Apply new configuration.
155 assert(os
.path
.exists(self
.__newpath
))
156 if not self
.__unlink
:
157 os
.link(self
.__newpath
, self
.__path
)
159 pass # implicit unlink of original file
161 # Remove temporary file.
162 os
.unlink(self
.__newpath
)
164 self
.__state
= self
.__STATE
['APPLIED']
167 if self
.__state
!= self
.__STATE
['APPLIED']:
168 raise Error("Attempt to revert configuration from state %s" % self
.__state
)
170 for child
in self
.__children
:
173 log("Reverting changes to %s configuration" % self
.__fname
)
175 # Remove existing new configuration
176 if os
.access(self
.__newpath
, os
.F_OK
):
177 os
.unlink(self
.__newpath
)
179 # Revert new configuration.
180 if os
.access(self
.__path
, os
.F_OK
):
181 os
.link(self
.__path
, self
.__newpath
)
182 os
.unlink(self
.__path
)
184 # Revert to old configuration.
185 if os
.access(self
.__oldpath
, os
.F_OK
):
186 os
.link(self
.__oldpath
, self
.__path
)
187 os
.unlink(self
.__oldpath
)
189 # Leave .*.xapi-new as an aid to debugging.
191 self
.__state
= self
.__STATE
['REVERTED']
194 if self
.__state
!= self
.__STATE
['APPLIED']:
195 raise Error("Attempt to commit configuration from state %s" % self
.__state
)
197 for child
in self
.__children
:
200 log("Committing changes to %s configuration" % self
.__fname
)
202 if os
.access(self
.__oldpath
, os
.F_OK
):
203 os
.unlink(self
.__oldpath
)
204 if os
.access(self
.__newpath
, os
.F_OK
):
205 os
.unlink(self
.__newpath
)
207 self
.__state
= self
.__STATE
['COMMITTED']
210 return output_directory
is not None
214 print >>sys
.stderr
, s
218 def check_allowed(pif
):
219 pifrec
= db
.get_pif_record(pif
)
221 f
= open("/proc/ardence")
222 macline
= filter(lambda x
: x
.startswith("HWaddr:"), f
.readlines())
224 if len(macline
) == 1:
225 p
= re
.compile(".*\s%(MAC)s\s.*" % pifrec
, re
.IGNORECASE
)
226 if p
.match(macline
[0]):
227 log("Skipping PVS device %(device)s (%(MAC)s)" % pifrec
)
233 def interface_exists(i
):
234 return os
.path
.exists("/sys/class/net/" + i
)
236 def get_netdev_mac(device
):
238 return read_first_line_of_file("/sys/class/net/%s/address" % device
)
240 # Probably no such device.
243 def get_netdev_tx_queue_len(device
):
245 return int(read_first_line_of_file("/sys/class/net/%s/tx_queue_len"
248 # Probably no such device.
251 def get_netdev_by_mac(mac
):
252 for device
in os
.listdir("/sys/class/net"):
253 dev_mac
= get_netdev_mac(device
)
254 if (dev_mac
and mac
.lower() == dev_mac
.lower() and
255 get_netdev_tx_queue_len(device
)):
260 # Helper functions for encoding/decoding database attributes to/from XML.
262 def str_to_xml(xml
, parent
, tag
, val
):
263 e
= xml
.createElement(tag
)
264 parent
.appendChild(e
)
265 v
= xml
.createTextNode(val
)
268 def getText(nodelist
):
270 for node
in nodelist
:
271 if node
.nodeType
== node
.TEXT_NODE
:
274 return getText(n
.childNodes
).strip()
277 def bool_to_xml(xml
, parent
, tag
, val
):
279 str_to_xml(xml
, parent
, tag
, "True")
281 str_to_xml(xml
, parent
, tag
, "False")
282 def bool_from_xml(n
):
289 raise Error("Unknown boolean value %s" % s
);
291 def strlist_to_xml(xml
, parent
, ltag
, itag
, val
):
292 e
= xml
.createElement(ltag
)
293 parent
.appendChild(e
)
295 c
= xml
.createElement(itag
)
297 cv
= xml
.createTextNode(v
)
299 def strlist_from_xml(n
, ltag
, itag
):
301 for n
in n
.childNodes
:
302 if n
.nodeName
== itag
:
303 ret
.append(str_from_xml(n
))
306 def otherconfig_to_xml(xml
, parent
, val
, attrs
):
307 otherconfig
= xml
.createElement("other_config")
308 parent
.appendChild(otherconfig
)
309 for n
,v
in val
.items():
311 raise Error("Unknown other-config attribute: %s" % n
)
312 str_to_xml(xml
, otherconfig
, n
, v
)
313 def otherconfig_from_xml(n
, attrs
):
315 for n
in n
.childNodes
:
316 if n
.nodeName
in attrs
:
317 ret
[n
.nodeName
] = str_from_xml(n
)
321 # Definitions of the database objects (and their attributes) used by interface-reconfigure.
323 # Each object is defined by a dictionary mapping an attribute name in
324 # the xapi database to a tuple containing two items:
325 # - a function which takes this attribute and encodes it as XML.
326 # - a function which takes XML and decocdes it into a value.
328 # other-config attributes are specified as a simple array of strings
331 VLAN_XML_TAG
= "vlan"
332 BOND_XML_TAG
= "bond"
333 NETWORK_XML_TAG
= "network"
335 ETHTOOL_OTHERCONFIG_ATTRS
= ['ethtool-%s' % x
for x
in 'autoneg', 'speed', 'duplex', 'rx', 'tx', 'sg', 'tso', 'ufo', 'gso' ]
337 PIF_ATTRS
= { 'uuid': (str_to_xml
,str_from_xml
),
338 'management': (bool_to_xml
,bool_from_xml
),
339 'network': (str_to_xml
,str_from_xml
),
340 'device': (str_to_xml
,str_from_xml
),
341 'bond_master_of': (lambda x
, p
, t
, v
: strlist_to_xml(x
, p
, 'bond_master_of', 'slave', v
),
342 lambda n
: strlist_from_xml(n
, 'bond_master_of', 'slave')),
343 'bond_slave_of': (str_to_xml
,str_from_xml
),
344 'VLAN': (str_to_xml
,str_from_xml
),
345 'VLAN_master_of': (str_to_xml
,str_from_xml
),
346 'VLAN_slave_of': (lambda x
, p
, t
, v
: strlist_to_xml(x
, p
, 'VLAN_slave_of', 'master', v
),
347 lambda n
: strlist_from_xml(n
, 'VLAN_slave_Of', 'master')),
348 'ip_configuration_mode': (str_to_xml
,str_from_xml
),
349 'IP': (str_to_xml
,str_from_xml
),
350 'netmask': (str_to_xml
,str_from_xml
),
351 'gateway': (str_to_xml
,str_from_xml
),
352 'DNS': (str_to_xml
,str_from_xml
),
353 'MAC': (str_to_xml
,str_from_xml
),
354 'other_config': (lambda x
, p
, t
, v
: otherconfig_to_xml(x
, p
, v
, PIF_OTHERCONFIG_ATTRS
),
355 lambda n
: otherconfig_from_xml(n
, PIF_OTHERCONFIG_ATTRS
)),
357 # Special case: We write the current value
358 # PIF.currently-attached to the cache but since it will
359 # not be valid when we come to use the cache later
360 # (i.e. after a reboot) we always read it as False.
361 'currently_attached': (bool_to_xml
, lambda n
: False),
364 PIF_OTHERCONFIG_ATTRS
= [ 'domain', 'peerdns', 'defaultroute', 'mtu', 'static-routes' ] + \
365 [ 'bond-%s' % x
for x
in 'mode', 'miimon', 'downdelay', 'updelay', 'use_carrier' ] + \
366 ETHTOOL_OTHERCONFIG_ATTRS
368 VLAN_ATTRS
= { 'uuid': (str_to_xml
,str_from_xml
),
369 'tagged_PIF': (str_to_xml
,str_from_xml
),
370 'untagged_PIF': (str_to_xml
,str_from_xml
),
373 BOND_ATTRS
= { 'uuid': (str_to_xml
,str_from_xml
),
374 'master': (str_to_xml
,str_from_xml
),
375 'slaves': (lambda x
, p
, t
, v
: strlist_to_xml(x
, p
, 'slaves', 'slave', v
),
376 lambda n
: strlist_from_xml(n
, 'slaves', 'slave')),
379 NETWORK_ATTRS
= { 'uuid': (str_to_xml
,str_from_xml
),
380 'bridge': (str_to_xml
,str_from_xml
),
381 'PIFs': (lambda x
, p
, t
, v
: strlist_to_xml(x
, p
, 'PIFs', 'PIF', v
),
382 lambda n
: strlist_from_xml(n
, 'PIFs', 'PIF')),
383 'other_config': (lambda x
, p
, t
, v
: otherconfig_to_xml(x
, p
, v
, NETWORK_OTHERCONFIG_ATTRS
),
384 lambda n
: otherconfig_from_xml(n
, NETWORK_OTHERCONFIG_ATTRS
)),
387 NETWORK_OTHERCONFIG_ATTRS
= [ 'mtu', 'static-routes' ] + ETHTOOL_OTHERCONFIG_ATTRS
389 class DatabaseCache(object):
390 def __read_xensource_inventory(self
):
391 filename
= "/etc/xensource-inventory"
392 f
= open(filename
, "r")
393 lines
= [x
.strip("\n") for x
in f
.readlines()]
396 defs
= [ (l
[:l
.find("=")], l
[(l
.find("=") + 1):]) for l
in lines
]
397 defs
= [ (a
, b
.strip("'")) for (a
,b
) in defs
]
400 def __pif_on_host(self
,pif
):
401 return self
.__pifs
.has_key(pif
)
403 def __get_pif_records_from_xapi(self
, session
, host
):
405 for (p
,rec
) in session
.xenapi
.PIF
.get_all_records().items():
406 if rec
['host'] != host
:
410 self
.__pifs
[p
][f
] = rec
[f
]
411 self
.__pifs
[p
]['other_config'] = {}
412 for f
in PIF_OTHERCONFIG_ATTRS
:
413 if not rec
['other_config'].has_key(f
): continue
414 self
.__pifs
[p
]['other_config'][f
] = rec
['other_config'][f
]
416 def __get_vlan_records_from_xapi(self
, session
):
418 for v
in session
.xenapi
.VLAN
.get_all():
419 rec
= session
.xenapi
.VLAN
.get_record(v
)
420 if not self
.__pif
_on
_host
(rec
['untagged_PIF']):
424 self
.__vlans
[v
][f
] = rec
[f
]
426 def __get_bond_records_from_xapi(self
, session
):
428 for b
in session
.xenapi
.Bond
.get_all():
429 rec
= session
.xenapi
.Bond
.get_record(b
)
430 if not self
.__pif
_on
_host
(rec
['master']):
434 self
.__bonds
[b
][f
] = rec
[f
]
436 def __get_network_records_from_xapi(self
, session
):
438 for n
in session
.xenapi
.network
.get_all():
439 rec
= session
.xenapi
.network
.get_record(n
)
440 self
.__networks
[n
] = {}
441 for f
in NETWORK_ATTRS
:
443 # drop PIFs on other hosts
444 self
.__networks
[n
][f
] = [p
for p
in rec
[f
] if self
.__pif
_on
_host
(p
)]
446 self
.__networks
[n
][f
] = rec
[f
]
447 self
.__networks
[n
]['other_config'] = {}
448 for f
in NETWORK_OTHERCONFIG_ATTRS
:
449 if not rec
['other_config'].has_key(f
): continue
450 self
.__networks
[n
]['other_config'][f
] = rec
['other_config'][f
]
452 def __to_xml(self
, xml
, parent
, key
, ref
, rec
, attrs
):
453 """Encode a database object as XML"""
454 e
= xml
.createElement(key
)
455 parent
.appendChild(e
)
457 e
.setAttribute('ref', ref
)
459 for n
,v
in rec
.items():
464 raise Error("Unknown attribute %s" % n
)
465 def __from_xml(self
, e
, attrs
):
466 """Decode a database object from XML"""
467 ref
= e
.attributes
['ref'].value
469 for n
in e
.childNodes
:
470 if n
.nodeName
in attrs
:
471 _
,h
= attrs
[n
.nodeName
]
472 rec
[n
.nodeName
] = h(n
)
475 def __init__(self
, session_ref
=None, cache_file
=None):
476 if session_ref
and cache_file
:
477 raise Error("can't specify session reference and cache file")
478 if cache_file
== None:
479 session
= XenAPI
.xapi_local()
482 log("No session ref given on command line, logging in.")
483 session
.xenapi
.login_with_password("root", "")
485 session
._session
= session_ref
489 inventory
= self
.__read
_xensource
_inventory
()
490 assert(inventory
.has_key('INSTALLATION_UUID'))
491 log("host uuid is %s" % inventory
['INSTALLATION_UUID'])
493 host
= session
.xenapi
.host
.get_by_uuid(inventory
['INSTALLATION_UUID'])
495 self
.__get
_pif
_records
_from
_xapi
(session
, host
)
497 self
.__get
_vlan
_records
_from
_xapi
(session
)
498 self
.__get
_bond
_records
_from
_xapi
(session
)
499 self
.__get
_network
_records
_from
_xapi
(session
)
502 session
.xenapi
.session
.logout()
504 log("Loading xapi database cache from %s" % cache_file
)
506 xml
= parseXML(cache_file
)
513 assert(len(xml
.childNodes
) == 1)
514 toplevel
= xml
.childNodes
[0]
516 assert(toplevel
.nodeName
== "xenserver-network-configuration")
518 for n
in toplevel
.childNodes
:
519 if n
.nodeName
== "#text":
521 elif n
.nodeName
== PIF_XML_TAG
:
522 (ref
,rec
) = self
.__from
_xml
(n
, PIF_ATTRS
)
523 self
.__pifs
[ref
] = rec
524 elif n
.nodeName
== BOND_XML_TAG
:
525 (ref
,rec
) = self
.__from
_xml
(n
, BOND_ATTRS
)
526 self
.__bonds
[ref
] = rec
527 elif n
.nodeName
== VLAN_XML_TAG
:
528 (ref
,rec
) = self
.__from
_xml
(n
, VLAN_ATTRS
)
529 self
.__vlans
[ref
] = rec
530 elif n
.nodeName
== NETWORK_XML_TAG
:
531 (ref
,rec
) = self
.__from
_xml
(n
, NETWORK_ATTRS
)
532 self
.__networks
[ref
] = rec
534 raise Error("Unknown XML element %s" % n
.nodeName
)
536 def save(self
, cache_file
):
538 xml
= getDOMImplementation().createDocument(
539 None, "xenserver-network-configuration", None)
540 for (ref
,rec
) in self
.__pifs
.items():
541 self
.__to
_xml
(xml
, xml
.documentElement
, PIF_XML_TAG
, ref
, rec
, PIF_ATTRS
)
542 for (ref
,rec
) in self
.__bonds
.items():
543 self
.__to
_xml
(xml
, xml
.documentElement
, BOND_XML_TAG
, ref
, rec
, BOND_ATTRS
)
544 for (ref
,rec
) in self
.__vlans
.items():
545 self
.__to
_xml
(xml
, xml
.documentElement
, VLAN_XML_TAG
, ref
, rec
, VLAN_ATTRS
)
546 for (ref
,rec
) in self
.__networks
.items():
547 self
.__to
_xml
(xml
, xml
.documentElement
, NETWORK_XML_TAG
, ref
, rec
,
550 f
= open(cache_file
, 'w')
551 f
.write(xml
.toprettyxml())
554 def get_pif_by_uuid(self
, uuid
):
555 pifs
= map(lambda (ref
,rec
): ref
,
556 filter(lambda (ref
,rec
): uuid
== rec
['uuid'],
557 self
.__pifs
.items()))
559 raise Error("Unknown PIF \"%s\"" % uuid
)
561 raise Error("Non-unique PIF \"%s\"" % uuid
)
565 def get_pifs_by_device(self
, device
):
566 return map(lambda (ref
,rec
): ref
,
567 filter(lambda (ref
,rec
): rec
['device'] == device
,
568 self
.__pifs
.items()))
570 def get_pif_by_bridge(self
, bridge
):
571 networks
= map(lambda (ref
,rec
): ref
,
572 filter(lambda (ref
,rec
): rec
['bridge'] == bridge
,
573 self
.__networks
.items()))
574 if len(networks
) == 0:
575 raise Error("No matching network \"%s\"")
578 for network
in networks
:
579 nwrec
= self
.get_network_record(network
)
580 for pif
in nwrec
['PIFs']:
581 pifrec
= self
.get_pif_record(pif
)
583 raise Error("Multiple PIFs on host for network %s" % (bridge
))
586 raise Error("No PIF on host for network %s" % (bridge
))
589 def get_pif_record(self
, pif
):
590 if self
.__pifs
.has_key(pif
):
591 return self
.__pifs
[pif
]
592 raise Error("Unknown PIF \"%s\" (get_pif_record)" % pif
)
593 def get_all_pifs(self
):
595 def pif_exists(self
, pif
):
596 return self
.__pifs
.has_key(pif
)
598 def get_management_pif(self
):
599 """ Returns the management pif on host
601 all
= self
.get_all_pifs()
603 pifrec
= self
.get_pif_record(pif
)
604 if pifrec
['management']: return pif
607 def get_network_record(self
, network
):
608 if self
.__networks
.has_key(network
):
609 return self
.__networks
[network
]
610 raise Error("Unknown network \"%s\"" % network
)
611 def get_all_networks(self
):
612 return self
.__networks
614 def get_bond_record(self
, bond
):
615 if self
.__bonds
.has_key(bond
):
616 return self
.__bonds
[bond
]
620 def get_vlan_record(self
, vlan
):
621 if self
.__vlans
.has_key(vlan
):
622 return self
.__vlans
[vlan
]
626 def bridge_name(pif
):
627 """Return the bridge name associated with pif, or None if network is bridgeless"""
628 pifrec
= db
.get_pif_record(pif
)
629 nwrec
= db
.get_network_record(pifrec
['network'])
632 # TODO: sanity check that nwrec['bridgeless'] != 'true'
633 return nwrec
['bridge']
635 # TODO: sanity check that nwrec['bridgeless'] == 'true'
638 def interface_name(pif
):
639 """Construct an interface name from the given PIF record."""
641 pifrec
= db
.get_pif_record(pif
)
643 if pifrec
['VLAN'] == '-1':
644 return pifrec
['device']
646 return "%(device)s.%(VLAN)s" % pifrec
648 def datapath_name(pif
):
649 """Return the OpenFlow datapath name associated with pif.
650 For a non-VLAN PIF, the datapath name is the bridge name.
651 For a VLAN PIF, the datapath name is the bridge name for the PIF's VLAN slave.
652 (xapi will create a datapath named with the bridge name even though we won't
657 pifrec
= db
.get_pif_record(pif
)
659 if pifrec
['VLAN'] == '-1':
660 return bridge_name(pif
)
662 return bridge_name(get_vlan_slave_of_pif(pif
))
665 """Return the the name of the network device that carries the
666 IP configuration (if any) associated with pif.
667 The ipdev name is the same as the bridge name.
670 pifrec
= db
.get_pif_record(pif
)
671 return bridge_name(pif
)
673 def get_physdev_pifs(pif
):
674 """Return the PIFs for the physical network device(s) associated with pif.
675 For a VLAN PIF, this is the VLAN slave's physical device PIF.
676 For a bond master PIF, these are the bond slave PIFs.
677 For a non-VLAN, non-bond master PIF, the PIF is its own physical device PIF.
679 pifrec
= db
.get_pif_record(pif
)
681 if pifrec
['VLAN'] != '-1':
682 return get_physdev_pifs(get_vlan_slave_of_pif(pif
))
683 elif len(pifrec
['bond_master_of']) != 0:
684 return get_bond_slaves_of_pif(pif
)
688 def get_physdev_names(pif
):
689 """Return the name(s) of the physical network device(s) associated with pif.
690 For a VLAN PIF, the physical devices are the VLAN slave's physical devices.
691 For a bond master PIF, the physical devices are the bond slaves.
692 For a non-VLAN, non-bond master PIF, the physical device is the PIF itself.
695 return [db
.get_pif_record(phys
)['device'] for phys
in get_physdev_pifs(pif
)]
697 def log_pif_action(action
, pif
):
698 pifrec
= db
.get_pif_record(pif
)
700 rec
['uuid'] = pifrec
['uuid']
701 rec
['ip_configuration_mode'] = pifrec
['ip_configuration_mode']
702 rec
['action'] = action
703 rec
['interface-name'] = interface_name(pif
)
704 if action
== "rewrite":
705 rec
['message'] = "Rewrite PIF %(uuid)s configuration" % rec
707 rec
['message'] = "Bring %(action)s PIF %(uuid)s" % rec
708 log("%(message)s: %(interface-name)s configured as %(ip_configuration_mode)s" % rec
)
710 def get_bond_masters_of_pif(pif
):
711 """Returns a list of PIFs which are bond masters of this PIF"""
713 pifrec
= db
.get_pif_record(pif
)
715 bso
= pifrec
['bond_slave_of']
717 # bond-slave-of is currently a single reference but in principle a
718 # PIF could be a member of several bonds which are not
719 # concurrently attached. Be robust to this possibility.
720 if not bso
or bso
== "OpaqueRef:NULL":
722 elif not type(bso
) == list:
725 bondrecs
= [db
.get_bond_record(bond
) for bond
in bso
]
726 bondrecs
= [rec
for rec
in bondrecs
if rec
]
728 return [bond
['master'] for bond
in bondrecs
]
730 def get_bond_slaves_of_pif(pif
):
731 """Returns a list of PIFs which make up the given bonded pif."""
733 pifrec
= db
.get_pif_record(pif
)
735 bmo
= pifrec
['bond_master_of']
737 raise Error("Bond-master-of contains too many elements")
742 bondrec
= db
.get_bond_record(bmo
[0])
744 raise Error("No bond record for bond master PIF")
746 return bondrec
['slaves']
748 def get_vlan_slave_of_pif(pif
):
749 """Find the PIF which is the VLAN slave of pif.
751 Returns the 'physical' PIF underneath the a VLAN PIF @pif."""
753 pifrec
= db
.get_pif_record(pif
)
755 vlan
= pifrec
['VLAN_master_of']
756 if not vlan
or vlan
== "OpaqueRef:NULL":
757 raise Error("PIF is not a VLAN master")
759 vlanrec
= db
.get_vlan_record(vlan
)
761 raise Error("No VLAN record found for PIF")
763 return vlanrec
['tagged_PIF']
765 def get_vlan_masters_of_pif(pif
):
766 """Returns a list of PIFs which are VLANs on top of the given pif."""
768 pifrec
= db
.get_pif_record(pif
)
769 vlans
= [db
.get_vlan_record(v
) for v
in pifrec
['VLAN_slave_of']]
770 return [v
['untagged_PIF'] for v
in vlans
if v
and db
.pif_exists(v
['untagged_PIF'])]
772 def interface_deconfigure_commands(interface
):
773 # The use of [!0-9] keeps an interface of 'eth0' from matching
774 # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
776 return ['--del-match=bridge.*.port=%s' % interface
,
777 '--del-match=bonding.%s.[!0-9]*' % interface
,
778 '--del-match=bonding.*.slave=%s' % interface
,
779 '--del-match=vlan.%s.[!0-9]*' % interface
,
780 '--del-match=port.%s.[!0-9]*' % interface
,
781 '--del-match=iface.%s.[!0-9]*' % interface
]
783 def run_command(command
):
784 log("Running command: " + ' '.join(command
))
785 if os
.spawnl(os
.P_WAIT
, command
[0], *command
) != 0:
786 log("Command failed: " + ' '.join(command
))
790 def rename_netdev(old_name
, new_name
):
791 log("Changing the name of %s to %s" % (old_name
, new_name
))
792 run_command(['/sbin/ifconfig', old_name
, 'down'])
793 if not run_command(['/sbin/ip', 'link', 'set', old_name
,
795 raise Error("Could not rename %s to %s" % (old_name
, new_name
))
797 # Check whether 'pif' exists and has the correct MAC.
798 # If not, try to find a device with the correct MAC and rename it.
799 # 'already_renamed' is used to avoid infinite recursion.
800 def remap_pif(pif
, already_renamed
=[]):
801 pifrec
= db
.get_pif_record(pif
)
802 device
= pifrec
['device']
805 # Is there a network device named 'device' at all?
806 device_exists
= interface_exists(device
)
808 # Yes. Does it have MAC 'mac'?
809 found_mac
= get_netdev_mac(device
)
810 if found_mac
and mac
.lower() == found_mac
.lower():
811 # Yes, everything checks out the way we want. Nothing to do.
814 log("No network device %s" % device
)
816 # What device has MAC 'mac'?
817 cur_device
= get_netdev_by_mac(mac
)
819 log("No network device has MAC %s" % mac
)
822 # First rename 'device', if it exists, to get it out of the way
823 # for 'cur_device' to replace it.
825 rename_netdev(device
, "dev%d" % random
.getrandbits(24))
827 # Rename 'cur_device' to 'device'.
828 rename_netdev(cur_device
, device
)
830 def read_first_line_of_file(name
):
833 file = open(name
, 'r')
834 return file.readline().rstrip('\n')
839 def down_netdev(interface
, deconfigure
=True):
840 if not interface_exists(interface
):
841 log("down_netdev: interface %s does not exist, ignoring" % interface
)
845 pidfile_name
= '/var/run/dhclient-%s.pid' % interface
847 os
.kill(int(read_first_line_of_file(pidfile_name
)), signal
.SIGTERM
)
851 # Remove dhclient pidfile.
853 os
.remove(pidfile_name
)
857 run_command(["/sbin/ifconfig", interface
, '0.0.0.0'])
859 run_command(["/sbin/ifconfig", interface
, 'down'])
861 def up_netdev(interface
):
862 run_command(["/sbin/ifconfig", interface
, 'up'])
864 def find_distinguished_pifs(pif
):
865 """Returns the PIFs on host that own DNS and the default route.
866 The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
867 The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
869 Note: we prune out the bond master pif (if it exists).
870 This is because when we are called to bring up an interface with a bond master, it is implicit that
871 we should bring down that master."""
873 pifrec
= db
.get_pif_record(pif
)
875 pifs
= [ __pif
for __pif
in db
.get_all_pifs() if
876 (not __pif
in get_bond_masters_of_pif(pif
)) ]
879 defaultroute_pif
= None
881 # loop through all the pifs on this host looking for one with
882 # other-config:peerdns = true, and one with
883 # other-config:default-route=true
885 __pifrec
= db
.get_pif_record(__pif
)
886 __oc
= __pifrec
['other_config']
887 if __oc
.has_key('peerdns') and __oc
['peerdns'] == 'true':
888 if peerdns_pif
== None:
891 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
892 (db
.get_pif_record(peerdns_pif
)['device'], __pifrec
['device']))
893 if __oc
.has_key('defaultroute') and __oc
['defaultroute'] == 'true':
894 if defaultroute_pif
== None:
895 defaultroute_pif
= __pif
897 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
898 (db
.get_pif_record(defaultroute_pif
)['device'], __pifrec
['device']))
900 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
901 if peerdns_pif
== None:
902 peerdns_pif
= management_pif
903 if defaultroute_pif
== None:
904 defaultroute_pif
= management_pif
906 return peerdns_pif
, defaultroute_pif
908 def run_ethtool(device
, oc
):
909 # Run "ethtool -s" if there are any settings.
911 if oc
.has_key('ethtool-speed'):
912 val
= oc
['ethtool-speed']
913 if val
in ["10", "100", "1000"]:
914 settings
+= ['speed', val
]
916 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val
)
917 if oc
.has_key('ethtool-duplex'):
918 val
= oc
['ethtool-duplex']
919 if val
in ["10", "100", "1000"]:
920 settings
+= ['duplex', 'val']
922 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val
)
923 if oc
.has_key('ethtool-autoneg'):
924 val
= oc
['ethtool-autoneg']
925 if val
in ["true", "on"]:
926 settings
+= ['autoneg', 'on']
927 elif val
in ["false", "off"]:
928 settings
+= ['autoneg', 'off']
930 log("Invalid value for ethtool-autoneg = %s. Must be on|true|off|false." % val
)
932 run_command(['/sbin/ethtool', '-s', device
] + settings
)
934 # Run "ethtool -K" if there are any offload settings.
936 for opt
in ("rx", "tx", "sg", "tso", "ufo", "gso"):
937 if oc
.has_key("ethtool-" + opt
):
938 val
= oc
["ethtool-" + opt
]
939 if val
in ["true", "on"]:
940 offload
+= [opt
, 'on']
941 elif val
in ["false", "off"]:
942 offload
+= [opt
, 'off']
944 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt
, val
))
946 run_command(['/sbin/ethtool', '-K', device
] + offload
)
949 if oc
.has_key('mtu'):
951 int(oc
['mtu']) # Check that the value is an integer
952 return ['mtu', oc
['mtu']]
953 except ValueError, x
:
954 log("Invalid value for mtu = %s" % mtu
)
957 def configure_local_port(pif
):
958 pifrec
= db
.get_pif_record(pif
)
959 datapath
= datapath_name(pif
)
960 ipdev
= ipdev_name(pif
)
962 nw
= pifrec
['network']
963 nwrec
= db
.get_network_record(nw
)
965 pif_oc
= pifrec
['other_config']
966 nw_oc
= nwrec
['other_config']
968 # IP (except DHCP) and MTU.
969 ifconfig_argv
= ['/sbin/ifconfig', ipdev
, 'up']
971 if pifrec
['ip_configuration_mode'] == "DHCP":
973 elif pifrec
['ip_configuration_mode'] == "Static":
974 ifconfig_argv
+= [pifrec
['IP']]
975 ifconfig_argv
+= ['netmask', pifrec
['netmask']]
976 gateway
= pifrec
['gateway']
977 elif pifrec
['ip_configuration_mode'] == "None":
981 raise Error("Unknown IP-configuration-mode %s" % pifrec
['ip_configuration_mode'])
982 ifconfig_argv
+= mtu_setting(nw_oc
)
983 run_command(ifconfig_argv
)
985 (peerdns_pif
, defaultroute_pif
) = find_distinguished_pifs(pif
)
988 if peerdns_pif
== pif
:
989 f
= ConfigurationFile('resolv.conf', "/etc")
990 if pif_oc
.has_key('domain'):
991 f
.write("search %s\n" % pif_oc
['domain'])
992 for dns
in pifrec
['DNS'].split(","):
993 f
.write("nameserver %s\n" % dns
)
999 if defaultroute_pif
== pif
and gateway
!= '':
1000 run_command(['/sbin/ip', 'route', 'replace', 'default',
1001 'via', gateway
, 'dev', ipdev
])
1002 if nw_oc
.has_key('static-routes'):
1003 for line
in nw_oc
['static-routes'].split(','):
1004 network
, masklen
, gateway
= line
.split('/')
1005 run_command(['/sbin/ip', 'route', 'add',
1006 '%s/%s' % (network
, masklen
), 'via', gateway
,
1010 run_ethtool(ipdev
, nw_oc
)
1013 if pifrec
['ip_configuration_mode'] == "DHCP":
1015 print "Determining IP information for %s..." % ipdev
,
1016 argv
= ['/sbin/dhclient', '-q',
1017 '-lf', '/var/lib/dhclient/dhclient-%s.leases' % ipdev
,
1018 '-pf', '/var/run/dhclient-%s.pid' % ipdev
,
1020 if run_command(argv
):
1025 def configure_physdev(pif
):
1026 pifrec
= db
.get_pif_record(pif
)
1027 device
= pifrec
['device']
1028 oc
= pifrec
['other_config']
1030 run_command(['/sbin/ifconfig', device
, 'up'] + mtu_setting(oc
))
1031 run_ethtool(device
, oc
)
1033 def modify_config(commands
):
1034 run_command(['/usr/bin/ovs-cfg-mod', '-vANY:console:emer',
1035 '-F', '/etc/ovs-vswitchd.conf']
1036 + commands
+ ['-c'])
1037 run_command(['/sbin/service', 'vswitch', 'reload'])
1039 def is_bond_pif(pif
):
1040 pifrec
= db
.get_pif_record(pif
)
1041 return len(pifrec
['bond_master_of']) != 0
1043 def configure_bond(pif
):
1044 pifrec
= db
.get_pif_record(pif
)
1045 interface
= interface_name(pif
)
1046 ipdev
= ipdev_name(pif
)
1047 datapath
= datapath_name(pif
)
1048 physdev_names
= get_physdev_names(pif
)
1050 argv
= ['--del-match=bonding.%s.[!0-9]*' % interface
]
1051 argv
+= ["--add=bonding.%s.slave=%s" % (interface
, slave
)
1052 for slave
in physdev_names
]
1053 argv
+= ['--add=bonding.%s.fake-iface=true' % interface
]
1055 if pifrec
['MAC'] != "":
1056 argv
+= ['--add=port.%s.mac=%s' % (interface
, pifrec
['MAC'])]
1060 "mode": "balance-slb",
1066 # override defaults with values from other-config whose keys
1067 # being with "bond-"
1068 oc
= pifrec
['other_config']
1069 overrides
= filter(lambda (key
,val
):
1070 key
.startswith("bond-"), oc
.items())
1071 overrides
= map(lambda (key
,val
): (key
[5:], val
), overrides
)
1072 bond_options
.update(overrides
)
1073 for (name
,val
) in bond_options
.items():
1074 argv
+= ["--add=bonding.%s.%s=%s" % (interface
, name
, val
)]
1078 pifrec
= db
.get_pif_record(pif
)
1080 bridge
= bridge_name(pif
)
1081 interface
= interface_name(pif
)
1082 ipdev
= ipdev_name(pif
)
1083 datapath
= datapath_name(pif
)
1084 physdev_pifs
= get_physdev_pifs(pif
)
1085 physdev_names
= get_physdev_names(pif
)
1087 if pifrec
['VLAN'] != '-1':
1088 vlan_slave
= get_vlan_slave_of_pif(pif
)
1089 if vlan_slave
and is_bond_pif(vlan_slave
):
1090 bond_master
= vlan_slave
1091 elif is_bond_pif(pif
):
1096 bond_slaves
= get_bond_slaves_of_pif(bond_master
)
1099 bond_masters
= get_bond_masters_of_pif(pif
)
1101 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1102 # files up-to-date, even though we don't use them or need them.
1103 f
= configure_pif(pif
)
1104 mode
= pifrec
['ip_configuration_mode']
1106 log("Configuring %s using %s configuration" % (bridge
, mode
))
1107 br
= open_network_ifcfg(pif
)
1108 configure_network(pif
, br
)
1112 log("Configuring %s using %s configuration" % (interface
, mode
))
1113 configure_network(pif
, f
)
1115 for master
in bond_masters
:
1116 master_bridge
= bridge_name(master
)
1117 removed
= unconfigure_pif(master
)
1118 f
.attach_child(removed
)
1120 removed
= open_network_ifcfg(master
)
1121 log("Unlinking stale file %s" % removed
.path())
1123 f
.attach_child(removed
)
1125 # /etc/xensource/scripts/vif needs to know where to add VIFs.
1127 if not os
.path
.exists(vswitch_state_dir
):
1128 os
.mkdir(vswitch_state_dir
)
1129 br
= ConfigurationFile("br-%s" % bridge
, vswitch_state_dir
)
1130 br
.write("VLAN_SLAVE=%s\n" % datapath
)
1131 br
.write("VLAN_VID=%s\n" % pifrec
['VLAN'])
1135 # Update all configuration files (both ours and Centos's).
1139 # Check the MAC address of each network device and remap if
1140 # necessary to make names match our expectations.
1141 for physdev_pif
in physdev_pifs
:
1142 remap_pif(physdev_pif
)
1144 # "ifconfig down" the network device and delete its IP address, etc.
1146 for physdev_name
in physdev_names
:
1147 down_netdev(physdev_name
)
1149 # If we are bringing up a bond, remove IP addresses from the
1150 # slaves (because we are implicitly being asked to take them down).
1152 # Conversely, if we are bringing up an interface that has bond
1153 # masters, remove IP addresses from the bond master (because we
1154 # are implicitly being asked to take it down).
1155 for bond_pif
in bond_slaves
+ bond_masters
:
1156 run_command(["/sbin/ifconfig", ipdev_name(bond_pif
), '0.0.0.0'])
1158 # Remove all keys related to pif and any bond masters linked to PIF.
1159 del_ports
= [ipdev
] + physdev_names
+ bond_masters
1160 if vlan_slave
and bond_master
:
1161 del_ports
+= [interface_name(bond_master
)]
1163 # What ports do we need to add to the datapath?
1165 # We definitely need the ipdev, and ordinarily we want the
1166 # physical devices too, but for bonds we need the bond as bridge
1168 add_ports
= [ipdev
, datapath
]
1170 add_ports
+= physdev_names
1172 add_ports
+= [interface_name(bond_master
)]
1174 # What ports do we need to delete?
1176 # - All the ports that we add, to avoid duplication and to drop
1177 # them from another datapath in case they're misassigned.
1179 # - The physical devices, since they will either be in add_ports
1180 # or added to the bonding device (see below).
1182 # - The bond masters for pif. (Ordinarily pif shouldn't have any
1183 # bond masters. If it does then interface-reconfigure is
1184 # implicitly being asked to take them down.)
1185 del_ports
= add_ports
+ physdev_names
+ bond_masters
1187 # What networks does this datapath carry?
1189 # - The network corresponding to the datapath's PIF.
1191 # - The networks corresponding to any VLANs attached to the
1194 for nwpif
in db
.get_pifs_by_device({'device': pifrec
['device']}):
1195 net
= db
.get_pif_record(nwpif
)['network']
1196 network_uuids
+= [db
.get_network_record(net
)['uuid']]
1198 # Bring up bond slaves early, because ovs-vswitchd initially
1199 # enables or disables bond slaves based on whether carrier is
1200 # detected when they are added, and a network device that is down
1201 # always reports "no carrier".
1202 bond_slave_physdev_pifs
= []
1203 for slave
in bond_slaves
:
1204 bond_slave_physdev_pifs
+= get_physdev_pifs(slave
)
1205 for slave_physdev_pif
in set(bond_slave_physdev_pifs
):
1206 configure_physdev(slave_physdev_pif
)
1208 # Now modify the ovs-vswitchd config file.
1210 for port
in set(del_ports
):
1211 argv
+= interface_deconfigure_commands(port
)
1212 for port
in set(add_ports
):
1213 argv
+= ['--add=bridge.%s.port=%s' % (datapath
, port
)]
1215 argv
+= ['--add=vlan.%s.tag=%s' % (ipdev
, pifrec
['VLAN'])]
1216 argv
+= ['--add=iface.%s.internal=true' % (ipdev
)]
1218 # xapi creates a bridge by the name of the ipdev and requires
1219 # that the IP address will be on it. We need to delete this
1220 # bridge because we need that device to be a member of our
1222 argv
+= ['--del-match=bridge.%s.[!0-9]*' % ipdev
]
1224 # xapi insists that its attempts to create the bridge succeed,
1225 # so force that to happen.
1226 argv
+= ['--add=iface.%s.fake-bridge=true' % (ipdev
)]
1229 os
.unlink("%s/br-%s" % (vswitch_state_dir
, bridge
))
1232 argv
+= ['--del-match=bridge.%s.xs-network-uuids=*' % datapath
]
1233 argv
+= ['--add=bridge.%s.xs-network-uuids=%s' % (datapath
, uuid
)
1234 for uuid
in set(network_uuids
)]
1236 argv
+= configure_bond(bond_master
)
1239 # Bring up VLAN slave, plus physical devices other than bond
1240 # slaves (which we brought up earlier).
1242 up_netdev(ipdev_name(vlan_slave
))
1243 for physdev_pif
in set(physdev_pifs
) - set(bond_slave_physdev_pifs
):
1244 configure_physdev(physdev_pif
)
1246 # Configure network device for local port.
1247 configure_local_port(pif
)
1249 # Update /etc/issue (which contains the IP address of the management interface)
1250 os
.system("/sbin/update-issue")
1253 # There seems to be a race somewhere: without this sleep, using
1254 # XenCenter to create a bond that becomes the management interface
1255 # fails with "The underlying connection was closed: A connection that
1256 # was expected to be kept alive was closed by the server." on every
1257 # second or third try, even though /var/log/messages doesn't show
1260 # The race is probably present even without vswitch, but bringing up a
1261 # bond without vswitch involves a built-in pause of 10 seconds or more
1262 # to wait for the bond to transition from learning to forwarding state.
1265 def action_down(pif
):
1266 rec
= db
.get_pif_record(pif
)
1267 interface
= interface_name(pif
)
1268 bridge
= bridge_name(pif
)
1269 ipdev
= ipdev_name(pif
)
1271 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1272 # files up-to-date, even though we don't use them or need them.
1273 f
= unconfigure_pif(pif
)
1275 br
= open_network_ifcfg(pif
)
1276 log("Unlinking stale file %s" % br
.path())
1283 log("action_down failed to apply changes: %s" % e
.msg
)
1288 if rec
['VLAN'] != '-1':
1289 # Get rid of the VLAN device itself.
1291 argv
+= interface_deconfigure_commands(ipdev
)
1293 # If the VLAN's slave is attached, stop here.
1294 slave
= get_vlan_slave_of_pif(pif
)
1295 if db
.get_pif_record(slave
)['currently_attached']:
1296 log("VLAN slave is currently attached")
1300 # If the VLAN's slave has other VLANs that are attached, stop here.
1301 masters
= get_vlan_masters_of_pif(slave
)
1303 if m
!= pif
and db
.get_pif_record(m
)['currently_attached']:
1304 log("VLAN slave has other master %s" % interface_naem(m
))
1308 # Otherwise, take down the VLAN's slave too.
1309 log("No more masters, bring down vlan slave %s" % interface_name(slave
))
1312 # Stop here if this PIF has attached VLAN masters.
1313 vlan_masters
= get_vlan_masters_of_pif(pif
)
1314 log("VLAN masters of %s - %s" % (rec
['device'], [interface_name(m
) for m
in vlan_masters
]))
1315 for m
in vlan_masters
:
1316 if db
.get_pif_record(m
)['currently_attached']:
1317 log("Leaving %s up due to currently attached VLAN master %s" % (interface
, interface_name(m
)))
1320 # pif is now either a bond or a physical device which needs to be
1321 # brought down. pif might have changed so re-check all its attributes.
1322 rec
= db
.get_pif_record(pif
)
1323 interface
= interface_name(pif
)
1324 bridge
= bridge_name(pif
)
1325 ipdev
= ipdev_name(pif
)
1328 bond_slaves
= get_bond_slaves_of_pif(pif
)
1329 log("bond slaves of %s - %s" % (rec
['device'], [interface_name(s
) for s
in bond_slaves
]))
1330 for slave
in bond_slaves
:
1331 slave_interface
= interface_name(slave
)
1332 log("bring down bond slave %s" % slave_interface
)
1333 argv
+= interface_deconfigure_commands(slave_interface
)
1334 down_netdev(slave_interface
)
1336 argv
+= interface_deconfigure_commands(ipdev
)
1339 argv
+= ['--del-match', 'bridge.%s.*' % datapath_name(pif
)]
1340 argv
+= ['--del-match', 'bonding.%s.[!0-9]*' % interface
]
1343 def action_rewrite(pif
):
1344 # Support "rpm -e vswitch" gracefully by keeping Centos configuration
1345 # files up-to-date, even though we don't use them or need them.
1346 pifrec
= db
.get_pif_record(pif
)
1347 f
= configure_pif(pif
)
1348 interface
= interface_name(pif
)
1349 bridge
= bridge_name(pif
)
1350 mode
= pifrec
['ip_configuration_mode']
1352 log("Configuring %s using %s configuration" % (bridge
, mode
))
1353 br
= open_network_ifcfg(pif
)
1354 configure_network(pif
, br
)
1358 log("Configuring %s using %s configuration" % (interface
, mode
))
1359 configure_network(pif
, f
)
1365 log("failed to apply changes: %s" % e
.msg
)
1369 # We have no code of our own to run here.
1372 def main(argv
=None):
1373 global output_directory
, management_pif
1379 force_interface
= None
1380 force_management
= False
1388 longops
= [ "output-directory=",
1389 "pif=", "pif-uuid=",
1395 "device=", "mode=", "ip=", "netmask=", "gateway=",
1397 arglist
, args
= getopt
.gnu_getopt(argv
[1:], shortops
, longops
)
1398 except getopt
.GetoptError
, msg
:
1401 force_rewrite_config
= {}
1404 if o
== "--output-directory":
1405 output_directory
= a
1408 elif o
== "--pif-uuid":
1410 elif o
== "--session":
1412 elif o
== "--force-interface" or o
== "--force":
1414 elif o
== "--management":
1415 force_management
= True
1416 elif o
in ["--device", "--mode", "--ip", "--netmask", "--gateway"]:
1417 force_rewrite_config
[o
[2:]] = a
1418 elif o
== "-h" or o
== "--help":
1419 print __doc__
% {'command-name': os
.path
.basename(argv
[0])}
1422 if not debug_mode():
1423 syslog
.openlog(os
.path
.basename(argv
[0]))
1424 log("Called as " + str.join(" ", argv
))
1426 raise Usage("Required option <action> not present")
1428 raise Usage("Too many arguments")
1431 # backwards compatibility
1432 if action
== "rewrite-configuration": action
= "rewrite"
1434 if output_directory
and ( session
or pif
):
1435 raise Usage("--session/--pif cannot be used with --output-directory")
1436 if ( session
or pif
) and pif_uuid
:
1437 raise Usage("--session/--pif and --pif-uuid are mutually exclusive.")
1438 if ( session
and not pif
) or ( not session
and pif
):
1439 raise Usage("--session and --pif must be used together.")
1440 if force_interface
and ( session
or pif
or pif_uuid
):
1441 raise Usage("--force is mutually exclusive with --session, --pif and --pif-uuid")
1442 if len(force_rewrite_config
) and not (force_interface
and action
== "rewrite"):
1443 raise Usage("\"--force rewrite\" needed for --device, --mode, --ip, --netmask, and --gateway")
1445 if action
== "init-dbcache" and arglist
:
1446 raise Usage("\"init-dbcache\" action does not accept any options")
1450 log("Force interface %s %s" % (force_interface
, action
))
1452 if action
== "rewrite":
1453 action_force_rewrite(force_interface
, force_rewrite_config
)
1455 db
= DatabaseCache(cache_file
=dbcache_file
)
1456 pif
= db
.get_pif_by_bridge(force_interface
)
1457 management_pif
= db
.get_management_pif()
1461 elif action
== "down":
1464 raise Usage("Unknown action %s" % action
)
1465 elif action
== "init-dbcache":
1466 DatabaseCache().save(dbcache_file
)
1468 db
= DatabaseCache(session_ref
=session
)
1471 pif
= db
.get_pif_by_uuid(pif_uuid
)
1474 raise Usage("No PIF given")
1476 if force_management
:
1477 # pif is going to be the management pif
1478 management_pif
= pif
1480 # pif is not going to be the management pif.
1481 # Search DB cache for pif on same host with management=true
1482 pifrec
= db
.get_pif_record(pif
)
1483 management_pif
= db
.get_management_pif()
1485 log_pif_action(action
, pif
)
1487 if not check_allowed(pif
):
1492 elif action
== "down":
1494 elif action
== "rewrite":
1497 raise Usage("Unknown action %s" % action
)
1500 db
.save(dbcache_file
)
1503 print >>sys
.stderr
, err
.msg
1504 print >>sys
.stderr
, "For help use --help."
1512 # The following code allows interface-reconfigure to keep Centos
1513 # network configuration files up-to-date, even though the vswitch
1514 # never uses them. In turn, that means that "rpm -e vswitch" does not
1515 # have to update any configuration files.
1517 def configure_ethtool(oc
, f
):
1518 # Options for "ethtool -s"
1520 setting_opts
= ["autoneg", "speed", "duplex"]
1521 # Options for "ethtool -K"
1523 offload_opts
= ["rx", "tx", "sg", "tso", "ufo", "gso"]
1525 for opt
in [opt
for opt
in setting_opts
+ offload_opts
if oc
.has_key("ethtool-" + opt
)]:
1526 val
= oc
["ethtool-" + opt
]
1528 if opt
in ["speed"]:
1529 if val
in ["10", "100", "1000"]:
1530 val
= "speed " + val
1532 log("Invalid value for ethtool-speed = %s. Must be 10|100|1000." % val
)
1534 elif opt
in ["duplex"]:
1535 if val
in ["half", "full"]:
1536 val
= "duplex " + val
1538 log("Invalid value for ethtool-duplex = %s. Must be half|full." % val
)
1540 elif opt
in ["autoneg"] + offload_opts
:
1541 if val
in ["true", "on"]:
1543 elif val
in ["false", "off"]:
1546 log("Invalid value for ethtool-%s = %s. Must be on|true|off|false." % (opt
, val
))
1549 if opt
in setting_opts
:
1550 if val
and settings
:
1551 settings
= settings
+ " " + val
1554 elif opt
in offload_opts
:
1556 offload
= offload
+ " " + val
1561 f
.write("ETHTOOL_OPTS=\"%s\"\n" % settings
)
1563 f
.write("ETHTOOL_OFFLOAD_OPTS=\"%s\"\n" % offload
)
1565 def configure_mtu(oc
, f
):
1566 if not oc
.has_key('mtu'):
1570 mtu
= int(oc
['mtu'])
1571 f
.write("MTU=%d\n" % mtu
)
1572 except ValueError, x
:
1573 log("Invalid value for mtu = %s" % mtu
)
1575 def configure_static_routes(interface
, oc
, f
):
1576 """Open a route-<interface> file for static routes.
1578 Opens the static routes configuration file for interface and writes one
1579 line for each route specified in the network's other config "static-routes" value.
1581 interface ( RO): xenbr1
1582 other-config (MRW): static-routes: 172.16.0.0/15/192.168.0.3,172.18.0.0/16/192.168.0.4;...
1584 Then route-xenbr1 should be
1585 172.16.0.0/15 via 192.168.0.3 dev xenbr1
1586 172.18.0.0/16 via 192.168.0.4 dev xenbr1
1588 fname
= "route-%s" % interface
1589 if oc
.has_key('static-routes'):
1590 # The key is present - extract comma seperates entries
1591 lines
= oc
['static-routes'].split(',')
1593 # The key is not present, i.e. there are no static routes
1596 child
= ConfigurationFile(fname
)
1597 child
.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1598 (os
.path
.basename(child
.path()), os
.path
.basename(sys
.argv
[0])))
1602 network
, masklen
, gateway
= l
.split('/')
1603 child
.write("%s/%s via %s dev %s\n" % (network
, masklen
, gateway
, interface
))
1605 f
.attach_child(child
)
1608 except ValueError, e
:
1609 log("Error in other-config['static-routes'] format for network %s: %s" % (interface
, e
))
1611 def __open_ifcfg(interface
):
1612 """Open a network interface configuration file.
1614 Opens the configuration file for interface, writes a header and
1615 common options and returns the file object.
1617 fname
= "ifcfg-%s" % interface
1618 f
= ConfigurationFile(fname
)
1620 f
.write("# DO NOT EDIT: This file (%s) was autogenerated by %s\n" % \
1621 (os
.path
.basename(f
.path()), os
.path
.basename(sys
.argv
[0])))
1622 f
.write("XEMANAGED=yes\n")
1623 f
.write("DEVICE=%s\n" % interface
)
1624 f
.write("ONBOOT=no\n")
1628 def open_network_ifcfg(pif
):
1629 bridge
= bridge_name(pif
)
1630 interface
= interface_name(pif
)
1632 return __open_ifcfg(bridge
)
1634 return __open_ifcfg(interface
)
1637 def open_pif_ifcfg(pif
):
1638 pifrec
= db
.get_pif_record(pif
)
1640 log("Configuring %s (%s)" % (interface_name(pif
), pifrec
['MAC']))
1642 f
= __open_ifcfg(interface_name(pif
))
1644 if pifrec
.has_key('other_config'):
1645 configure_ethtool(pifrec
['other_config'], f
)
1646 configure_mtu(pifrec
['other_config'], f
)
1650 def configure_network(pif
, f
):
1651 """Write the configuration file for a network.
1653 Writes configuration derived from the network object into the relevant
1654 ifcfg file. The configuration file is passed in, but if the network is
1655 bridgeless it will be ifcfg-<interface>, otherwise it will be ifcfg-<bridge>.
1657 This routine may also write ifcfg files of the networks corresponding to other PIFs
1658 in order to maintain consistency.
1661 pif: Opaque_ref of pif
1662 f : ConfigurationFile(/path/to/ifcfg) to which we append network configuration
1665 pifrec
= db
.get_pif_record(pif
)
1666 nw
= pifrec
['network']
1667 nwrec
= db
.get_network_record(nw
)
1669 bridge
= bridge_name(pif
)
1670 interface
= interface_name(pif
)
1676 if nwrec
.has_key('other_config'):
1677 configure_ethtool(nwrec
['other_config'], f
)
1678 configure_mtu(nwrec
['other_config'], f
)
1679 configure_static_routes(device
, nwrec
['other_config'], f
)
1682 if pifrec
.has_key('other_config'):
1683 oc
= pifrec
['other_config']
1685 if device
== bridge
:
1686 f
.write("TYPE=Bridge\n")
1687 f
.write("DELAY=0\n")
1688 f
.write("STP=off\n")
1689 f
.write("PIFDEV=%s\n" % interface_name(pif
))
1691 if pifrec
['ip_configuration_mode'] == "DHCP":
1692 f
.write("BOOTPROTO=dhcp\n")
1693 f
.write("PERSISTENT_DHCLIENT=yes\n")
1694 elif pifrec
['ip_configuration_mode'] == "Static":
1695 f
.write("BOOTPROTO=none\n")
1696 f
.write("NETMASK=%(netmask)s\n" % pifrec
)
1697 f
.write("IPADDR=%(IP)s\n" % pifrec
)
1698 f
.write("GATEWAY=%(gateway)s\n" % pifrec
)
1699 elif pifrec
['ip_configuration_mode'] == "None":
1700 f
.write("BOOTPROTO=none\n")
1702 raise Error("Unknown ip-configuration-mode %s" % pifrec
['ip_configuration_mode'])
1704 if pifrec
.has_key('DNS') and pifrec
['DNS'] != "":
1705 ServerList
= pifrec
['DNS'].split(",")
1706 for i
in range(len(ServerList
)): f
.write("DNS%d=%s\n" % (i
+1, ServerList
[i
]))
1707 if oc
and oc
.has_key('domain'):
1708 f
.write("DOMAIN='%s'\n" % oc
['domain'].replace(',', ' '))
1710 # We only allow one ifcfg-xenbr* to have PEERDNS=yes and there can be only one GATEWAYDEV in /etc/sysconfig/network.
1711 # The peerdns pif will be the one with pif::other-config:peerdns=true, or the mgmt pif if none have this set.
1712 # The gateway pif will be the one with pif::other-config:defaultroute=true, or the mgmt pif if none have this set.
1714 # Work out which pif on this host should be the one with PEERDNS=yes and which should be the GATEWAYDEV
1716 # Note: we prune out the bond master pif (if it exists).
1717 # This is because when we are called to bring up an interface with a bond master, it is implicit that
1718 # we should bring down that master.
1719 pifs_on_host
= [ __pif
for __pif
in db
.get_all_pifs() if
1720 not __pif
in get_bond_masters_of_pif(pif
) ]
1721 other_pifs_on_host
= [ __pif
for __pif
in pifs_on_host
if __pif
!= pif
]
1724 defaultroute_pif
= None
1726 # loop through all the pifs on this host looking for one with
1727 # other-config:peerdns = true, and one with
1728 # other-config:default-route=true
1729 for __pif
in pifs_on_host
:
1730 __pifrec
= db
.get_pif_record(__pif
)
1731 __oc
= __pifrec
['other_config']
1732 if __oc
.has_key('peerdns') and __oc
['peerdns'] == 'true':
1733 if peerdns_pif
== None:
1736 log('Warning: multiple pifs with "peerdns=true" - choosing %s and ignoring %s' % \
1737 (db
.get_pif_record(peerdns_pif
)['device'], __pifrec
['device']))
1738 if __oc
.has_key('defaultroute') and __oc
['defaultroute'] == 'true':
1739 if defaultroute_pif
== None:
1740 defaultroute_pif
= __pif
1742 log('Warning: multiple pifs with "defaultroute=true" - choosing %s and ignoring %s' % \
1743 (db
.get_pif_record(defaultroute_pif
)['device'], __pifrec
['device']))
1745 # If no pif is explicitly specified then use the mgmt pif for peerdns/defaultroute
1746 if peerdns_pif
== None:
1747 peerdns_pif
= management_pif
1748 if defaultroute_pif
== None:
1749 defaultroute_pif
= management_pif
1751 # Update all the other network's ifcfg files and ensure consistency
1752 for __pif
in other_pifs_on_host
:
1753 __f
= open_network_ifcfg(__pif
)
1754 peerdns_line_wanted
= 'PEERDNS=%s\n' % ((__pif
== peerdns_pif
) and 'yes' or 'no')
1755 lines
= __f
.readlines()
1757 if not peerdns_line_wanted
in lines
:
1758 # the PIF selected for DNS has changed and as a result this ifcfg file needs rewriting
1760 if not line
.lstrip().startswith('PEERDNS'):
1762 log("Setting %s in %s" % (peerdns_line_wanted
.strip(), __f
.path()))
1763 __f
.write(peerdns_line_wanted
)
1768 # There is no need to change this ifcfg file. So don't attach_child.
1771 # ... and for this pif too
1772 f
.write('PEERDNS=%s\n' % ((pif
== peerdns_pif
) and 'yes' or 'no'))
1775 fnetwork
= ConfigurationFile("network", "/etc/sysconfig")
1776 for line
in fnetwork
.readlines():
1777 if line
.lstrip().startswith('GATEWAY') :
1779 fnetwork
.write(line
)
1780 if defaultroute_pif
:
1781 gatewaydev
= bridge_name(defaultroute_pif
)
1783 gatewaydev
= interface_name(defaultroute_pif
)
1784 fnetwork
.write('GATEWAYDEV=%s\n' % gatewaydev
)
1786 f
.attach_child(fnetwork
)
1791 def configure_physical_interface(pif
):
1792 """Write the configuration for a physical interface.
1794 Writes the configuration file for the physical interface described by
1797 Returns the open file handle for the interface configuration file.
1800 pifrec
= db
.get_pif_record(pif
)
1802 f
= open_pif_ifcfg(pif
)
1804 f
.write("TYPE=Ethernet\n")
1805 f
.write("HWADDR=%(MAC)s\n" % pifrec
)
1809 def configure_bond_interface(pif
):
1810 """Write the configuration for a bond interface.
1812 Writes the configuration file for the bond interface described by
1813 the pif object. Handles writing the configuration for the slave
1816 Returns the open file handle for the bond interface configuration
1820 pifrec
= db
.get_pif_record(pif
)
1821 oc
= pifrec
['other_config']
1822 f
= open_pif_ifcfg(pif
)
1824 if pifrec
['MAC'] != "":
1825 f
.write("MACADDR=%s\n" % pifrec
['MAC'])
1827 for slave
in get_bond_slaves_of_pif(pif
):
1828 s
= configure_physical_interface(slave
)
1829 s
.write("MASTER=%(device)s\n" % pifrec
)
1830 s
.write("SLAVE=yes\n")
1834 # The bond option defaults
1836 "mode": "balance-slb",
1843 # override defaults with values from other-config whose keys being with "bond-"
1844 overrides
= filter(lambda (key
,val
): key
.startswith("bond-"), oc
.items())
1845 overrides
= map(lambda (key
,val
): (key
[5:], val
), overrides
)
1846 bond_options
.update(overrides
)
1848 # write the bond options to ifcfg-bondX
1849 f
.write('BONDING_OPTS="')
1850 for (name
,val
) in bond_options
.items():
1851 f
.write("%s=%s " % (name
,val
))
1855 def configure_vlan_interface(pif
):
1856 """Write the configuration for a VLAN interface.
1858 Writes the configuration file for the VLAN interface described by
1859 the pif object. Handles writing the configuration for the master
1860 interface if necessary.
1862 Returns the open file handle for the VLAN interface configuration
1866 slave
= configure_pif(get_vlan_slave_of_pif(pif
))
1869 f
= open_pif_ifcfg(pif
)
1870 f
.write("VLAN=yes\n")
1871 f
.attach_child(slave
)
1875 def configure_pif(pif
):
1876 """Write the configuration for a PIF object.
1878 Writes the configuration file the PIF and all dependent
1879 interfaces (bond slaves and VLAN masters etc).
1881 Returns the open file handle for the interface configuration file.
1884 pifrec
= db
.get_pif_record(pif
)
1886 if pifrec
['VLAN'] != '-1':
1887 f
= configure_vlan_interface(pif
)
1888 elif len(pifrec
['bond_master_of']) != 0:
1889 f
= configure_bond_interface(pif
)
1891 f
= configure_physical_interface(pif
)
1893 bridge
= bridge_name(pif
)
1895 f
.write("BRIDGE=%s\n" % bridge
)
1899 def unconfigure_pif(pif
):
1900 """Clear up the files created by configure_pif"""
1901 f
= open_pif_ifcfg(pif
)
1902 log("Unlinking stale file %s" % f
.path())
1906 if __name__
== "__main__":
1912 err
= traceback
.format_exception(*ex
)
1916 if not debug_mode():