xenserver: Synchronize scripts with XenServer 6.0.0.
[sliver-openvswitch.git] / xenserver / usr_share_openvswitch_scripts_ovs-xapi-sync
1 #!/usr/bin/python
2 # Copyright (c) 2009, 2010, 2011 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 signal
28 import subprocess
29 import sys
30 import time
31
32 import XenAPI
33
34 import ovs.dirs
35 from ovs.db import error
36 from ovs.db import types
37 import ovs.util
38 import ovs.daemon
39 import ovs.db.idl
40
41 root_prefix = ''                # Prefix for absolute file names, for testing.
42 vlog = ovs.vlog.Vlog("ovs-xapi-sync")
43 vsctl = "/usr/bin/ovs-vsctl"
44 session = None
45 force_run = False
46
47
48 # Set up a session to interact with XAPI.
49 #
50 # On system start-up, OVS comes up before XAPI, so we can't log into the
51 # session until later.  Try to do this on-demand, since we won't
52 # actually do anything interesting until XAPI is up.
53 def init_session():
54     global session
55     if session is not None:
56         return True
57
58     try:
59         session = XenAPI.xapi_local()
60         session.xenapi.login_with_password("", "")
61     except XenAPI.Failure, e:
62         session = None
63         vlog.warn("Couldn't login to XAPI (%s)" % e)
64         return False
65
66     return True
67
68
69 def get_network_by_bridge(br_name):
70     if not init_session():
71         vlog.warn("Failed to get bridge id %s because"
72                 " XAPI session could not be initialized" % br_name)
73         return None
74
75     for n in session.xenapi.network.get_all():
76         rec = session.xenapi.network.get_record(n)
77         if rec['bridge'] == br_name:
78             return rec
79
80     return None
81
82
83 # By default, the "bridge-id" external id in the Bridge table is the
84 # same as "xs-network-uuids".  This may be overridden by defining a
85 # "nicira-bridge-id" key in the "other_config" field of the network
86 # record of XAPI.  If nicira-bridge-id is undefined returns default.
87 # On error returns None.
88 def get_bridge_id(br_name, default=None):
89     rec = get_network_by_bridge(br_name)
90     if rec:
91         return rec['other_config'].get('nicira-bridge-id', default)
92     return None
93
94
95 # By default, the "iface-id" external id in the Interface table is the
96 # same as "xs-vif-uuid".  This may be overridden by defining a
97 # "nicira-iface-id" key in the "other_config" field of the VIF
98 # record of XAPI.
99 def get_iface_id(if_name, xs_vif_uuid):
100     if not if_name.startswith("vif") and not if_name.startswith("tap"):
101         # Treat whatever was passed into 'xs_vif_uuid' as a default
102         # value for non-VIFs.
103         return xs_vif_uuid
104
105     if not init_session():
106         vlog.warn("Failed to get interface id %s because"
107                 " XAPI session could not be initialized" % if_name)
108         return xs_vif_uuid
109
110     try:
111         vif = session.xenapi.VIF.get_by_uuid(xs_vif_uuid)
112         rec = session.xenapi.VIF.get_record(vif)
113         return rec['other_config'].get('nicira-iface-id', xs_vif_uuid)
114     except XenAPI.Failure:
115         vlog.warn("Could not find XAPI entry for VIF %s" % if_name)
116         return xs_vif_uuid
117
118
119 def call_vsctl(args):
120     cmd = [vsctl, "--timeout=30", "-vANY:console:off"] + args
121     exitcode = subprocess.call(cmd)
122     if exitcode != 0:
123         vlog.warn("Couldn't call ovs-vsctl")
124
125
126 def set_or_delete(d, key, value):
127     if value is None:
128         if key in d:
129             del d[key]
130             return True
131     else:
132         if d.get(key) != value:
133             d[key] = value
134             return True
135     return False
136
137
138 def set_external_id(row, key, value):
139     external_ids = row.external_ids
140     if set_or_delete(external_ids, key, value):
141         row.external_ids = external_ids
142
143
144 # XenServer does not call interface-reconfigure on internal networks,
145 # which is where the fail-mode would normally be set.
146 def update_fail_mode(row):
147     rec = get_network_by_bridge(row.name)
148     if not rec:
149         return
150
151     fail_mode = rec['other_config'].get('vswitch-controller-fail-mode')
152
153     if not fail_mode:
154         pools = session.xenapi.pool.get_all()
155         if len(pools) == 1:
156             prec = session.xenapi.pool.get_record(pools[0])
157             fail_mode = prec['other_config'].get(
158                     'vswitch-controller-fail-mode')
159
160     if fail_mode not in ['standalone', 'secure']:
161         fail_mode = 'standalone'
162
163     if row.fail_mode != fail_mode:
164         row.fail_mode = fail_mode
165
166
167 def update_in_band_mgmt(row):
168     rec = get_network_by_bridge(row.name)
169     if not rec:
170         return
171
172     dib = rec['other_config'].get('vswitch-disable-in-band')
173
174     other_config = row.other_config
175     if dib and dib not in ['true', 'false']:
176         vlog.warn('"%s" isn\'t a valid setting for '
177                 "other_config:disable-in-band on %s" % (dib, row.name))
178     elif set_or_delete(other_config, 'disable-in-band', dib):
179         row.other_config = other_config
180
181
182 def update_bridge_id(row):
183     id_ = get_bridge_id(row.name, row.external_ids.get("xs-network-uuids"))
184     if not id_:
185         return
186
187     set_external_id(row, "bridge-id", id_.split(";")[0])
188
189
190 def keep_table_columns(schema, table_name, columns):
191     table = schema.tables.get(table_name)
192     if not table:
193         raise error.Error("schema has no %s table" % table_name)
194
195     new_columns = {}
196     for column_name in columns:
197         column = table.columns.get(column_name)
198         if not column:
199             raise error.Error("%s table schema lacks %s column"
200                               % (table_name, column_name))
201         new_columns[column_name] = column
202     table.columns = new_columns
203     return table
204
205
206 def prune_schema(schema):
207     new_tables = {}
208     new_tables["Bridge"] = keep_table_columns(
209         schema, "Bridge", ("name", "external_ids", "other_config",
210                            "fail_mode"))
211     new_tables["Interface"] = keep_table_columns(
212         schema, "Interface", ("name", "external_ids"))
213     schema.tables = new_tables
214
215
216 def handler(signum, _):
217     global force_run
218     if (signum == signal.SIGHUP):
219         force_run = True
220
221
222 def main():
223     global force_run
224
225     parser = argparse.ArgumentParser()
226     parser.add_argument("database", metavar="DATABASE",
227             help="A socket on which ovsdb-server is listening.")
228     parser.add_argument("--root-prefix", metavar="DIR",
229                         help="Use DIR as alternate root directory"
230                         " (for testing).")
231
232     ovs.vlog.add_args(parser)
233     ovs.daemon.add_args(parser)
234     args = parser.parse_args()
235     ovs.vlog.handle_args(args)
236     ovs.daemon.handle_args(args)
237
238     global root_prefix
239     if args.root_prefix:
240         root_prefix = args.root_prefix
241
242     remote = args.database
243     schema_file = "%s/vswitch.ovsschema" % ovs.dirs.PKGDATADIR
244     schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schema_file))
245     prune_schema(schema)
246     idl = ovs.db.idl.Idl(remote, schema)
247
248     ovs.daemon.daemonize()
249
250     # This daemon is usually started before XAPI, but to complete our
251     # tasks, we need it.  Wait here until it's up.
252     cookie_file = root_prefix + "/var/run/xapi_init_complete.cookie"
253     while not os.path.exists(cookie_file):
254         time.sleep(1)
255
256     signal.signal(signal.SIGHUP, handler)
257
258     bridges = {}                # Map from bridge name to xs_network_uuids
259     interfaces = {}             # Map from interface name to xs-vif-uuid
260     while True:
261         if not force_run and not idl.run():
262             poller = ovs.poller.Poller()
263             idl.wait(poller)
264             poller.block()
265             continue
266
267         if force_run:
268             vlog.info("Forced to re-run as the result of a SIGHUP")
269             bridges = {}
270             interfaces = {}
271             force_run = False
272
273         txn = ovs.db.idl.Transaction(idl)
274
275         new_bridges = {}
276         for row in idl.tables["Bridge"].rows.itervalues():
277             old_xnu = bridges.get(row.name)
278             new_xnu = row.external_ids.get("xs-network-uuids", "")
279             if old_xnu is None:
280                 # New bridge.
281                 update_fail_mode(row)
282                 update_in_band_mgmt(row)
283             if new_xnu != old_xnu:
284                 # New bridge or bridge's xs-network-uuids has changed.
285                 update_bridge_id(row)
286             new_bridges[row.name] = new_xnu
287         bridges = new_bridges
288
289         iface_by_name = {}
290         for row in idl.tables["Interface"].rows.itervalues():
291             iface_by_name[row.name] = row
292
293         new_interfaces = {}
294         for row in idl.tables["Interface"].rows.itervalues():
295             # Match up paired vif and tap devices.
296             if row.name.startswith("vif"):
297                 vif = row
298                 tap = iface_by_name.get("tap%s" % row.name[3:])
299             elif row.name.startswith("tap"):
300                 tap = row
301                 vif = iface_by_name.get("vif%s" % row.name[3:])
302             else:
303                 tap = vif = None
304
305             # Several tap external-ids need to be copied from the vif.
306             if row == tap and vif:
307                 keys = ["attached-mac",
308                         "xs-network-uuid",
309                         "xs-vif-uuid",
310                         "xs-vm-uuid"]
311                 for k in keys:
312                     set_external_id(row, k, vif.external_ids.get(k))
313
314             # If it's a new interface or its xs-vif-uuid has changed, then
315             # obtain the iface-id from XAPI.
316             #
317             # (A tap's xs-vif-uuid comes from its vif.  That falls out
318             # naturally from the copy loop above.)
319             new_xvu = row.external_ids.get("xs-vif-uuid", "")
320             old_xvu = interfaces.get(row.name)
321             if old_xvu != new_xvu:
322                 iface_id = get_iface_id(row.name, new_xvu)
323                 if iface_id and row.external_ids.get("iface-id") != iface_id:
324                     set_external_id(row, "iface-id", iface_id)
325
326             # When there's a vif and a tap, the tap is active (used for
327             # traffic).  When there's just a vif, the vif is active.
328             #
329             # A tap on its own shouldn't happen, and we don't know
330             # anything about other kinds of devices, so we don't use
331             # an iface-status for those devices at all.
332             if vif and tap:
333                 set_external_id(tap, "iface-status", "active")
334                 set_external_id(vif, "iface-status", "inactive")
335             elif vif:
336                 set_external_id(vif, "iface-status", "active")
337             else:
338                 set_external_id(row, "iface-status", None)
339
340             new_interfaces[row.name] = new_xvu
341         interfaces = new_interfaces
342
343         txn.commit_block()
344
345
346 if __name__ == '__main__':
347     try:
348         main()
349     except SystemExit:
350         # Let system.exit() calls complete normally
351         raise
352     except:
353         vlog.exception("traceback")
354         sys.exit(ovs.daemon.RESTART_EXIT_CODE)