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