vlog: Take advantage of relaxed "-v" syntax through the tree.
[sliver-openvswitch.git] / xenserver / usr_share_openvswitch_scripts_ovs-xapi-sync
1 #!/usr/bin/python
2 # Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
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 argparse
26 import os
27 import sys
28 import time
29
30 import XenAPI
31
32 import ovs.dirs
33 from ovs.db import error
34 from ovs.db import types
35 import ovs.daemon
36 import ovs.db.idl
37 import ovs.unixctl
38 import ovs.unixctl.server
39
40 vlog = ovs.vlog.Vlog("ovs-xapi-sync")
41 session = None
42 flush_cache = False
43 exiting = False
44
45
46 def unixctl_exit(conn, unused_argv, unused_aux):
47     global exiting
48     exiting = True
49     conn.reply(None)
50
51
52 def unixctl_flush_cache(conn, unused_argv, unused_aux):
53     global flush_cache
54     flush_cache = True
55     conn.reply(None)
56
57
58 # Set up a session to interact with XAPI.
59 #
60 # On system start-up, OVS comes up before XAPI, so we can't log into the
61 # session until later.  Try to do this on-demand, since we won't
62 # actually do anything interesting until XAPI is up.
63 def init_session():
64     global session
65     if session is not None:
66         return True
67
68     try:
69         session = XenAPI.xapi_local()
70         session.xenapi.login_with_password("", "")
71     except XenAPI.Failure, e:
72         session = None
73         vlog.warn("Couldn't login to XAPI (%s)" % e)
74         return False
75
76     return True
77
78
79 def get_network_by_bridge(br_name):
80     if not init_session():
81         vlog.warn("Failed to get bridge id %s because"
82                 " XAPI session could not be initialized" % br_name)
83         return None
84
85     for n in session.xenapi.network.get_all():
86         rec = session.xenapi.network.get_record(n)
87         if rec['bridge'] == br_name:
88             return rec
89
90     return None
91
92
93 # By default, the "bridge-id" external id in the Bridge table is the
94 # same as "xs-network-uuids".  This may be overridden by defining a
95 # "nicira-bridge-id" key in the "other_config" field of the network
96 # record of XAPI.  If nicira-bridge-id is undefined returns default.
97 # On error returns None.
98 def get_bridge_id(br_name, default=None):
99     rec = get_network_by_bridge(br_name)
100     if rec:
101         return rec['other_config'].get('nicira-bridge-id', default)
102     return None
103
104
105 # By default, the "iface-id" external id in the Interface table is the
106 # same as "xs-vif-uuid".  This may be overridden by defining a
107 # "nicira-iface-id" key in the "other_config" field of the VIF
108 # record of XAPI.
109 def get_iface_id(if_name, xs_vif_uuid):
110     if not if_name.startswith("vif") and not if_name.startswith("tap"):
111         # Treat whatever was passed into 'xs_vif_uuid' as a default
112         # value for non-VIFs.
113         return xs_vif_uuid
114
115     if not init_session():
116         vlog.warn("Failed to get interface id %s because"
117                 " XAPI session could not be initialized" % if_name)
118         return xs_vif_uuid
119
120     try:
121         vif = session.xenapi.VIF.get_by_uuid(xs_vif_uuid)
122         rec = session.xenapi.VIF.get_record(vif)
123         return rec['other_config'].get('nicira-iface-id', xs_vif_uuid)
124     except XenAPI.Failure:
125         vlog.warn("Could not find XAPI entry for VIF %s" % if_name)
126         return xs_vif_uuid
127
128
129 # By default, the "vm-id" external id in the Interface table is the
130 # same as "xs-vm-uuid".  This may be overridden by defining a
131 # "nicira-vm-id" key in the "other_config" field of the VM
132 # record of XAPI.
133 def get_vm_id(if_name, xs_vm_uuid):
134     if not if_name.startswith("vif") and not if_name.startswith("tap"):
135         # Treat whatever was passed into 'xs_vm_uuid' as a default
136         # value for non-VIFs.
137         return xs_vm_uuid
138
139     if not init_session():
140         vlog.warn("Failed to get vm id for interface id %s because"
141                 " XAPI session could not be initialized" % if_name)
142         return xs_vm_uuid
143
144     try:
145         vm = session.xenapi.VM.get_by_uuid(xs_vm_uuid)
146         rec = session.xenapi.VM.get_record(vm)
147         return rec['other_config'].get('nicira-vm-id', xs_vm_uuid)
148     except XenAPI.Failure:
149         vlog.warn("Could not find XAPI entry for VIF %s" % if_name)
150         return xs_vm_uuid
151
152
153 def set_or_delete(d, key, value):
154     if value is None:
155         if key in d:
156             del d[key]
157             return True
158     else:
159         if d.get(key) != value:
160             d[key] = value
161             return True
162     return False
163
164
165 def set_external_id(row, key, value):
166     row.verify("external_ids")
167     external_ids = row.external_ids
168     if set_or_delete(external_ids, key, value):
169         row.external_ids = external_ids
170
171
172 # XenServer does not call interface-reconfigure on internal networks,
173 # which is where the fail-mode would normally be set.
174 def update_fail_mode(row):
175     rec = get_network_by_bridge(row.name)
176     if not rec:
177         return
178
179     fail_mode = rec['other_config'].get('vswitch-controller-fail-mode')
180
181     if not fail_mode:
182         pools = session.xenapi.pool.get_all()
183         if len(pools) == 1:
184             prec = session.xenapi.pool.get_record(pools[0])
185             fail_mode = prec['other_config'].get(
186                     'vswitch-controller-fail-mode')
187
188     if fail_mode not in ['standalone', 'secure']:
189         fail_mode = 'standalone'
190
191     row.verify("fail_mode")
192     if row.fail_mode != fail_mode:
193         row.fail_mode = fail_mode
194
195
196 def update_in_band_mgmt(row):
197     rec = get_network_by_bridge(row.name)
198     if not rec:
199         return
200
201     dib = rec['other_config'].get('vswitch-disable-in-band')
202
203     row.verify("other_config")
204     other_config = row.other_config
205     if dib and dib not in ['true', 'false']:
206         vlog.warn('"%s" isn\'t a valid setting for '
207                 "other_config:disable-in-band on %s" % (dib, row.name))
208     elif set_or_delete(other_config, 'disable-in-band', dib):
209         row.other_config = other_config
210
211
212 def main():
213     global flush_cache
214
215     parser = argparse.ArgumentParser()
216     parser.add_argument("database", metavar="DATABASE",
217             help="A socket on which ovsdb-server is listening.")
218     parser.add_argument("--root-prefix", metavar="DIR", default='',
219                         help="Use DIR as alternate root directory"
220                         " (for testing).")
221
222     ovs.vlog.add_args(parser)
223     ovs.daemon.add_args(parser)
224     args = parser.parse_args()
225     ovs.vlog.handle_args(args)
226     ovs.daemon.handle_args(args)
227
228     remote = args.database
229     schema_helper = ovs.db.idl.SchemaHelper()
230     schema_helper.register_columns("Bridge", ["name", "external_ids",
231                                               "other_config", "fail_mode"])
232     schema_helper.register_columns("Interface", ["name", "external_ids"])
233     idl = ovs.db.idl.Idl(remote, schema_helper)
234
235     ovs.daemon.daemonize()
236
237     ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None)
238     ovs.unixctl.command_register("flush-cache", "", 0, 0, unixctl_flush_cache,
239                                  None)
240     error, unixctl_server = ovs.unixctl.server.UnixctlServer.create(None)
241     if error:
242         ovs.util.ovs_fatal(error, "could not create unixctl server", vlog)
243
244     # This daemon is usually started before XAPI, but to complete our
245     # tasks, we need it.  Wait here until it's up.
246     cookie_file = args.root_prefix + "/var/run/xapi_init_complete.cookie"
247     while not os.path.exists(cookie_file):
248         time.sleep(1)
249
250     bridges = {}                # Map from bridge name to nicira-bridge-id
251     iface_ids = {}              # Map from xs-vif-uuid to iface-id
252     vm_ids = {}                 # Map from xs-vm-uuid to vm-id
253     seqno = idl.change_seqno    # Sequence number when we last processed the db
254     while True:
255         unixctl_server.run()
256         if exiting:
257             break;
258
259         idl.run()
260         if not flush_cache and seqno == idl.change_seqno:
261             poller = ovs.poller.Poller()
262             unixctl_server.wait(poller)
263             idl.wait(poller)
264             poller.block()
265             continue
266
267         if flush_cache:
268             vlog.info("Flushing cache as the result of unixctl.")
269             bridges = {}
270             iface_ids = {}
271             vm_ids = {}
272             flush_cache = False
273         seqno = idl.change_seqno
274
275         txn = ovs.db.idl.Transaction(idl)
276
277         new_bridges = {}
278         for row in idl.tables["Bridge"].rows.itervalues():
279             if row.name in bridges:
280                 nbd = bridges[row.name]
281             else:
282                 # New bridge.
283                 update_fail_mode(row)
284                 update_in_band_mgmt(row)
285                 nbd = get_bridge_id(row.name)
286
287             bridge_id = nbd
288             if bridge_id is None:
289                 bridge_id = row.external_ids.get("xs-network-uuids")
290
291             if bridge_id is not None:
292                 set_external_id(row, "bridge-id", bridge_id.split(";")[0])
293
294             new_bridges[row.name] = nbd
295         bridges = new_bridges
296
297         iface_by_name = {}
298         for row in idl.tables["Interface"].rows.itervalues():
299             iface_by_name[row.name] = row
300
301         new_iface_ids = {}
302         new_vm_ids = {}
303         for row in idl.tables["Interface"].rows.itervalues():
304             # Match up paired vif and tap devices.
305             if row.name.startswith("vif"):
306                 vif = row
307                 tap = iface_by_name.get("tap%s" % row.name[3:])
308             elif row.name.startswith("tap"):
309                 tap = row
310                 vif = iface_by_name.get("vif%s" % row.name[3:])
311             else:
312                 tap = vif = None
313
314             # Several tap external-ids need to be copied from the vif.
315             if row == tap and vif:
316                 keys = ["attached-mac",
317                         "xs-network-uuid",
318                         "xs-vif-uuid",
319                         "xs-vm-uuid"]
320                 for k in keys:
321                     set_external_id(row, k, vif.external_ids.get(k))
322
323             # Map from xs-vif-uuid to iface-id.
324             #
325             # (A tap's xs-vif-uuid comes from its vif.  That falls out
326             # naturally from the copy loop above.)
327             xvu = row.external_ids.get("xs-vif-uuid")
328             if xvu:
329                 iface_id = (new_iface_ids.get(xvu)
330                             or iface_ids.get(xvu)
331                             or get_iface_id(row.name, xvu))
332                 new_iface_ids[xvu] = iface_id
333             else:
334                 # No xs-vif-uuid therefore no iface-id.
335                 iface_id = None
336             set_external_id(row, "iface-id", iface_id)
337
338             # Map from xs-vm-uuid to vm-id.
339             xvmu = row.external_ids.get("xs-vm-uuid")
340             if xvmu:
341                 vm_id = (new_vm_ids.get(xvmu)
342                          or vm_ids.get(xvmu)
343                          or get_vm_id(row.name, xvmu))
344                 new_vm_ids[xvmu] = vm_id
345             else:
346                 vm_id = None
347             set_external_id(row, "vm-id", vm_id)
348
349             # When there's a vif and a tap, the tap is active (used for
350             # traffic).  When there's just a vif, the vif is active.
351             #
352             # A tap on its own shouldn't happen, and we don't know
353             # anything about other kinds of devices, so we don't use
354             # an iface-status for those devices at all.
355             if vif and tap:
356                 set_external_id(tap, "iface-status", "active")
357                 set_external_id(vif, "iface-status", "inactive")
358             elif vif:
359                 set_external_id(vif, "iface-status", "active")
360             else:
361                 set_external_id(row, "iface-status", None)
362         iface_ids = new_iface_ids
363         vm_ids = new_vm_ids
364
365         txn.add_comment("ovs-xapi-sync: Updating records from XAPI")
366         txn.commit_block()
367
368     unixctl_server.close()
369     idl.close()
370
371
372 if __name__ == '__main__':
373     try:
374         main()
375     except SystemExit:
376         # Let system.exit() calls complete normally
377         raise
378     except:
379         vlog.exception("traceback")
380         sys.exit(ovs.daemon.RESTART_EXIT_CODE)