07303e3eabd29792c9c75a6b2a7b3491bba08f32
[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.RotatingFileHandler(
41         "/var/log/openvswitch/ovs-external-ids.log")
42 l_formatter = logging.Formatter('%(filename)s: %(levelname)s: %(message)s')
43 l_handler.setFormatter(l_formatter)
44 s_log.addHandler(l_handler)
45 s_log.setLevel(logging.INFO)
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, xs_vif_uuid):
92     if not if_name.startswith("vif"):
93         # Treat whatever was passed into 'xs_vif_uuid' as a default
94         # value for non-VIFs.
95         return xs_vif_uuid
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 xs_vif_uuid
101
102     try:
103         vif = session.xenapi.VIF.get_by_uuid(xs_vif_uuid)
104         rec = session.xenapi.VIF.get_record(vif)
105         return rec['other_config'].get('nicira-iface-id', xs_vif_uuid)
106     except XenAPI.Failure:
107         s_log.warning("Could not find XAPI entry for VIF %s" % if_name)
108         return xs_vif_uuid
109
110 def set_external_id(table, record, key, value):
111     col = 'external-ids:"' + key + '"="' + value + '"'
112     cmd = [vsctl, "--timeout=30", "-vANY:console:emer", "set", table, record, col]
113     exitcode = subprocess.call(cmd)
114     if exitcode != 0:
115         s_log.warning("Couldn't call ovs-vsctl")
116
117 # XAPI on XenServer 5.6 uses the external-id "network-uuids" for internal
118 # networks, but we now prefer "xs-network-uuids".  Look for its use and
119 # write our preferred external-id.
120 def update_network_uuids(name, ids):
121     if ids["network-uuids"] and not ids["xs-network-uuids"]:
122         set_external_id("Bridge", name, "xs-network-uuids",
123                 ids["network-uuids"])
124
125 def update_bridge_id(name, ids):
126     id = get_bridge_id(name, ids.get("xs-network-uuids"))
127
128     if not id:
129         return
130
131     primary_id = id.split(";")[0]
132
133     if ids.get("bridge-id") != primary_id:
134         set_external_id("Bridge", name, "bridge-id", primary_id)
135
136 def update_iface_id(name, ids):
137     id = get_iface_id(name, ids.get("xs-vif-uuid"))
138     if ids.get("iface-id") != id and id:
139         set_external_id("Interface", name, "iface-id", id)
140
141 def keep_table_columns(schema, table_name, column_types):
142     table = schema.tables.get(table_name)
143     if not table:
144         raise error.Error("schema has no %s table" % table_name)
145
146     new_columns = {}
147     for column_name, column_type in column_types.iteritems():
148         column = table.columns.get(column_name)
149         if not column:
150             raise error.Error("%s table schema lacks %s column"
151                               % (table_name, column_name))
152         if column.type != column_type:
153             raise error.Error("%s column in %s table has type \"%s\", "
154                               "expected type \"%s\""
155                               % (column_name, table_name,
156                                  column.type.toEnglish(),
157                                  column_type.toEnglish()))
158         new_columns[column_name] = column
159     table.columns = new_columns
160     return table
161
162 def monitor_uuid_schema_cb(schema):
163     string_type = types.Type(types.BaseType(types.StringType))
164     string_map_type = types.Type(types.BaseType(types.StringType),
165                                  types.BaseType(types.StringType),
166                                  0, sys.maxint)
167
168     new_tables = {}
169     for table_name in ("Bridge", "Interface"):
170         new_tables[table_name] = keep_table_columns(
171             schema, table_name, {"name": string_type,
172                                  "external_ids": string_map_type})
173     schema.tables = new_tables
174
175 def usage():
176     print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
177     print "where DATABASE is a socket on which ovsdb-server is listening."
178     ovs.daemon.usage()
179     print "Other options:"
180     print "  -h, --help               display this help message"
181     sys.exit(0)
182
183 def handler(signum, frame):
184     global force_run
185     if (signum == signal.SIGHUP):
186         force_run = True
187
188 def main(argv):
189     global force_run
190
191     try:
192         options, args = getopt.gnu_getopt(
193             argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
194     except getopt.GetoptError, geo:
195         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
196         sys.exit(1)
197
198     for key, value in options:
199         if key in ['-h', '--help']:
200             usage()
201         elif not ovs.daemon.parse_opt(key, value):
202             sys.stderr.write("%s: unhandled option %s\n"
203                              % (ovs.util.PROGRAM_NAME, key))
204             sys.exit(1)
205
206     if len(args) != 1:
207         sys.stderr.write("%s: exactly one nonoption argument is required "
208                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
209         sys.exit(1)
210
211     ovs.daemon.die_if_already_running()
212
213     remote = args[0]
214     idl = ovs.db.idl.Idl(remote, "Open_vSwitch", monitor_uuid_schema_cb)
215
216     ovs.daemon.daemonize()
217
218     # This daemon is usually started before XAPI, but to complete our
219     # tasks, we need it.  Wait here until it's up.
220     while not os.path.exists("/var/run/xapi_init_complete.cookie"):
221         time.sleep(1)
222
223     signal.signal(signal.SIGHUP, handler)
224
225     bridges = {}
226     interfaces = {}
227     while True:
228         if not force_run and not idl.run():
229             poller = ovs.poller.Poller()
230             idl.wait(poller)
231             poller.block()
232             continue
233
234         if force_run:
235             s_log.info("Forced to re-run as the result of a SIGHUP")
236             bridges    = {}
237             interfaces = {}
238             force_run  = False
239
240         new_bridges = {}
241         for rec in idl.data["Bridge"].itervalues():
242             name = rec.name.as_scalar()
243             xs_network_uuids = rec.external_ids.get("xs-network-uuids")
244             network_uuids = rec.external_ids.get("network-uuids")
245             new_bridges[name] = {"xs-network-uuids": xs_network_uuids,
246                                  "network-uuids": network_uuids}
247
248         new_interfaces = {}
249         for rec in idl.data["Interface"].itervalues():
250             name = rec.name.as_scalar()
251             xs_vif_uuid = rec.external_ids.get("xs-vif-uuid")
252             new_interfaces[name] = {"xs-vif-uuid": xs_vif_uuid}
253
254         if bridges != new_bridges:
255             for name,ids in new_bridges.items():
256                 # Network uuids shouldn't change in the life of a bridge,
257                 # so only check for "network-uuids" on creation.
258                 if name not in bridges:
259                     update_network_uuids(name, ids)
260
261                 if (name not in bridges) or (bridges[name] != ids):
262                     update_bridge_id(name, ids)
263
264             bridges = new_bridges
265
266         if interfaces != new_interfaces:
267             for name,ids in new_interfaces.items():
268                 if (name not in interfaces) or (interfaces[name] != ids):
269                     update_iface_id(name, ids)
270             interfaces = new_interfaces
271
272 if __name__ == '__main__':
273     try:
274         main(sys.argv)
275     except SystemExit:
276         # Let system.exit() calls complete normally
277         raise
278     except:
279         s_log.exception("traceback")
280         sys.exit(ovs.daemon.RESTART_EXIT_CODE)