Prepare Open vSwitch 1.1.2 release.
[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                       name)
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         ids["bridge-id"] = primary_id
180
181 def update_iface_id(name, ids):
182     id = get_iface_id(name, ids.get("xs-vif-uuid"))
183     if ids.get("iface-id") != id and id:
184         set_external_id("Interface", name, "iface-id", id)
185         ids["iface-id"] = id
186
187 def keep_table_columns(schema, table_name, column_types):
188     table = schema.tables.get(table_name)
189     if not table:
190         raise error.Error("schema has no %s table" % table_name)
191
192     new_columns = {}
193     for column_name, column_type in column_types.iteritems():
194         column = table.columns.get(column_name)
195         if not column:
196             raise error.Error("%s table schema lacks %s column"
197                               % (table_name, column_name))
198         if column.type != column_type:
199             raise error.Error("%s column in %s table has type \"%s\", "
200                               "expected type \"%s\""
201                               % (column_name, table_name,
202                                  column.type.toEnglish(),
203                                  column_type.toEnglish()))
204         new_columns[column_name] = column
205     table.columns = new_columns
206     return table
207
208 def monitor_uuid_schema_cb(schema):
209     string_type = types.Type(types.BaseType(types.StringType))
210     string_map_type = types.Type(types.BaseType(types.StringType),
211                                  types.BaseType(types.StringType),
212                                  0, sys.maxint)
213
214     new_tables = {}
215     for table_name in ("Bridge", "Interface"):
216         new_tables[table_name] = keep_table_columns(
217             schema, table_name, {"name": string_type,
218                                  "external_ids": string_map_type})
219     schema.tables = new_tables
220
221 def usage():
222     print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
223     print "where DATABASE is a socket on which ovsdb-server is listening."
224     ovs.daemon.usage()
225     print "Other options:"
226     print "  -h, --help               display this help message"
227     sys.exit(0)
228
229 def handler(signum, frame):
230     global force_run
231     if (signum == signal.SIGHUP):
232         force_run = True
233
234 def main(argv):
235     global force_run
236
237     try:
238         options, args = getopt.gnu_getopt(
239             argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
240     except getopt.GetoptError, geo:
241         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
242         sys.exit(1)
243
244     for key, value in options:
245         if key in ['-h', '--help']:
246             usage()
247         elif not ovs.daemon.parse_opt(key, value):
248             sys.stderr.write("%s: unhandled option %s\n"
249                              % (ovs.util.PROGRAM_NAME, key))
250             sys.exit(1)
251
252     if len(args) != 1:
253         sys.stderr.write("%s: exactly one nonoption argument is required "
254                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
255         sys.exit(1)
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             bridge_id = rec.external_ids.get("bridge-id")
289             new_bridges[name] = {"xs-network-uuids": xs_network_uuids,
290                                  "bridge-id": bridge_id}
291
292         new_interfaces = {}
293         for rec in idl.data["Interface"].itervalues():
294             name = rec.name.as_scalar()
295             xs_vif_uuid = rec.external_ids.get("xs-vif-uuid")
296             iface_id = rec.external_ids.get("iface-id")
297             new_interfaces[name] = {"xs-vif-uuid": xs_vif_uuid,
298                                     "iface-id": iface_id}
299
300         if bridges != new_bridges:
301             for name,ids in new_bridges.items():
302                 if name not in bridges:
303                     update_fail_mode(name)
304                     update_in_band_mgmt(name)
305
306                 if (name not in bridges) or (bridges[name] != ids):
307                     update_bridge_id(name, ids)
308
309             bridges = new_bridges
310
311         if interfaces != new_interfaces:
312             for name,ids in new_interfaces.items():
313                 if (name not in interfaces) or (interfaces[name] != ids):
314                     update_iface_id(name, ids)
315             interfaces = new_interfaces
316
317 if __name__ == '__main__':
318     try:
319         main(sys.argv)
320     except SystemExit:
321         # Let system.exit() calls complete normally
322         raise
323     except:
324         s_log.exception("traceback")
325         sys.exit(ovs.daemon.RESTART_EXIT_CODE)