xenserver: Set fail_mode on internal bridges.
[sliver-openvswitch.git] / xenserver / usr_share_openvswitch_scripts_ovs-external-ids
1 #!/usr/bin/python
2 # Copyright (c) 2009, 2010 Nicira Networks
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16
17 # A daemon to monitor the external_ids columns of the Bridge and
18 # Interface OVSDB tables.  Its primary responsibility is to set the
19 # "bridge-id" and "iface-id" keys in the Bridge and Interface tables,
20 # respectively.  It also looks for the use of "network-uuids" in the
21 # Bridge table and duplicates its value to the preferred "xs-network-uuids".
22
23 import getopt
24 import logging, logging.handlers
25 import os
26 import signal
27 import subprocess
28 import sys
29 import time
30
31 import XenAPI
32
33 from ovs.db import error
34 from ovs.db import types
35 import ovs.util
36 import ovs.daemon
37 import ovs.db.idl
38
39 s_log     = logging.getLogger("ovs-external-ids")
40 l_handler = logging.handlers.RotatingFileHandler(
41         "/var/log/openvswitch/ovs-external-ids.log")
42 l_formatter = logging.Formatter('%(filename)s: %(levelname)s: %(message)s')
43 l_handler.setFormatter(l_formatter)
44 s_log.addHandler(l_handler)
45 s_log.setLevel(logging.INFO)
46
47 vsctl="/usr/bin/ovs-vsctl"
48 session = None
49 force_run = False
50
51 # Set up a session to interact with XAPI.
52 #
53 # On system start-up, OVS comes up before XAPI, so we can't log into the
54 # session until later.  Try to do this on-demand, since we won't
55 # actually do anything interesting until XAPI is up.
56 def init_session():
57     global session
58     if session is not None:
59         return True
60
61     try:
62         session = XenAPI.xapi_local()
63         session.xenapi.login_with_password("", "")
64     except:
65         session = None
66         s_log.warning("Couldn't login to XAPI")
67         return False
68
69     return True
70
71 def get_network_by_bridge(br_name):
72     if not init_session():
73         s_log.warning("Failed to get bridge id %s because"
74                 " XAPI session could not be initialized" % br_name)
75         return None
76
77     for n in session.xenapi.network.get_all():
78         rec = session.xenapi.network.get_record(n)
79         if rec['bridge'] == br_name:
80             return rec
81
82     return None
83
84 # By default, the "bridge-id" external id in the Bridge table is the
85 # same as "xs-network-uuids".  This may be overridden by defining a
86 # "nicira-bridge-id" key in the "other_config" field of the network
87 # record of XAPI.  If nicira-bridge-id is undefined returns default.
88 # On error returns None.
89 def get_bridge_id(br_name, default=None):
90     rec = get_network_by_bridge(br_name)
91     if rec:
92         return rec['other_config'].get('nicira-bridge-id', default)
93     return None
94
95 # By default, the "iface-id" external id in the Interface table is the
96 # same as "xs-vif-uuid".  This may be overridden by defining a
97 # "nicira-iface-id" key in the "other_config" field of the VIF
98 # record of XAPI.
99 def get_iface_id(if_name, xs_vif_uuid):
100     if not if_name.startswith("vif"):
101         # Treat whatever was passed into 'xs_vif_uuid' as a default
102         # value for non-VIFs.
103         return xs_vif_uuid
104
105     if not init_session():
106         s_log.warning("Failed to get interface id %s because"
107                 " XAPI session could not be initialized" % if_name)
108         return xs_vif_uuid
109
110     try:
111         vif = session.xenapi.VIF.get_by_uuid(xs_vif_uuid)
112         rec = session.xenapi.VIF.get_record(vif)
113         return rec['other_config'].get('nicira-iface-id', xs_vif_uuid)
114     except XenAPI.Failure:
115         s_log.warning("Could not find XAPI entry for VIF %s" % if_name)
116         return xs_vif_uuid
117
118 def call_vsctl(args):
119     cmd = [vsctl, "--timeout=30", "-vANY:console:emer"] + args
120     exitcode = subprocess.call(cmd)
121     if exitcode != 0:
122         s_log.warning("Couldn't call ovs-vsctl")
123
124 def set_external_id(table, record, key, value):
125     col = 'external-ids:"' + key + '"="' + value + '"'
126     call_vsctl(["set", table, record, col])
127
128 # XAPI on XenServer 5.6 uses the external-id "network-uuids" for internal
129 # networks, but we now prefer "xs-network-uuids".  Look for its use and
130 # write our preferred external-id.
131 def update_network_uuids(name, ids):
132     if ids["network-uuids"] and not ids["xs-network-uuids"]:
133         set_external_id("Bridge", name, "xs-network-uuids",
134                 ids["network-uuids"])
135
136 def update_fail_mode(name):
137     rec = get_network_by_bridge(name)
138
139     if not rec:
140         return
141
142     fail_mode = rec['other_config'].get('vswitch-controller-fail-mode')
143
144     if not fail_mode:
145         pools = session.xenapi.pool.get_all()
146         if len(pools) == 1:
147             prec = session.xenapi.pool.get_record(pools[0])
148             fail_mode = prec['other_config'].get('vswitch-controller-fail-mode')
149
150     if fail_mode not in ['standalone', 'secure']:
151         fail_mode = 'standalone'
152
153     call_vsctl(["set", "bridge", name, "fail_mode=" + fail_mode])
154
155 def update_bridge_id(name, ids):
156     id = get_bridge_id(name, ids.get("xs-network-uuids"))
157
158     if not id:
159         return
160
161     primary_id = id.split(";")[0]
162
163     if ids.get("bridge-id") != primary_id:
164         set_external_id("Bridge", name, "bridge-id", primary_id)
165
166 def update_iface_id(name, ids):
167     id = get_iface_id(name, ids.get("xs-vif-uuid"))
168     if ids.get("iface-id") != id and id:
169         set_external_id("Interface", name, "iface-id", id)
170
171 def keep_table_columns(schema, table_name, column_types):
172     table = schema.tables.get(table_name)
173     if not table:
174         raise error.Error("schema has no %s table" % table_name)
175
176     new_columns = {}
177     for column_name, column_type in column_types.iteritems():
178         column = table.columns.get(column_name)
179         if not column:
180             raise error.Error("%s table schema lacks %s column"
181                               % (table_name, column_name))
182         if column.type != column_type:
183             raise error.Error("%s column in %s table has type \"%s\", "
184                               "expected type \"%s\""
185                               % (column_name, table_name,
186                                  column.type.toEnglish(),
187                                  column_type.toEnglish()))
188         new_columns[column_name] = column
189     table.columns = new_columns
190     return table
191
192 def monitor_uuid_schema_cb(schema):
193     string_type = types.Type(types.BaseType(types.StringType))
194     string_map_type = types.Type(types.BaseType(types.StringType),
195                                  types.BaseType(types.StringType),
196                                  0, sys.maxint)
197
198     new_tables = {}
199     for table_name in ("Bridge", "Interface"):
200         new_tables[table_name] = keep_table_columns(
201             schema, table_name, {"name": string_type,
202                                  "external_ids": string_map_type})
203     schema.tables = new_tables
204
205 def usage():
206     print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
207     print "where DATABASE is a socket on which ovsdb-server is listening."
208     ovs.daemon.usage()
209     print "Other options:"
210     print "  -h, --help               display this help message"
211     sys.exit(0)
212
213 def handler(signum, frame):
214     global force_run
215     if (signum == signal.SIGHUP):
216         force_run = True
217
218 def main(argv):
219     global force_run
220
221     try:
222         options, args = getopt.gnu_getopt(
223             argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
224     except getopt.GetoptError, geo:
225         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
226         sys.exit(1)
227
228     for key, value in options:
229         if key in ['-h', '--help']:
230             usage()
231         elif not ovs.daemon.parse_opt(key, value):
232             sys.stderr.write("%s: unhandled option %s\n"
233                              % (ovs.util.PROGRAM_NAME, key))
234             sys.exit(1)
235
236     if len(args) != 1:
237         sys.stderr.write("%s: exactly one nonoption argument is required "
238                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
239         sys.exit(1)
240
241     ovs.daemon.die_if_already_running()
242
243     remote = args[0]
244     idl = ovs.db.idl.Idl(remote, "Open_vSwitch", monitor_uuid_schema_cb)
245
246     ovs.daemon.daemonize()
247
248     # This daemon is usually started before XAPI, but to complete our
249     # tasks, we need it.  Wait here until it's up.
250     while not os.path.exists("/var/run/xapi_init_complete.cookie"):
251         time.sleep(1)
252
253     signal.signal(signal.SIGHUP, handler)
254
255     bridges = {}
256     interfaces = {}
257     while True:
258         if not force_run and not idl.run():
259             poller = ovs.poller.Poller()
260             idl.wait(poller)
261             poller.block()
262             continue
263
264         if force_run:
265             s_log.info("Forced to re-run as the result of a SIGHUP")
266             bridges    = {}
267             interfaces = {}
268             force_run  = False
269
270         new_bridges = {}
271         for rec in idl.data["Bridge"].itervalues():
272             name = rec.name.as_scalar()
273             xs_network_uuids = rec.external_ids.get("xs-network-uuids")
274             network_uuids = rec.external_ids.get("network-uuids")
275             new_bridges[name] = {"xs-network-uuids": xs_network_uuids,
276                                  "network-uuids": network_uuids}
277
278         new_interfaces = {}
279         for rec in idl.data["Interface"].itervalues():
280             name = rec.name.as_scalar()
281             xs_vif_uuid = rec.external_ids.get("xs-vif-uuid")
282             new_interfaces[name] = {"xs-vif-uuid": xs_vif_uuid}
283
284         if bridges != new_bridges:
285             for name,ids in new_bridges.items():
286                 # Network uuids shouldn't change in the life of a bridge,
287                 # so only check for "network-uuids" on creation.
288                 if name not in bridges:
289                     update_network_uuids(name, ids)
290                     update_fail_mode(name)
291
292                 if (name not in bridges) or (bridges[name] != ids):
293                     update_bridge_id(name, ids)
294
295             bridges = new_bridges
296
297         if interfaces != new_interfaces:
298             for name,ids in new_interfaces.items():
299                 if (name not in interfaces) or (interfaces[name] != ids):
300                     update_iface_id(name, ids)
301             interfaces = new_interfaces
302
303 if __name__ == '__main__':
304     try:
305         main(sys.argv)
306     except SystemExit:
307         # Let system.exit() calls complete normally
308         raise
309     except:
310         s_log.exception("traceback")
311         sys.exit(ovs.daemon.RESTART_EXIT_CODE)