xenserver: Add GPLv2 license text.
[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     if ids.get("bridge-id") != id and id:
120         set_external_id("Bridge", name, "bridge-id", id)
121
122 def update_iface_id(name, ids):
123     id = get_iface_id(name, ids.get("xs-vif-uuid"))
124     if ids.get("iface-id") != id and id:
125         set_external_id("Interface", name, "iface-id", id)
126
127 def keep_table_columns(schema, table_name, column_types):
128     table = schema.tables.get(table_name)
129     if not table:
130         raise error.Error("schema has no %s table" % table_name)
131
132     new_columns = {}
133     for column_name, column_type in column_types.iteritems():
134         column = table.columns.get(column_name)
135         if not column:
136             raise error.Error("%s table schema lacks %s column"
137                               % (table_name, column_name))
138         if column.type != column_type:
139             raise error.Error("%s column in %s table has type \"%s\", "
140                               "expected type \"%s\""
141                               % (column_name, table_name,
142                                  column.type.toEnglish(),
143                                  column_type.toEnglish()))
144         new_columns[column_name] = column
145     table.columns = new_columns
146     return table
147  
148 def monitor_uuid_schema_cb(schema):
149     string_type = types.Type(types.BaseType(types.StringType))
150     string_map_type = types.Type(types.BaseType(types.StringType),
151                                  types.BaseType(types.StringType),
152                                  0, sys.maxint)
153  
154     new_tables = {}
155     for table_name in ("Bridge", "Interface"):
156         new_tables[table_name] = keep_table_columns(
157             schema, table_name, {"name": string_type,
158                                  "external_ids": string_map_type})
159     schema.tables = new_tables
160
161 def usage():
162     print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
163     print "where DATABASE is a socket on which ovsdb-server is listening."
164     ovs.daemon.usage()
165     print "Other options:"
166     print "  -h, --help               display this help message"
167     sys.exit(0)
168
169 def handler(signum, frame):
170     global force_run
171     if (signum == signal.SIGHUP):
172         force_run = True
173
174 def main(argv):
175     global force_run
176
177     try:
178         options, args = getopt.gnu_getopt(
179             argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
180     except getopt.GetoptError, geo:
181         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
182         sys.exit(1)
183  
184     for key, value in options:
185         if key in ['-h', '--help']:
186             usage()
187         elif not ovs.daemon.parse_opt(key, value):
188             sys.stderr.write("%s: unhandled option %s\n"
189                              % (ovs.util.PROGRAM_NAME, key))
190             sys.exit(1)
191  
192     if len(args) != 1:
193         sys.stderr.write("%s: exactly one nonoption argument is required "
194                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
195         sys.exit(1)
196
197     ovs.daemon.die_if_already_running()
198  
199     remote = args[0]
200     idl = ovs.db.idl.Idl(remote, "Open_vSwitch", monitor_uuid_schema_cb)
201
202     ovs.daemon.daemonize()
203
204     # This daemon is usually started before XAPI, but to complete our
205     # tasks, we need it.  Wait here until it's up.
206     while not os.path.exists("/var/run/xapi_init_complete.cookie"):
207         time.sleep(1)
208
209     signal.signal(signal.SIGHUP, handler)
210  
211     bridges = {}
212     interfaces = {}
213     while True:
214         if not force_run and not idl.run():
215             poller = ovs.poller.Poller()
216             idl.wait(poller)
217             poller.block()
218             continue
219
220         if force_run:
221             bridges    = {}
222             interfaces = {}
223             force_run = False
224
225         new_bridges = {}
226         for rec in idl.data["Bridge"].itervalues():
227             name = rec.name.as_scalar()
228             xs_network_uuids = rec.external_ids.get("xs-network-uuids")
229             network_uuids = rec.external_ids.get("network-uuids")
230             new_bridges[name] = {"xs-network-uuids": xs_network_uuids,
231                                  "network-uuids": network_uuids}
232  
233         new_interfaces = {}
234         for rec in idl.data["Interface"].itervalues():
235             name = rec.name.as_scalar()
236             xs_vif_uuid = rec.external_ids.get("xs-vif-uuid")
237             new_interfaces[name] = {"xs-vif-uuid": xs_vif_uuid}
238  
239         if bridges != new_bridges:
240             for name,ids in new_bridges.items():
241                 # Network uuids shouldn't change in the life of a bridge,
242                 # so only check for "network-uuids" on creation.
243                 if name not in bridges:
244                     update_network_uuids(name, ids)
245
246                 if (name not in bridges) or (bridges[name] != ids):
247                     update_bridge_id(name, ids)
248
249             bridges = new_bridges
250
251         if interfaces != new_interfaces:
252             for name,ids in new_interfaces.items():
253                 if (name not in interfaces) or (interfaces[name] != ids):
254                     update_iface_id(name, ids)
255             interfaces = new_interfaces
256  
257 if __name__ == '__main__':
258     try:
259         main(sys.argv)
260     except error.Error, e:
261         sys.stderr.write("%s\n" % e)
262         sys.exit(1)