xenserver: Add support for disabling in-band management via XAPI.
[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 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 s_log     = logging.getLogger("ovs-xapi-sync")
42 l_handler = logging.handlers.RotatingFileHandler(
43         "/var/log/openvswitch/ovs-xapi-sync.log")
44 l_formatter = logging.Formatter('%(filename)s: %(levelname)s: %(message)s')
45 l_handler.setFormatter(l_formatter)
46 s_log.addHandler(l_handler)
47 s_log.setLevel(logging.INFO)
48
49 vsctl="/usr/bin/ovs-vsctl"
50 session = None
51 force_run = False
52
53 # Set up a session to interact with XAPI.
54 #
55 # On system start-up, OVS comes up before XAPI, so we can't log into the
56 # session until later.  Try to do this on-demand, since we won't
57 # actually do anything interesting until XAPI is up.
58 def init_session():
59     global session
60     if session is not None:
61         return True
62
63     try:
64         session = XenAPI.xapi_local()
65         session.xenapi.login_with_password("", "")
66     except:
67         session = None
68         s_log.warning("Couldn't login to XAPI")
69         return False
70
71     return True
72
73 def get_network_by_bridge(br_name):
74     if not init_session():
75         s_log.warning("Failed to get bridge id %s because"
76                 " XAPI session could not be initialized" % br_name)
77         return None
78
79     for n in session.xenapi.network.get_all():
80         rec = session.xenapi.network.get_record(n)
81         if rec['bridge'] == br_name:
82             return rec
83
84     return None
85
86 # By default, the "bridge-id" external id in the Bridge table is the
87 # same as "xs-network-uuids".  This may be overridden by defining a
88 # "nicira-bridge-id" key in the "other_config" field of the network
89 # record of XAPI.  If nicira-bridge-id is undefined returns default.
90 # On error returns None.
91 def get_bridge_id(br_name, default=None):
92     rec = get_network_by_bridge(br_name)
93     if rec:
94         return rec['other_config'].get('nicira-bridge-id', default)
95     return None
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"):
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 def call_vsctl(args):
121     cmd = [vsctl, "--timeout=30", "-vANY:console:emer"] + args
122     exitcode = subprocess.call(cmd)
123     if exitcode != 0:
124         s_log.warning("Couldn't call ovs-vsctl")
125
126 def set_external_id(table, record, key, value):
127     col = 'external-ids:"' + key + '"="' + value + '"'
128     call_vsctl(["set", table, record, col])
129
130 # XenServer does not call interface-reconfigure on internal networks,
131 # which is where the fail-mode would normally be set.
132 def update_fail_mode(name):
133     rec = get_network_by_bridge(name)
134
135     if not rec:
136         return
137
138     fail_mode = rec['other_config'].get('vswitch-controller-fail-mode')
139
140     if not fail_mode:
141         pools = session.xenapi.pool.get_all()
142         if len(pools) == 1:
143             prec = session.xenapi.pool.get_record(pools[0])
144             fail_mode = prec['other_config'].get('vswitch-controller-fail-mode')
145
146     if fail_mode not in ['standalone', 'secure']:
147         fail_mode = 'standalone'
148
149     call_vsctl(["set", "bridge", name, "fail_mode=" + fail_mode])
150
151 def update_in_band_mgmt(name):
152     rec = get_network_by_bridge(name)
153
154     if not rec:
155         return
156
157     dib = rec['other_config'].get('vswitch-disable-in-band')
158     if not dib:
159         call_vsctl(['remove', 'bridge', name, 'other_config',
160                     'disable-in-band'])
161     elif dib in ['true', 'false']:
162         call_vsctl(['set', 'bridge', name,
163                     'other_config:disable-in-band=' + dib])
164     else:
165         s_log.warning('"' + dib + '"'
166                       "isn't a valid setting for other_config:disable-in-band on " +
167                       bridge)
168
169 def update_bridge_id(name, ids):
170     id = get_bridge_id(name, ids.get("xs-network-uuids"))
171
172     if not id:
173         return
174
175     primary_id = id.split(";")[0]
176
177     if ids.get("bridge-id") != primary_id:
178         set_external_id("Bridge", name, "bridge-id", primary_id)
179
180 def update_iface_id(name, ids):
181     id = get_iface_id(name, ids.get("xs-vif-uuid"))
182     if ids.get("iface-id") != id and id:
183         set_external_id("Interface", name, "iface-id", id)
184
185 def keep_table_columns(schema, table_name, column_types):
186     table = schema.tables.get(table_name)
187     if not table:
188         raise error.Error("schema has no %s table" % table_name)
189
190     new_columns = {}
191     for column_name, column_type in column_types.iteritems():
192         column = table.columns.get(column_name)
193         if not column:
194             raise error.Error("%s table schema lacks %s column"
195                               % (table_name, column_name))
196         if column.type != column_type:
197             raise error.Error("%s column in %s table has type \"%s\", "
198                               "expected type \"%s\""
199                               % (column_name, table_name,
200                                  column.type.toEnglish(),
201                                  column_type.toEnglish()))
202         new_columns[column_name] = column
203     table.columns = new_columns
204     return table
205
206 def monitor_uuid_schema_cb(schema):
207     string_type = types.Type(types.BaseType(types.StringType))
208     string_map_type = types.Type(types.BaseType(types.StringType),
209                                  types.BaseType(types.StringType),
210                                  0, sys.maxint)
211
212     new_tables = {}
213     for table_name in ("Bridge", "Interface"):
214         new_tables[table_name] = keep_table_columns(
215             schema, table_name, {"name": string_type,
216                                  "external_ids": string_map_type})
217     schema.tables = new_tables
218
219 def usage():
220     print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
221     print "where DATABASE is a socket on which ovsdb-server is listening."
222     ovs.daemon.usage()
223     print "Other options:"
224     print "  -h, --help               display this help message"
225     sys.exit(0)
226
227 def handler(signum, frame):
228     global force_run
229     if (signum == signal.SIGHUP):
230         force_run = True
231
232 def main(argv):
233     global force_run
234
235     try:
236         options, args = getopt.gnu_getopt(
237             argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
238     except getopt.GetoptError, geo:
239         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
240         sys.exit(1)
241
242     for key, value in options:
243         if key in ['-h', '--help']:
244             usage()
245         elif not ovs.daemon.parse_opt(key, value):
246             sys.stderr.write("%s: unhandled option %s\n"
247                              % (ovs.util.PROGRAM_NAME, key))
248             sys.exit(1)
249
250     if len(args) != 1:
251         sys.stderr.write("%s: exactly one nonoption argument is required "
252                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
253         sys.exit(1)
254
255     ovs.daemon.die_if_already_running()
256
257     remote = args[0]
258     idl = ovs.db.idl.Idl(remote, "Open_vSwitch", monitor_uuid_schema_cb)
259
260     ovs.daemon.daemonize()
261
262     # This daemon is usually started before XAPI, but to complete our
263     # tasks, we need it.  Wait here until it's up.
264     while not os.path.exists("/var/run/xapi_init_complete.cookie"):
265         time.sleep(1)
266
267     signal.signal(signal.SIGHUP, handler)
268
269     bridges = {}
270     interfaces = {}
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         new_bridges = {}
285         for rec in idl.data["Bridge"].itervalues():
286             name = rec.name.as_scalar()
287             xs_network_uuids = rec.external_ids.get("xs-network-uuids")
288             new_bridges[name] = {"xs-network-uuids": xs_network_uuids}
289
290         new_interfaces = {}
291         for rec in idl.data["Interface"].itervalues():
292             name = rec.name.as_scalar()
293             xs_vif_uuid = rec.external_ids.get("xs-vif-uuid")
294             new_interfaces[name] = {"xs-vif-uuid": xs_vif_uuid}
295
296         if bridges != new_bridges:
297             for name,ids in new_bridges.items():
298                 if name not in bridges:
299                     update_fail_mode(name)
300                     update_in_band_mgmt(name)
301
302                 if (name not in bridges) or (bridges[name] != ids):
303                     update_bridge_id(name, ids)
304
305             bridges = new_bridges
306
307         if interfaces != new_interfaces:
308             for name,ids in new_interfaces.items():
309                 if (name not in interfaces) or (interfaces[name] != ids):
310                     update_iface_id(name, ids)
311             interfaces = new_interfaces
312
313 if __name__ == '__main__':
314     try:
315         main(sys.argv)
316     except SystemExit:
317         # Let system.exit() calls complete normally
318         raise
319     except:
320         s_log.exception("traceback")
321         sys.exit(ovs.daemon.RESTART_EXIT_CODE)