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