1a174a05d62bc987304b5b5ca7d9af24ecb9c397
[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 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 handler(signum, _):
219     global force_run
220     if (signum == signal.SIGHUP):
221         force_run = True
222
223
224 def main():
225     global force_run
226
227     s_log.addHandler(logging.StreamHandler())
228     try:
229         l_handler = logging.handlers.RotatingFileHandler(
230             "/var/log/openvswitch/ovs-xapi-sync.log")
231         l_formatter = logging.Formatter('%(filename)s: %(levelname)s: %(message)s')
232         l_handler.setFormatter(l_formatter)
233         s_log.addHandler(l_handler)
234     except IOError, e:
235         logging.basicConfig()
236         s_log.warn("failed to open logfile (%s)" % e)
237     s_log.setLevel(logging.INFO)
238
239     parser = argparse.ArgumentParser()
240     parser.add_argument("database", metavar="DATABASE",
241             help="A socket on which ovsdb-server is listening.")
242     parser.add_argument("--root-prefix", metavar="DIR",
243                         help="Use DIR as alternate root directory"
244                         " (for testing).")
245
246     ovs.daemon.add_args(parser)
247     args = parser.parse_args()
248     ovs.daemon.handle_args(args)
249
250     global root_prefix
251     root_prefix = args.root_prefix
252
253     remote = args.database
254     schema_file = "%s/vswitch.ovsschema" % ovs.dirs.PKGDATADIR
255     schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schema_file))
256     prune_schema(schema)
257     idl = ovs.db.idl.Idl(remote, schema)
258
259     ovs.daemon.daemonize()
260
261     # This daemon is usually started before XAPI, but to complete our
262     # tasks, we need it.  Wait here until it's up.
263     cookie_file = root_prefix + "/var/run/xapi_init_complete.cookie"
264     while not os.path.exists(cookie_file):
265         time.sleep(1)
266
267     signal.signal(signal.SIGHUP, handler)
268
269     bridges = {}                # Map from bridge name to xs_network_uuids
270     interfaces = {}             # Map from interface name to xs-vif-uuid
271     while True:
272         if not force_run and not idl.run():
273             poller = ovs.poller.Poller()
274             idl.wait(poller)
275             poller.block()
276             continue
277
278         if force_run:
279             s_log.info("Forced to re-run as the result of a SIGHUP")
280             bridges = {}
281             interfaces = {}
282             force_run = False
283
284         txn = ovs.db.idl.Transaction(idl)
285
286         new_bridges = {}
287         for row in idl.tables["Bridge"].rows.itervalues():
288             old_xnu = bridges.get(row.name)
289             new_xnu = row.external_ids.get("xs-network-uuids", "")
290             if old_xnu is None:
291                 # New bridge.
292                 update_fail_mode(row)
293                 update_in_band_mgmt(row)
294             if new_xnu != old_xnu:
295                 # New bridge or bridge's xs-network-uuids has changed.
296                 update_bridge_id(row)
297             new_bridges[row.name] = new_xnu
298         bridges = new_bridges
299
300         iface_by_name = {}
301         for row in idl.tables["Interface"].rows.itervalues():
302             iface_by_name[row.name] = row
303
304         new_interfaces = {}
305         for row in idl.tables["Interface"].rows.itervalues():
306             # Match up paired vif and tap devices.
307             if row.name.startswith("vif"):
308                 vif = row
309                 tap = iface_by_name.get("tap%s" % row.name[3:])
310             elif row.name.startswith("tap"):
311                 tap = row
312                 vif = iface_by_name.get("vif%s" % row.name[3:])
313             else:
314                 tap = vif = None
315
316             # Several tap external-ids need to be copied from the vif.
317             if row == tap and vif:
318                 keys = ["attached-mac",
319                         "xs-network-uuid",
320                         "xs-vif-uuid",
321                         "xs-vm-uuid"]
322                 for k in keys:
323                     set_external_id(row, k, vif.external_ids.get(k))
324
325             # If it's a new interface or its xs-vif-uuid has changed, then
326             # obtain the iface-id from XAPI.
327             #
328             # (A tap's xs-vif-uuid comes from its vif.  That falls out
329             # naturally from the copy loop above.)
330             new_xvu = row.external_ids.get("xs-vif-uuid", "")
331             old_xvu = interfaces.get(row.name)
332             if old_xvu != new_xvu:
333                 iface_id = get_iface_id(row.name, new_xvu)
334                 if iface_id and row.external_ids.get("iface-id") != iface_id:
335                     set_external_id(row, "iface-id", iface_id)
336
337             # When there's a vif and a tap, the tap is active (used for
338             # traffic).  When there's just a vif, the vif is active.
339             #
340             # A tap on its own shouldn't happen, and we don't know
341             # anything about other kinds of devices, so we don't use
342             # an iface-status for those devices at all.
343             if vif and tap:
344                 set_external_id(tap, "iface-status", "active")
345                 set_external_id(vif, "iface-status", "inactive")
346             elif vif:
347                 set_external_id(vif, "iface-status", "active")
348             else:
349                 set_external_id(row, "iface-status", None)
350
351             new_interfaces[row.name] = new_xvu
352         interfaces = new_interfaces
353
354         txn.commit_block()
355
356
357 if __name__ == '__main__':
358     try:
359         main()
360     except SystemExit:
361         # Let system.exit() calls complete normally
362         raise
363     except:
364         s_log.exception("traceback")
365         sys.exit(ovs.daemon.RESTART_EXIT_CODE)