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