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