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