xenserver: Added additional logging to ovs-external-ids
[sliver-openvswitch.git] / xenserver / usr_share_openvswitch_scripts_ovs-external-ids
1 #!/usr/bin/python
2 # Copyright (c) 2009, 2010 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.  Its primary responsibility is to set the
19 # "bridge-id" and "iface-id" keys in the Bridge and Interface tables,
20 # respectively.  It also looks for the use of "network-uuids" in the
21 # Bridge table and duplicates its value to the preferred "xs-network-uuids".
22
23 import getopt
24 import logging, logging.handlers
25 import os
26 import signal
27 import subprocess
28 import sys
29 import time
30
31 import XenAPI
32
33 from ovs.db import error
34 from ovs.db import types
35 import ovs.util
36 import ovs.daemon
37 import ovs.db.idl
38
39 s_log     = logging.getLogger("ovs-external-ids")
40 l_handler = logging.handlers.SysLogHandler(
41         "/dev/log",
42         facility=logging.handlers.SysLogHandler.LOG_DAEMON)
43 l_formatter = logging.Formatter('%(filename)s: %(levelname)s: %(message)s')
44 l_handler.setFormatter(l_formatter)
45 s_log.addHandler(l_handler)
46
47 vsctl="/usr/bin/ovs-vsctl"
48 session = None
49 force_run = False
50
51 # Set up a session to interact with XAPI.
52 #
53 # On system start-up, OVS comes up before XAPI, so we can't log into the
54 # session until later.  Try to do this on-demand, since we won't
55 # actually do anything interesting until XAPI is up.
56 def init_session():
57     global session
58     if session is not None:
59         return True
60
61     try:
62         session = XenAPI.xapi_local()
63         session.xenapi.login_with_password("", "")
64     except:
65         session = None
66         s_log.warning("Couldn't login to XAPI")
67         return False
68
69     return True
70
71 # By default, the "bridge-id" external id in the Bridge table is the
72 # same as "xs-network-uuids".  This may be overridden by defining a
73 # "nicira-bridge-id" key in the "other_config" field of the network
74 # record of XAPI.
75 def get_bridge_id(br_name, default=None):
76     if not init_session():
77         s_log.warning("Failed to get bridge id %s because"
78                 " XAPI session could not be initialized" % br_name)
79         return default
80
81     for n in session.xenapi.network.get_all():
82         rec = session.xenapi.network.get_record(n)
83         if rec['bridge'] != br_name:
84             continue
85         return rec['other_config'].get('nicira-bridge-id', default)
86
87 # By default, the "iface-id" external id in the Interface table is the
88 # same as "xs-vif-uuid".  This may be overridden by defining a
89 # "nicira-iface-id" key in the "other_config" field of the VIF
90 # record of XAPI.
91 def get_iface_id(if_name, default=None):
92     if not if_name.startswith("vif"):
93         return default
94
95     domain,device = if_name.strip("vif").split(".")
96
97     if not init_session():
98         s_log.warning("Failed to get interface id %s because"
99                 " XAPI session could not be initialized" % if_name)
100         return default
101
102     for n in session.xenapi.VM.get_all():
103         if session.xenapi.VM.get_domid(n) == domain:
104             vifs = session.xenapi.VM.get_VIFs(n)
105             for vif in vifs:
106                 rec = session.xenapi.VIF.get_record(vif)
107                 if rec['device'] == device:
108                     return rec['other_config'].get('nicira-iface-id', default)
109     return None
110
111
112 def set_external_id(table, record, key, value):
113     col = 'external-ids:"' + key + '"="' + value + '"'
114     cmd = [vsctl, "--timeout=30", "-vANY:console:emer", "set", table, record, col]
115     exitcode = subprocess.call(cmd)
116     if exitcode != 0:
117         s_log.warning("Couldn't call ovs-vsctl")
118
119 # XAPI on XenServer 5.6 uses the external-id "network-uuids" for internal
120 # networks, but we now prefer "xs-network-uuids".  Look for its use and
121 # write our preferred external-id.
122 def update_network_uuids(name, ids):
123     if ids["network-uuids"] and not ids["xs-network-uuids"]:
124         set_external_id("Bridge", name, "xs-network-uuids",
125                 ids["network-uuids"])
126
127 def update_bridge_id(name, ids):
128     id = get_bridge_id(name, ids.get("xs-network-uuids"))
129
130     if not id:
131         return
132
133     primary_id = id.split(";")[0]
134
135     if ids.get("bridge-id") != primary_id:
136         set_external_id("Bridge", name, "bridge-id", primary_id)
137
138 def update_iface_id(name, ids):
139     id = get_iface_id(name, ids.get("xs-vif-uuid"))
140     if ids.get("iface-id") != id and id:
141         set_external_id("Interface", name, "iface-id", id)
142
143 def keep_table_columns(schema, table_name, column_types):
144     table = schema.tables.get(table_name)
145     if not table:
146         raise error.Error("schema has no %s table" % table_name)
147
148     new_columns = {}
149     for column_name, column_type in column_types.iteritems():
150         column = table.columns.get(column_name)
151         if not column:
152             raise error.Error("%s table schema lacks %s column"
153                               % (table_name, column_name))
154         if column.type != column_type:
155             raise error.Error("%s column in %s table has type \"%s\", "
156                               "expected type \"%s\""
157                               % (column_name, table_name,
158                                  column.type.toEnglish(),
159                                  column_type.toEnglish()))
160         new_columns[column_name] = column
161     table.columns = new_columns
162     return table
163
164 def monitor_uuid_schema_cb(schema):
165     string_type = types.Type(types.BaseType(types.StringType))
166     string_map_type = types.Type(types.BaseType(types.StringType),
167                                  types.BaseType(types.StringType),
168                                  0, sys.maxint)
169
170     new_tables = {}
171     for table_name in ("Bridge", "Interface"):
172         new_tables[table_name] = keep_table_columns(
173             schema, table_name, {"name": string_type,
174                                  "external_ids": string_map_type})
175     schema.tables = new_tables
176
177 def usage():
178     print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
179     print "where DATABASE is a socket on which ovsdb-server is listening."
180     ovs.daemon.usage()
181     print "Other options:"
182     print "  -h, --help               display this help message"
183     sys.exit(0)
184
185 def handler(signum, frame):
186     global force_run
187     if (signum == signal.SIGHUP):
188         force_run = True
189
190 def main(argv):
191     global force_run
192
193     try:
194         options, args = getopt.gnu_getopt(
195             argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
196     except getopt.GetoptError, geo:
197         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
198         sys.exit(1)
199
200     for key, value in options:
201         if key in ['-h', '--help']:
202             usage()
203         elif not ovs.daemon.parse_opt(key, value):
204             sys.stderr.write("%s: unhandled option %s\n"
205                              % (ovs.util.PROGRAM_NAME, key))
206             sys.exit(1)
207
208     if len(args) != 1:
209         sys.stderr.write("%s: exactly one nonoption argument is required "
210                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
211         sys.exit(1)
212
213     ovs.daemon.die_if_already_running()
214
215     remote = args[0]
216     idl = ovs.db.idl.Idl(remote, "Open_vSwitch", monitor_uuid_schema_cb)
217
218     ovs.daemon.daemonize()
219
220     # This daemon is usually started before XAPI, but to complete our
221     # tasks, we need it.  Wait here until it's up.
222     while not os.path.exists("/var/run/xapi_init_complete.cookie"):
223         time.sleep(1)
224
225     signal.signal(signal.SIGHUP, handler)
226
227     bridges = {}
228     interfaces = {}
229     while True:
230         if not force_run and not idl.run():
231             poller = ovs.poller.Poller()
232             idl.wait(poller)
233             poller.block()
234             continue
235
236         if force_run:
237             s_log.info("Forced to re-run as the result of a SIGHUP")
238             bridges    = {}
239             interfaces = {}
240             force_run  = False
241
242         new_bridges = {}
243         for rec in idl.data["Bridge"].itervalues():
244             name = rec.name.as_scalar()
245             xs_network_uuids = rec.external_ids.get("xs-network-uuids")
246             network_uuids = rec.external_ids.get("network-uuids")
247             new_bridges[name] = {"xs-network-uuids": xs_network_uuids,
248                                  "network-uuids": network_uuids}
249
250         new_interfaces = {}
251         for rec in idl.data["Interface"].itervalues():
252             name = rec.name.as_scalar()
253             xs_vif_uuid = rec.external_ids.get("xs-vif-uuid")
254             new_interfaces[name] = {"xs-vif-uuid": xs_vif_uuid}
255
256         if bridges != new_bridges:
257             for name,ids in new_bridges.items():
258                 # Network uuids shouldn't change in the life of a bridge,
259                 # so only check for "network-uuids" on creation.
260                 if name not in bridges:
261                     update_network_uuids(name, ids)
262
263                 if (name not in bridges) or (bridges[name] != ids):
264                     update_bridge_id(name, ids)
265
266             bridges = new_bridges
267
268         if interfaces != new_interfaces:
269             for name,ids in new_interfaces.items():
270                 if (name not in interfaces) or (interfaces[name] != ids):
271                     update_iface_id(name, ids)
272             interfaces = new_interfaces
273
274 if __name__ == '__main__':
275     try:
276         main(sys.argv)
277     except SystemExit:
278         # Let system.exit() calls complete normally
279         raise
280     except:
281         s_log.exception("traceback")
282         sys.exit(1)