xenserver: Rename monitor-external-ids -> ovs-external-ids
[sliver-openvswitch.git] / xenserver / usr_share_openvswitch_scripts_ovs-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                 "ovs-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                 "ovs-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     bridges = {}
200     interfaces = {}
201     while True:
202         if not idl.run():
203             poller = ovs.poller.Poller()
204             idl.wait(poller)
205             poller.block()
206             continue
207         new_bridges = {}
208         for rec in idl.data["Bridge"].itervalues():
209             name = rec.name.as_scalar()
210             xs_network_uuids = rec.external_ids.get("xs-network-uuids")
211             network_uuids = rec.external_ids.get("network-uuids")
212             new_bridges[name] = {"xs-network-uuids": xs_network_uuids,
213                                  "network-uuids": network_uuids}
214
215         new_interfaces = {}
216         for rec in idl.data["Interface"].itervalues():
217             name = rec.name.as_scalar()
218             xs_vif_uuid = rec.external_ids.get("xs-vif-uuid")
219             new_interfaces[name] = {"xs-vif-uuid": xs_vif_uuid}
220
221         if bridges != new_bridges:
222             for name,ids in new_bridges.items():
223                 # Network uuids shouldn't change in the life of a bridge,
224                 # so only check for "network-uuids" on creation.
225                 if name not in bridges:
226                     update_network_uuids(name, ids)
227
228                 if (name not in bridges) or (bridges[name] != ids):
229                     update_bridge_id(name, ids)
230
231             bridges = new_bridges
232
233         if interfaces != new_interfaces:
234             for name,ids in new_interfaces.items():
235                 if (name not in interfaces) or (interfaces[name] != ids):
236                     update_iface_id(name, ids)
237             interfaces = new_interfaces
238
239 if __name__ == '__main__':
240     try:
241         main(sys.argv)
242     except error.Error, e:
243         sys.stderr.write("%s\n" % e)
244         sys.exit(1)