c87171f06ad22c98dc23c5b1d7db035a1ee1acba
[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         if column.type != column_type:
135             raise error.Error("%s column in %s table has type \"%s\", "
136                               "expected type \"%s\""
137                               % (column_name, table_name,
138                                  column.type.toEnglish(),
139                                  column_type.toEnglish()))
140         new_columns[column_name] = column
141     table.columns = new_columns
142     return table
143  
144 def monitor_uuid_schema_cb(schema):
145     string_type = types.Type(types.BaseType(types.StringType))
146     string_map_type = types.Type(types.BaseType(types.StringType),
147                                  types.BaseType(types.StringType),
148                                  0, sys.maxint)
149  
150     new_tables = {}
151     for table_name in ("Bridge", "Interface"):
152         new_tables[table_name] = keep_table_columns(
153             schema, table_name, {"name": string_type,
154                                  "external_ids": string_map_type})
155     schema.tables = new_tables
156
157 def usage():
158     print "usage: %s [OPTIONS] DATABASE" % sys.argv[0]
159     print "where DATABASE is a socket on which ovsdb-server is listening."
160     ovs.daemon.usage()
161     print "Other options:"
162     print "  -h, --help               display this help message"
163     sys.exit(0)
164  
165 def main(argv):
166     try:
167         options, args = getopt.gnu_getopt(
168             argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS)
169     except getopt.GetoptError, geo:
170         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
171         sys.exit(1)
172  
173     for key, value in options:
174         if key in ['-h', '--help']:
175             usage()
176         elif not ovs.daemon.parse_opt(key, value):
177             sys.stderr.write("%s: unhandled option %s\n"
178                              % (ovs.util.PROGRAM_NAME, key))
179             sys.exit(1)
180  
181     if len(args) != 1:
182         sys.stderr.write("%s: exactly one nonoption argument is required "
183                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
184         sys.exit(1)
185
186     ovs.daemon.die_if_already_running()
187  
188     remote = args[0]
189     idl = ovs.db.idl.Idl(remote, "Open_vSwitch", monitor_uuid_schema_cb)
190
191     ovs.daemon.daemonize()
192  
193     bridges = {}
194     interfaces = {}
195     while True:
196         if not idl.run():
197             poller = ovs.poller.Poller()
198             idl.wait(poller)
199             poller.block()
200             continue
201  
202         new_bridges = {}
203         for rec in idl.data["Bridge"].itervalues():
204             name = rec.name.as_scalar()
205             xs_network_uuids = rec.external_ids.get("xs-network-uuids")
206             network_uuids = rec.external_ids.get("network-uuids")
207             new_bridges[name] = {"xs-network-uuids": xs_network_uuids,
208                                  "network-uuids": network_uuids}
209  
210         new_interfaces = {}
211         for rec in idl.data["Interface"].itervalues():
212             name = rec.name.as_scalar()
213             xs_vif_uuid = rec.external_ids.get("xs-vif-uuid")
214             new_interfaces[name] = {"xs-vif-uuid": xs_vif_uuid}
215  
216         if bridges != new_bridges:
217             for name,ids in new_bridges.items():
218                 # Network uuids shouldn't change in the life of a bridge,
219                 # so only check for "network-uuids" on creation.
220                 if name not in bridges:
221                     update_network_uuids(name, ids)
222
223                 update_bridge_id(name, ids)
224
225             bridges = new_bridges
226
227         if interfaces != new_interfaces:
228             for name,ids in new_interfaces.items():
229                 update_iface_id(name, ids)
230             interfaces = new_interfaces
231  
232 if __name__ == '__main__':
233     try:
234         main(sys.argv)
235     except error.Error, e:
236         sys.stderr.write("%s\n" % e)
237         sys.exit(1)