xenserver: Clean up /usr/sbin/brctl dangling symlink.
[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_bridge_id(name, ids):
152     id = get_bridge_id(name, ids.get("xs-network-uuids"))
153
154     if not id:
155         return
156
157     primary_id = id.split(";")[0]
158
159     if ids.get("bridge-id") != primary_id:
160         set_external_id("Bridge", name, "bridge-id", primary_id)
161
162 def update_iface_id(name, ids):
163     id = get_iface_id(name, ids.get("xs-vif-uuid"))
164     if ids.get("iface-id") != id and id:
165         set_external_id("Interface", name, "iface-id", id)
166
167 def keep_table_columns(schema, table_name, column_types):
168     table = schema.tables.get(table_name)
169     if not table:
170         raise error.Error("schema has no %s table" % table_name)
171
172     new_columns = {}
173     for column_name, column_type in column_types.iteritems():
174         column = table.columns.get(column_name)
175         if not column:
176             raise error.Error("%s table schema lacks %s column"
177                               % (table_name, column_name))
178         if column.type != column_type:
179             raise error.Error("%s column in %s table has type \"%s\", "
180                               "expected type \"%s\""
181                               % (column_name, table_name,
182                                  column.type.toEnglish(),
183                                  column_type.toEnglish()))
184         new_columns[column_name] = column
185     table.columns = new_columns
186     return table
187
188 def monitor_uuid_schema_cb(schema):
189     string_type = types.Type(types.BaseType(types.StringType))
190     string_map_type = types.Type(types.BaseType(types.StringType),
191                                  types.BaseType(types.StringType),
192                                  0, sys.maxint)
193
194     new_tables = {}
195     for table_name in ("Bridge", "Interface"):
196         new_tables[table_name] = keep_table_columns(
197             schema, table_name, {"name": string_type,
198                                  "external_ids": string_map_type})
199     schema.tables = new_tables
200
201 def usage():
202     print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
203     print "where DATABASE is a socket on which ovsdb-server is listening."
204     ovs.daemon.usage()
205     print "Other options:"
206     print "  -h, --help               display this help message"
207     sys.exit(0)
208
209 def handler(signum, frame):
210     global force_run
211     if (signum == signal.SIGHUP):
212         force_run = True
213
214 def main(argv):
215     global force_run
216
217     try:
218         options, args = getopt.gnu_getopt(
219             argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
220     except getopt.GetoptError, geo:
221         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
222         sys.exit(1)
223
224     for key, value in options:
225         if key in ['-h', '--help']:
226             usage()
227         elif not ovs.daemon.parse_opt(key, value):
228             sys.stderr.write("%s: unhandled option %s\n"
229                              % (ovs.util.PROGRAM_NAME, key))
230             sys.exit(1)
231
232     if len(args) != 1:
233         sys.stderr.write("%s: exactly one nonoption argument is required "
234                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
235         sys.exit(1)
236
237     ovs.daemon.die_if_already_running()
238
239     remote = args[0]
240     idl = ovs.db.idl.Idl(remote, "Open_vSwitch", monitor_uuid_schema_cb)
241
242     ovs.daemon.daemonize()
243
244     # This daemon is usually started before XAPI, but to complete our
245     # tasks, we need it.  Wait here until it's up.
246     while not os.path.exists("/var/run/xapi_init_complete.cookie"):
247         time.sleep(1)
248
249     signal.signal(signal.SIGHUP, handler)
250
251     bridges = {}
252     interfaces = {}
253     while True:
254         if not force_run and not idl.run():
255             poller = ovs.poller.Poller()
256             idl.wait(poller)
257             poller.block()
258             continue
259
260         if force_run:
261             s_log.info("Forced to re-run as the result of a SIGHUP")
262             bridges    = {}
263             interfaces = {}
264             force_run  = False
265
266         new_bridges = {}
267         for rec in idl.data["Bridge"].itervalues():
268             name = rec.name.as_scalar()
269             xs_network_uuids = rec.external_ids.get("xs-network-uuids")
270             new_bridges[name] = {"xs-network-uuids": xs_network_uuids}
271
272         new_interfaces = {}
273         for rec in idl.data["Interface"].itervalues():
274             name = rec.name.as_scalar()
275             xs_vif_uuid = rec.external_ids.get("xs-vif-uuid")
276             new_interfaces[name] = {"xs-vif-uuid": xs_vif_uuid}
277
278         if bridges != new_bridges:
279             for name,ids in new_bridges.items():
280                 if name not in bridges:
281                     update_fail_mode(name)
282
283                 if (name not in bridges) or (bridges[name] != ids):
284                     update_bridge_id(name, ids)
285
286             bridges = new_bridges
287
288         if interfaces != new_interfaces:
289             for name,ids in new_interfaces.items():
290                 if (name not in interfaces) or (interfaces[name] != ids):
291                     update_iface_id(name, ids)
292             interfaces = new_interfaces
293
294 if __name__ == '__main__':
295     try:
296         main(sys.argv)
297     except SystemExit:
298         # Let system.exit() calls complete normally
299         raise
300     except:
301         s_log.exception("traceback")
302         sys.exit(ovs.daemon.RESTART_EXIT_CODE)