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