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