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