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