python: Implement write support in Python IDL for OVSDB.
[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     string_type = types.Type(types.BaseType(types.StringType))
195     string_map_type = types.Type(types.BaseType(types.StringType),
196                                  types.BaseType(types.StringType),
197                                  0, sys.maxint)
198
199     new_tables = {}
200     new_tables["Bridge"] = keep_table_columns(
201         schema, "Bridge", ("name", "external_ids", "other_config",
202                            "fail_mode"))
203     new_tables["Interface"] = keep_table_columns(
204         schema, "Interface", ("name", "external_ids"))
205     schema.tables = new_tables
206
207 def usage():
208     print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
209     print "where DATABASE is a socket on which ovsdb-server is listening."
210     ovs.daemon.usage()
211     print "Other options:"
212     print "  -h, --help               display this help message"
213     sys.exit(0)
214
215 def handler(signum, _):
216     global force_run
217     if (signum == signal.SIGHUP):
218         force_run = True
219
220 def main(argv):
221     global force_run
222
223     l_handler = logging.handlers.RotatingFileHandler(
224             "/var/log/openvswitch/ovs-xapi-sync.log")
225     l_formatter = logging.Formatter('%(filename)s: %(levelname)s: %(message)s')
226     l_handler.setFormatter(l_formatter)
227     s_log.addHandler(l_handler)
228     s_log.setLevel(logging.INFO)
229
230     try:
231         options, args = getopt.gnu_getopt(
232             argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
233     except getopt.GetoptError, geo:
234         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
235         sys.exit(1)
236
237     for key, value in options:
238         if key in ['-h', '--help']:
239             usage()
240         elif not ovs.daemon.parse_opt(key, value):
241             sys.stderr.write("%s: unhandled option %s\n"
242                              % (ovs.util.PROGRAM_NAME, key))
243             sys.exit(1)
244
245     if len(args) != 1:
246         sys.stderr.write("%s: exactly one nonoption argument is required "
247                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
248         sys.exit(1)
249
250     remote = args[0]
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     # This daemon is usually started before XAPI, but to complete our
259     # tasks, we need it.  Wait here until it's up.
260     while not os.path.exists("/var/run/xapi_init_complete.cookie"):
261         time.sleep(1)
262
263     signal.signal(signal.SIGHUP, handler)
264
265     bridges = {}                # Map from bridge name to xs_network_uuids
266     interfaces = {}             # Map from interface name to 
267     while True:
268         if not force_run and not idl.run():
269             poller = ovs.poller.Poller()
270             idl.wait(poller)
271             poller.block()
272             continue
273
274         if force_run:
275             s_log.info("Forced to re-run as the result of a SIGHUP")
276             bridges    = {}
277             interfaces = {}
278             force_run  = False
279
280         txn = ovs.db.idl.Transaction(idl)
281
282         new_bridges = {}
283         for row in idl.tables["Bridge"].rows.itervalues():
284             old_xnu = bridges.get(row.name)
285             new_xnu = row.external_ids.get("xs-network-uuids", "")
286             if old_xnu is None:
287                 # New bridge.
288                 update_fail_mode(row)
289                 update_in_band_mgmt(row)
290             if new_xnu != old_xnu:
291                 # New bridge or bridge's xs-network-uuids has changed.
292                 update_bridge_id(row)
293             new_bridges[row.name] = new_xnu
294         bridges = new_bridges
295
296         iface_by_name = {}
297         for row in idl.tables["Interface"].rows.itervalues():
298             iface_by_name[row.name] = row
299
300         new_interfaces = {}
301         for row in idl.tables["Interface"].rows.itervalues():
302             # Match up paired vif and tap devices.
303             if row.name.startswith("vif"):
304                 vif = row
305                 tap = iface_by_name.get("tap%s" % row.name[3:])
306             elif row.name.startswith("tap"):
307                 tap = row
308                 vif = iface_by_name.get("vif%s" % row.name[3:])
309             else:
310                 tap = vif = None
311
312             # Several tap external-ids need to be copied from the vif.
313             if row == tap and vif:
314                 keys = ["attached-mac",
315                         "xs-network-uuid",
316                         "xs-vif-uuid",
317                         "xs-vm-uuid"]
318                 for k in keys:
319                     set_external_id(row, k, vif.external_ids.get(k))
320
321             # If it's a new interface or its xs-vif-uuid has changed, then
322             # obtain the iface-id from XAPI.
323             #
324             # (A tap's xs-vif-uuid comes from its vif.  That falls out
325             # naturally from the copy loop above.)
326             new_xvu = row.external_ids.get("xs-vif-uuid", "")
327             old_xvu = interfaces.get(row.name)
328             if old_xvu != new_xvu:
329                 iface_id = get_iface_id(row.name, new_xvu)
330                 if iface_id and row.external_ids.get("iface-id") != iface_id:
331                     set_external_id(row, "iface-id", iface_id)
332
333             # When there's a vif and a tap, the tap is active (used for
334             # traffic).  When there's just a vif, the vif is active.
335             #
336             # A tap on its own shouldn't happen, and we don't know
337             # anything about other kinds of devices, so we don't use
338             # an iface-status for those devices at all.
339             if vif and tap:
340                 set_external_id(tap, "iface-status", "active")
341                 set_external_id(vif, "iface-status", "inactive")
342             elif vif:
343                 set_external_id(vif, "iface-status", "active")
344             else:
345                 set_external_id(row, "iface-status", None)
346
347             new_interfaces[row.name] = new_xvu
348         interfaces = new_interfaces
349
350         txn.commit_block()
351
352 if __name__ == '__main__':
353     try:
354         main(sys.argv)
355     except SystemExit:
356         # Let system.exit() calls complete normally
357         raise
358     except:
359         s_log.exception("traceback")
360         sys.exit(ovs.daemon.RESTART_EXIT_CODE)