idl: Convert python daemons to utilize SchemaHelper.
[sliver-openvswitch.git] / xenserver / usr_share_openvswitch_scripts_ovs-xapi-sync
1 #!/usr/bin/python
2 # Copyright (c) 2009, 2010, 2011, 2012 Nicira Networks
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
39 vlog = ovs.vlog.Vlog("ovs-xapi-sync")
40 session = None
41 flush_cache = False
42 exiting = 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     for n in session.xenapi.network.get_all():
85         rec = session.xenapi.network.get_record(n)
86         if rec['bridge'] == br_name:
87             return rec
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     external_ids = row.external_ids
166     if set_or_delete(external_ids, key, value):
167         row.external_ids = external_ids
168
169
170 # XenServer does not call interface-reconfigure on internal networks,
171 # which is where the fail-mode would normally be set.
172 def update_fail_mode(row):
173     rec = get_network_by_bridge(row.name)
174     if not rec:
175         return
176
177     fail_mode = rec['other_config'].get('vswitch-controller-fail-mode')
178
179     if not fail_mode:
180         pools = session.xenapi.pool.get_all()
181         if len(pools) == 1:
182             prec = session.xenapi.pool.get_record(pools[0])
183             fail_mode = prec['other_config'].get(
184                     'vswitch-controller-fail-mode')
185
186     if fail_mode not in ['standalone', 'secure']:
187         fail_mode = 'standalone'
188
189     if row.fail_mode != fail_mode:
190         row.fail_mode = fail_mode
191
192
193 def update_in_band_mgmt(row):
194     rec = get_network_by_bridge(row.name)
195     if not rec:
196         return
197
198     dib = rec['other_config'].get('vswitch-disable-in-band')
199
200     other_config = row.other_config
201     if dib and dib not in ['true', 'false']:
202         vlog.warn('"%s" isn\'t a valid setting for '
203                 "other_config:disable-in-band on %s" % (dib, row.name))
204     elif set_or_delete(other_config, 'disable-in-band', dib):
205         row.other_config = other_config
206
207
208 def main():
209     global flush_cache
210
211     parser = argparse.ArgumentParser()
212     parser.add_argument("database", metavar="DATABASE",
213             help="A socket on which ovsdb-server is listening.")
214     parser.add_argument("--root-prefix", metavar="DIR", default='',
215                         help="Use DIR as alternate root directory"
216                         " (for testing).")
217
218     ovs.vlog.add_args(parser)
219     ovs.daemon.add_args(parser)
220     args = parser.parse_args()
221     ovs.vlog.handle_args(args)
222     ovs.daemon.handle_args(args)
223
224     remote = args.database
225     schema_helper = ovs.db.idl.SchemaHelper()
226     schema_helper.register_columns("Bridge", ["name", "external_ids",
227                                               "other_config", "fail_mode"])
228     schema_helper.register_columns("Interface", ["name", "external_ids"])
229     idl = ovs.db.idl.Idl(remote, schema_helper)
230
231     ovs.daemon.daemonize()
232
233     ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None)
234     ovs.unixctl.command_register("flush-cache", "", 0, 0, unixctl_flush_cache,
235                                  None)
236     error, unixctl_server = ovs.unixctl.UnixctlServer.create(None)
237     if error:
238         ovs.util.ovs_fatal(error, "could not create unixctl server", vlog)
239
240     # This daemon is usually started before XAPI, but to complete our
241     # tasks, we need it.  Wait here until it's up.
242     cookie_file = args.root_prefix + "/var/run/xapi_init_complete.cookie"
243     while not os.path.exists(cookie_file):
244         time.sleep(1)
245
246     bridges = {}                # Map from bridge name to nicira-bridge-id
247     iface_ids = {}              # Map from xs-vif-uuid to iface-id
248     vm_ids = {}                 # Map from xs-vm-uuid to vm-id
249     seqno = idl.change_seqno    # Sequence number when we last processed the db
250     while True:
251         unixctl_server.run()
252         if exiting:
253             break;
254
255         idl.run()
256         if not flush_cache and seqno == idl.change_seqno:
257             poller = ovs.poller.Poller()
258             unixctl_server.wait(poller)
259             idl.wait(poller)
260             poller.block()
261             continue
262
263         if flush_cache:
264             vlog.info("Flushing cache as the result of unixctl.")
265             bridges = {}
266             iface_ids = {}
267             vm_ids = {}
268             flush_cache = False
269         seqno = idl.change_seqno
270
271         txn = ovs.db.idl.Transaction(idl)
272
273         new_bridges = {}
274         for row in idl.tables["Bridge"].rows.itervalues():
275             if row.name in bridges:
276                 nbd = bridges[row.name]
277             else:
278                 # New bridge.
279                 update_fail_mode(row)
280                 update_in_band_mgmt(row)
281                 nbd = get_bridge_id(row.name)
282
283             bridge_id = nbd
284             if bridge_id is None:
285                 bridge_id = row.external_ids.get("xs-network-uuids")
286
287             if bridge_id is not None:
288                 set_external_id(row, "bridge-id", bridge_id.split(";")[0])
289
290             new_bridges[row.name] = nbd
291         bridges = new_bridges
292
293         iface_by_name = {}
294         for row in idl.tables["Interface"].rows.itervalues():
295             iface_by_name[row.name] = row
296
297         new_iface_ids = {}
298         new_vm_ids = {}
299         for row in idl.tables["Interface"].rows.itervalues():
300             # Match up paired vif and tap devices.
301             if row.name.startswith("vif"):
302                 vif = row
303                 tap = iface_by_name.get("tap%s" % row.name[3:])
304             elif row.name.startswith("tap"):
305                 tap = row
306                 vif = iface_by_name.get("vif%s" % row.name[3:])
307             else:
308                 tap = vif = None
309
310             # Several tap external-ids need to be copied from the vif.
311             if row == tap and vif:
312                 keys = ["attached-mac",
313                         "xs-network-uuid",
314                         "xs-vif-uuid",
315                         "xs-vm-uuid"]
316                 for k in keys:
317                     set_external_id(row, k, vif.external_ids.get(k))
318
319             # Map from xs-vif-uuid to iface-id.
320             #
321             # (A tap's xs-vif-uuid comes from its vif.  That falls out
322             # naturally from the copy loop above.)
323             xvu = row.external_ids.get("xs-vif-uuid")
324             if xvu:
325                 iface_id = (new_iface_ids.get(xvu)
326                             or iface_ids.get(xvu)
327                             or get_iface_id(row.name, xvu))
328                 new_iface_ids[xvu] = iface_id
329             else:
330                 # No xs-vif-uuid therefore no iface-id.
331                 iface_id = None
332             set_external_id(row, "iface-id", iface_id)
333
334             # Map from xs-vm-uuid to vm-id.
335             xvmu = row.external_ids.get("xs-vm-uuid")
336             if xvmu:
337                 vm_id = (new_vm_ids.get(xvmu)
338                          or vm_ids.get(xvmu)
339                          or get_vm_id(row.name, xvmu))
340                 new_vm_ids[xvmu] = vm_id
341             else:
342                 vm_id = None
343             set_external_id(row, "vm-id", vm_id)
344
345             # When there's a vif and a tap, the tap is active (used for
346             # traffic).  When there's just a vif, the vif is active.
347             #
348             # A tap on its own shouldn't happen, and we don't know
349             # anything about other kinds of devices, so we don't use
350             # an iface-status for those devices at all.
351             if vif and tap:
352                 set_external_id(tap, "iface-status", "active")
353                 set_external_id(vif, "iface-status", "inactive")
354             elif vif:
355                 set_external_id(vif, "iface-status", "active")
356             else:
357                 set_external_id(row, "iface-status", None)
358         iface_ids = new_iface_ids
359         vm_ids = new_vm_ids
360
361         txn.commit_block()
362
363     unixctl_server.close()
364     idl.close()
365
366
367 if __name__ == '__main__':
368     try:
369         main()
370     except SystemExit:
371         # Let system.exit() calls complete normally
372         raise
373     except:
374         vlog.exception("traceback")
375         sys.exit(ovs.daemon.RESTART_EXIT_CODE)