xenserver: New iface-status external id.
[cascardo/ovs.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") 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 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(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     status = ids.get("iface-status")
188     if status:
189         set_external_id("Interface", name, "iface-status", status)
190
191 def keep_table_columns(schema, table_name, column_types):
192     table = schema.tables.get(table_name)
193     if not table:
194         raise error.Error("schema has no %s table" % table_name)
195
196     new_columns = {}
197     for column_name, column_type in column_types.iteritems():
198         column = table.columns.get(column_name)
199         if not column:
200             raise error.Error("%s table schema lacks %s column"
201                               % (table_name, column_name))
202         if column.type != column_type:
203             raise error.Error("%s column in %s table has type \"%s\", "
204                               "expected type \"%s\""
205                               % (column_name, table_name,
206                                  column.type.toEnglish(),
207                                  column_type.toEnglish()))
208         new_columns[column_name] = column
209     table.columns = new_columns
210     return table
211
212 def monitor_uuid_schema_cb(schema):
213     string_type = types.Type(types.BaseType(types.StringType))
214     string_map_type = types.Type(types.BaseType(types.StringType),
215                                  types.BaseType(types.StringType),
216                                  0, sys.maxint)
217
218     new_tables = {}
219     for table_name in ("Bridge", "Interface"):
220         new_tables[table_name] = keep_table_columns(
221             schema, table_name, {"name": string_type,
222                                  "external_ids": string_map_type})
223     schema.tables = new_tables
224
225 def usage():
226     print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
227     print "where DATABASE is a socket on which ovsdb-server is listening."
228     ovs.daemon.usage()
229     print "Other options:"
230     print "  -h, --help               display this help message"
231     sys.exit(0)
232
233 def handler(signum, frame):
234     global force_run
235     if (signum == signal.SIGHUP):
236         force_run = True
237
238 def main(argv):
239     global force_run
240
241     try:
242         options, args = getopt.gnu_getopt(
243             argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
244     except getopt.GetoptError, geo:
245         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
246         sys.exit(1)
247
248     for key, value in options:
249         if key in ['-h', '--help']:
250             usage()
251         elif not ovs.daemon.parse_opt(key, value):
252             sys.stderr.write("%s: unhandled option %s\n"
253                              % (ovs.util.PROGRAM_NAME, key))
254             sys.exit(1)
255
256     if len(args) != 1:
257         sys.stderr.write("%s: exactly one nonoption argument is required "
258                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
259         sys.exit(1)
260
261     remote = args[0]
262     idl = ovs.db.idl.Idl(remote, "Open_vSwitch", monitor_uuid_schema_cb)
263
264     ovs.daemon.daemonize()
265
266     # This daemon is usually started before XAPI, but to complete our
267     # tasks, we need it.  Wait here until it's up.
268     while not os.path.exists("/var/run/xapi_init_complete.cookie"):
269         time.sleep(1)
270
271     signal.signal(signal.SIGHUP, handler)
272
273     bridges = {}
274     interfaces = {}
275     while True:
276         if not force_run and not idl.run():
277             poller = ovs.poller.Poller()
278             idl.wait(poller)
279             poller.block()
280             continue
281
282         if force_run:
283             s_log.info("Forced to re-run as the result of a SIGHUP")
284             bridges    = {}
285             interfaces = {}
286             force_run  = False
287
288         new_bridges = {}
289         for rec in idl.data["Bridge"].itervalues():
290             name = rec.name.as_scalar()
291             xs_network_uuids = rec.external_ids.get("xs-network-uuids")
292             bridge_id = rec.external_ids.get("bridge-id")
293             new_bridges[name] = {"xs-network-uuids": xs_network_uuids,
294                                  "bridge-id": bridge_id}
295
296         new_interfaces = {}
297         for rec in idl.data["Interface"].itervalues():
298             name = rec.name.as_scalar()
299             xs_vif_uuid = rec.external_ids.get("xs-vif-uuid")
300             iface_id = rec.external_ids.get("iface-id")
301             new_interfaces[name] = {"xs-vif-uuid": xs_vif_uuid,
302                                     "iface-id": iface_id}
303
304             if name.startswith("vif"):
305                 new_interfaces[name]["iface-status"] = "active"
306
307         #Tap devices take their xs-vif-uuid from their corresponding vif and
308         #cause that vif to be labled inactive.
309         for name in new_interfaces:
310             if not name.startswith("tap"):
311                 continue
312
313             vif = name.replace("tap", "vif", 1)
314
315             if vif in new_interfaces:
316                 xs_vif_uuid = new_interfaces[vif]["xs-vif-uuid"]
317                 new_interfaces[name]["xs-vif-uuid"] = xs_vif_uuid
318
319                 new_interfaces[vif]["iface-status"] = "inactive"
320                 new_interfaces[name]["iface-status"] = "active"
321
322         if bridges != new_bridges:
323             for name,ids in new_bridges.items():
324                 if name not in bridges:
325                     update_fail_mode(name)
326                     update_in_band_mgmt(name)
327
328                 if (name not in bridges) or (bridges[name] != ids):
329                     update_bridge_id(name, ids)
330
331             bridges = new_bridges
332
333         if interfaces != new_interfaces:
334             for name,ids in new_interfaces.items():
335                 if (name not in interfaces) or (interfaces[name] != ids):
336                     update_iface(name, ids)
337             interfaces = new_interfaces
338
339 if __name__ == '__main__':
340     try:
341         main(sys.argv)
342     except SystemExit:
343         # Let system.exit() calls complete normally
344         raise
345     except:
346         s_log.exception("traceback")
347         sys.exit(ovs.daemon.RESTART_EXIT_CODE)