eea319a8197c30e93a540831a8bd65d9eb131d4a
[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 keep_table_columns(schema, table_name, columns):
209     table = schema.tables.get(table_name)
210     if not table:
211         raise error.Error("schema has no %s table" % table_name)
212
213     new_columns = {}
214     for column_name in columns:
215         column = table.columns.get(column_name)
216         if not column:
217             raise error.Error("%s table schema lacks %s column"
218                               % (table_name, column_name))
219         new_columns[column_name] = column
220     table.columns = new_columns
221     return table
222
223
224 def prune_schema(schema):
225     new_tables = {}
226     new_tables["Bridge"] = keep_table_columns(
227         schema, "Bridge", ("name", "external_ids", "other_config",
228                            "fail_mode"))
229     new_tables["Interface"] = keep_table_columns(
230         schema, "Interface", ("name", "external_ids"))
231     schema.tables = new_tables
232
233
234 def main():
235     global flush_cache
236
237     parser = argparse.ArgumentParser()
238     parser.add_argument("database", metavar="DATABASE",
239             help="A socket on which ovsdb-server is listening.")
240     parser.add_argument("--root-prefix", metavar="DIR", default='',
241                         help="Use DIR as alternate root directory"
242                         " (for testing).")
243
244     ovs.vlog.add_args(parser)
245     ovs.daemon.add_args(parser)
246     args = parser.parse_args()
247     ovs.vlog.handle_args(args)
248     ovs.daemon.handle_args(args)
249
250     remote = args.database
251     schema_file = "%s/vswitch.ovsschema" % ovs.dirs.PKGDATADIR
252     schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schema_file))
253     prune_schema(schema)
254     idl = ovs.db.idl.Idl(remote, schema)
255
256     ovs.daemon.daemonize()
257
258     ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None)
259     ovs.unixctl.command_register("flush-cache", "", 0, 0, unixctl_flush_cache,
260                                  None)
261     error, unixctl_server = ovs.unixctl.UnixctlServer.create(None)
262     if error:
263         ovs.util.ovs_fatal(error, "could not create unixctl server", vlog)
264
265     # This daemon is usually started before XAPI, but to complete our
266     # tasks, we need it.  Wait here until it's up.
267     cookie_file = args.root_prefix + "/var/run/xapi_init_complete.cookie"
268     while not os.path.exists(cookie_file):
269         time.sleep(1)
270
271     bridges = {}                # Map from bridge name to nicira-bridge-id
272     iface_ids = {}              # Map from xs-vif-uuid to iface-id
273     vm_ids = {}                 # Map from xs-vm-uuid to vm-id
274     seqno = idl.change_seqno    # Sequence number when we last processed the db
275     while True:
276         unixctl_server.run()
277         if exiting:
278             break;
279
280         idl.run()
281         if not flush_cache and seqno == idl.change_seqno:
282             poller = ovs.poller.Poller()
283             unixctl_server.wait(poller)
284             idl.wait(poller)
285             poller.block()
286             continue
287
288         if flush_cache:
289             vlog.info("Flushing cache as the result of unixctl.")
290             bridges = {}
291             iface_ids = {}
292             vm_ids = {}
293             flush_cache = False
294         seqno = idl.change_seqno
295
296         txn = ovs.db.idl.Transaction(idl)
297
298         new_bridges = {}
299         for row in idl.tables["Bridge"].rows.itervalues():
300             if row.name in bridges:
301                 nbd = bridges[row.name]
302             else:
303                 # New bridge.
304                 update_fail_mode(row)
305                 update_in_band_mgmt(row)
306                 nbd = get_bridge_id(row.name)
307
308             bridge_id = nbd
309             if bridge_id is None:
310                 bridge_id = row.external_ids.get("xs-network-uuids")
311
312             if bridge_id is not None:
313                 set_external_id(row, "bridge-id", bridge_id.split(";")[0])
314
315             new_bridges[row.name] = nbd
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.commit_block()
387
388     unixctl_server.close()
389     idl.close()
390
391
392 if __name__ == '__main__':
393     try:
394         main()
395     except SystemExit:
396         # Let system.exit() calls complete normally
397         raise
398     except:
399         vlog.exception("traceback")
400         sys.exit(ovs.daemon.RESTART_EXIT_CODE)