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