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