vlog: Add a new log level "off".
[sliver-openvswitch.git] / xenserver / usr_share_openvswitch_scripts_ovs-xapi-sync
1 #!/usr/bin/python
2 # Copyright (c) 2009, 2010, 2011 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 for changes that require interrogating XAPI.
19 # Its responsibilities include:
20 #
21 #   - Set the "bridge-id" key in the Bridge table.
22 #   - Set the "iface-id" key in the Interface table.
23 #   - Set the fail-mode on internal bridges.
24
25 import getopt
26 import logging, logging.handlers
27 import os
28 import signal
29 import subprocess
30 import sys
31 import time
32
33 import XenAPI
34
35 from ovs.db import error
36 from ovs.db import types
37 import ovs.util
38 import ovs.daemon
39 import ovs.db.idl
40
41 s_log     = logging.getLogger("ovs-xapi-sync")
42 l_handler = logging.handlers.RotatingFileHandler(
43         "/var/log/openvswitch/ovs-xapi-sync.log")
44 l_formatter = logging.Formatter('%(filename)s: %(levelname)s: %(message)s')
45 l_handler.setFormatter(l_formatter)
46 s_log.addHandler(l_handler)
47 s_log.setLevel(logging.INFO)
48
49 vsctl="/usr/bin/ovs-vsctl"
50 session = None
51 force_run = False
52
53 # Set up a session to interact with XAPI.
54 #
55 # On system start-up, OVS comes up before XAPI, so we can't log into the
56 # session until later.  Try to do this on-demand, since we won't
57 # actually do anything interesting until XAPI is up.
58 def init_session():
59     global session
60     if session is not None:
61         return True
62
63     try:
64         session = XenAPI.xapi_local()
65         session.xenapi.login_with_password("", "")
66     except:
67         session = None
68         s_log.warning("Couldn't login to XAPI")
69         return False
70
71     return True
72
73 def get_network_by_bridge(br_name):
74     if not init_session():
75         s_log.warning("Failed to get bridge id %s because"
76                 " XAPI session could not be initialized" % br_name)
77         return None
78
79     for n in session.xenapi.network.get_all():
80         rec = session.xenapi.network.get_record(n)
81         if rec['bridge'] == br_name:
82             return rec
83
84     return None
85
86 # By default, the "bridge-id" external id in the Bridge table is the
87 # same as "xs-network-uuids".  This may be overridden by defining a
88 # "nicira-bridge-id" key in the "other_config" field of the network
89 # record of XAPI.  If nicira-bridge-id is undefined returns default.
90 # On error returns None.
91 def get_bridge_id(br_name, default=None):
92     rec = get_network_by_bridge(br_name)
93     if rec:
94         return rec['other_config'].get('nicira-bridge-id', default)
95     return None
96
97 # By default, the "iface-id" external id in the Interface table is the
98 # same as "xs-vif-uuid".  This may be overridden by defining a
99 # "nicira-iface-id" key in the "other_config" field of the VIF
100 # record of XAPI.
101 def get_iface_id(if_name, xs_vif_uuid):
102     if not if_name.startswith("vif") and not if_name.startswith("tap"):
103         # Treat whatever was passed into 'xs_vif_uuid' as a default
104         # value for non-VIFs.
105         return xs_vif_uuid
106
107     if not init_session():
108         s_log.warning("Failed to get interface id %s because"
109                 " XAPI session could not be initialized" % if_name)
110         return xs_vif_uuid
111
112     try:
113         vif = session.xenapi.VIF.get_by_uuid(xs_vif_uuid)
114         rec = session.xenapi.VIF.get_record(vif)
115         return rec['other_config'].get('nicira-iface-id', xs_vif_uuid)
116     except XenAPI.Failure:
117         s_log.warning("Could not find XAPI entry for VIF %s" % if_name)
118         return xs_vif_uuid
119
120 def call_vsctl(args):
121     cmd = [vsctl, "--timeout=30", "-vANY:console:off"] + args
122     exitcode = subprocess.call(cmd)
123     if exitcode != 0:
124         s_log.warning("Couldn't call ovs-vsctl")
125
126 def set_external_id(table, record, key, value):
127     if value:
128         col = 'external-ids:"%s"="%s"' % (key, value)
129         call_vsctl(["set", table, record, col])
130     else:
131         call_vsctl(["remove", table, record, "external-ids", key])
132
133
134 # XenServer does not call interface-reconfigure on internal networks,
135 # which is where the fail-mode would normally be set.
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_in_band_mgmt(name):
156     rec = get_network_by_bridge(name)
157
158     if not rec:
159         return
160
161     dib = rec['other_config'].get('vswitch-disable-in-band')
162     if not dib:
163         call_vsctl(['remove', 'bridge', name, 'other_config',
164                     'disable-in-band'])
165     elif dib in ['true', 'false']:
166         call_vsctl(['set', 'bridge', name,
167                     'other_config:disable-in-band=' + dib])
168     else:
169         s_log.warning('"' + dib + '"'
170                       "isn't a valid setting for other_config:disable-in-band on " +
171                       name)
172
173 def update_bridge_id(name, ids):
174     id = get_bridge_id(name, ids.get("xs-network-uuids"))
175
176     if not id:
177         return
178
179     primary_id = id.split(";")[0]
180
181     if ids.get("bridge-id") != primary_id:
182         set_external_id("Bridge", name, "bridge-id", primary_id)
183         ids["bridge-id"] = primary_id
184
185 def update_iface(name, ids):
186     id = get_iface_id(name, ids.get("xs-vif-uuid"))
187     if ids.get("iface-id") != id and id:
188         set_external_id("Interface", name, "iface-id", id)
189         ids["iface-id"] = id
190
191     status = ids.get("iface-status")
192     if status:
193         set_external_id("Interface", name, "iface-status", status)
194
195 def keep_table_columns(schema, table_name, column_types):
196     table = schema.tables.get(table_name)
197     if not table:
198         raise error.Error("schema has no %s table" % table_name)
199
200     new_columns = {}
201     for column_name, column_type in column_types.iteritems():
202         column = table.columns.get(column_name)
203         if not column:
204             raise error.Error("%s table schema lacks %s column"
205                               % (table_name, column_name))
206         if column.type != column_type:
207             raise error.Error("%s column in %s table has type \"%s\", "
208                               "expected type \"%s\""
209                               % (column_name, table_name,
210                                  column.type.toEnglish(),
211                                  column_type.toEnglish()))
212         new_columns[column_name] = column
213     table.columns = new_columns
214     return table
215
216 def monitor_uuid_schema_cb(schema):
217     string_type = types.Type(types.BaseType(types.StringType))
218     string_map_type = types.Type(types.BaseType(types.StringType),
219                                  types.BaseType(types.StringType),
220                                  0, sys.maxint)
221
222     new_tables = {}
223     for table_name in ("Bridge", "Interface"):
224         new_tables[table_name] = keep_table_columns(
225             schema, table_name, {"name": string_type,
226                                  "external_ids": string_map_type})
227     schema.tables = new_tables
228
229 def usage():
230     print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
231     print "where DATABASE is a socket on which ovsdb-server is listening."
232     ovs.daemon.usage()
233     print "Other options:"
234     print "  -h, --help               display this help message"
235     sys.exit(0)
236
237 def handler(signum, frame):
238     global force_run
239     if (signum == signal.SIGHUP):
240         force_run = True
241
242 def update_tap_from_vif(idl, tap_name, vif_name):
243     ifaces = idl.data["Interface"]
244     tap = None
245     vif = None
246
247     for i in ifaces.values():
248         name = i.name.as_scalar().strip('"')
249         if name == tap_name:
250             tap = i
251         elif name == vif_name:
252             vif = i
253
254     if vif and tap:
255         vxid = vif.external_ids
256         txid = tap.external_ids
257
258         keys = ["attached-mac", "xs-network-uuid", "xs-vif-uuid", "xs-vm-uuid"]
259         for k in keys:
260             if vxid.get(k) != txid.get(k):
261                 set_external_id("Interface", tap_name, k, vxid.get(k))
262
263 def main(argv):
264     global force_run
265
266     try:
267         options, args = getopt.gnu_getopt(
268             argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
269     except getopt.GetoptError, geo:
270         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
271         sys.exit(1)
272
273     for key, value in options:
274         if key in ['-h', '--help']:
275             usage()
276         elif not ovs.daemon.parse_opt(key, value):
277             sys.stderr.write("%s: unhandled option %s\n"
278                              % (ovs.util.PROGRAM_NAME, key))
279             sys.exit(1)
280
281     if len(args) != 1:
282         sys.stderr.write("%s: exactly one nonoption argument is required "
283                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
284         sys.exit(1)
285
286     remote = args[0]
287     idl = ovs.db.idl.Idl(remote, "Open_vSwitch", monitor_uuid_schema_cb)
288
289     ovs.daemon.daemonize()
290
291     # This daemon is usually started before XAPI, but to complete our
292     # tasks, we need it.  Wait here until it's up.
293     while not os.path.exists("/var/run/xapi_init_complete.cookie"):
294         time.sleep(1)
295
296     signal.signal(signal.SIGHUP, handler)
297
298     bridges = {}
299     interfaces = {}
300     while True:
301         if not force_run and not idl.run():
302             poller = ovs.poller.Poller()
303             idl.wait(poller)
304             poller.block()
305             continue
306
307         if force_run:
308             s_log.info("Forced to re-run as the result of a SIGHUP")
309             bridges    = {}
310             interfaces = {}
311             force_run  = False
312
313         new_bridges = {}
314         for rec in idl.data["Bridge"].itervalues():
315             name = rec.name.as_scalar()
316             xs_network_uuids = rec.external_ids.get("xs-network-uuids")
317             bridge_id = rec.external_ids.get("bridge-id")
318             new_bridges[name] = {"xs-network-uuids": xs_network_uuids,
319                                  "bridge-id": bridge_id}
320
321         new_interfaces = {}
322         for rec in idl.data["Interface"].itervalues():
323             name = rec.name.as_scalar()
324             xs_vif_uuid = rec.external_ids.get("xs-vif-uuid")
325             iface_id = rec.external_ids.get("iface-id")
326             new_interfaces[name] = {"xs-vif-uuid": xs_vif_uuid,
327                                     "iface-id": iface_id}
328
329             if name.startswith("vif"):
330                 new_interfaces[name]["iface-status"] = "active"
331
332         #Tap devices take their xs-vif-uuid from their corresponding vif and
333         #cause that vif to be labled inactive.
334         for name in new_interfaces:
335             if not name.startswith("tap"):
336                 continue
337
338             vif = name.replace("tap", "vif", 1)
339
340             if vif in new_interfaces:
341                 xs_vif_uuid = new_interfaces[vif]["xs-vif-uuid"]
342                 new_interfaces[name]["xs-vif-uuid"] = xs_vif_uuid
343
344                 new_interfaces[vif]["iface-status"] = "inactive"
345                 new_interfaces[name]["iface-status"] = "active"
346
347                 update_tap_from_vif(idl, name, vif)
348
349         if bridges != new_bridges:
350             for name,ids in new_bridges.items():
351                 if name not in bridges:
352                     update_fail_mode(name)
353                     update_in_band_mgmt(name)
354
355                 if (name not in bridges) or (bridges[name] != ids):
356                     update_bridge_id(name, ids)
357
358             bridges = new_bridges
359
360         if interfaces != new_interfaces:
361             for name,ids in new_interfaces.items():
362                 if (name not in interfaces) or (interfaces[name] != ids):
363                     update_iface(name, ids)
364             interfaces = new_interfaces
365
366 if __name__ == '__main__':
367     try:
368         main(sys.argv)
369     except SystemExit:
370         # Let system.exit() calls complete normally
371         raise
372     except:
373         s_log.exception("traceback")
374         sys.exit(ovs.daemon.RESTART_EXIT_CODE)