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