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