]>
Commit | Line | Data |
---|---|---|
df09921d | 1 | #!/usr/bin/python |
f04f9af0 | 2 | # Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc. |
df09921d JP |
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 | |
a49fe70c JP |
18 | # Interface OVSDB tables for changes that require interrogating XAPI. |
19 | # Its responsibilities include: | |
20 | # | |
21 | # - Set the "bridge-id" key in the Bridge table. | |
22 | # - Set the "iface-id" key in the Interface table. | |
23 | # - Set the fail-mode on internal bridges. | |
df09921d | 24 | |
b153e667 | 25 | import argparse |
29e21ea2 | 26 | import os |
df09921d | 27 | import sys |
29e21ea2 | 28 | import time |
df09921d JP |
29 | |
30 | import XenAPI | |
31 | ||
8cdf0349 | 32 | import ovs.dirs |
df09921d JP |
33 | from ovs.db import error |
34 | from ovs.db import types | |
df09921d JP |
35 | import ovs.daemon |
36 | import ovs.db.idl | |
8084c011 | 37 | import ovs.unixctl |
53cf9963 | 38 | import ovs.unixctl.server |
df09921d | 39 | |
201bf205 | 40 | vlog = ovs.vlog.Vlog("ovs-xapi-sync") |
df09921d | 41 | session = None |
a256b6e5 | 42 | flush_cache = False |
8084c011 | 43 | exiting = False |
91a9863c | 44 | xapi_down = False |
8084c011 EJ |
45 | |
46 | ||
47 | def unixctl_exit(conn, unused_argv, unused_aux): | |
48 | global exiting | |
49 | exiting = True | |
50 | conn.reply(None) | |
df09921d | 51 | |
e75a1470 | 52 | |
a256b6e5 EJ |
53 | def unixctl_flush_cache(conn, unused_argv, unused_aux): |
54 | global flush_cache | |
55 | flush_cache = True | |
56 | conn.reply(None) | |
57 | ||
58 | ||
df09921d JP |
59 | # Set up a session to interact with XAPI. |
60 | # | |
61 | # On system start-up, OVS comes up before XAPI, so we can't log into the | |
62 | # session until later. Try to do this on-demand, since we won't | |
63 | # actually do anything interesting until XAPI is up. | |
64 | def init_session(): | |
65 | global session | |
66 | if session is not None: | |
67 | return True | |
68 | ||
69 | try: | |
70 | session = XenAPI.xapi_local() | |
71 | session.xenapi.login_with_password("", "") | |
1b0fdca5 | 72 | except XenAPI.Failure, e: |
df09921d | 73 | session = None |
201bf205 | 74 | vlog.warn("Couldn't login to XAPI (%s)" % e) |
df09921d JP |
75 | return False |
76 | ||
77 | return True | |
78 | ||
e75a1470 | 79 | |
5692e384 | 80 | def get_network_by_bridge(br_name): |
df09921d | 81 | if not init_session(): |
201bf205 | 82 | vlog.warn("Failed to get bridge id %s because" |
0d8568c1 | 83 | " XAPI session could not be initialized" % br_name) |
5692e384 | 84 | return None |
df09921d | 85 | |
1dc6839d RH |
86 | recs = session.xenapi.network.get_all_records_where('field "bridge"="%s"' % br_name) |
87 | if len(recs) > 0: | |
88 | return recs.values()[0] | |
5692e384 EJ |
89 | |
90 | return None | |
91 | ||
19af9272 | 92 | # There are possibilities when multiple xs-network-uuids are set for a bridge. |
83d75d32 GS |
93 | # In cases like that, we should choose the bridge-id associated with the bridge |
94 | # name. | |
95 | def get_single_bridge_id(bridge_ids, br_name, default=None): | |
91a9863c | 96 | global xapi_down |
83d75d32 GS |
97 | |
98 | rec = get_network_by_bridge(br_name) | |
99 | if rec and rec['uuid'] in bridge_ids: | |
100 | return rec['uuid'] | |
19af9272 | 101 | |
91a9863c GS |
102 | vlog.warn("Failed to get a single bridge id from Xapi.") |
103 | xapi_down = True | |
19af9272 | 104 | return default |
e75a1470 | 105 | |
5692e384 EJ |
106 | # By default, the "bridge-id" external id in the Bridge table is the |
107 | # same as "xs-network-uuids". This may be overridden by defining a | |
108 | # "nicira-bridge-id" key in the "other_config" field of the network | |
109 | # record of XAPI. If nicira-bridge-id is undefined returns default. | |
110 | # On error returns None. | |
111 | def get_bridge_id(br_name, default=None): | |
112 | rec = get_network_by_bridge(br_name) | |
113 | if rec: | |
df09921d | 114 | return rec['other_config'].get('nicira-bridge-id', default) |
5692e384 | 115 | return None |
df09921d | 116 | |
e75a1470 | 117 | |
69c675a0 | 118 | # By default, the "iface-id" external id in the Interface table is the |
df09921d JP |
119 | # same as "xs-vif-uuid". This may be overridden by defining a |
120 | # "nicira-iface-id" key in the "other_config" field of the VIF | |
121 | # record of XAPI. | |
72238868 | 122 | def get_iface_id(if_name, xs_vif_uuid): |
40043044 | 123 | if not if_name.startswith("vif") and not if_name.startswith("tap"): |
72238868 JP |
124 | # Treat whatever was passed into 'xs_vif_uuid' as a default |
125 | # value for non-VIFs. | |
126 | return xs_vif_uuid | |
df09921d JP |
127 | |
128 | if not init_session(): | |
201bf205 | 129 | vlog.warn("Failed to get interface id %s because" |
0d8568c1 | 130 | " XAPI session could not be initialized" % if_name) |
72238868 | 131 | return xs_vif_uuid |
df09921d | 132 | |
72238868 JP |
133 | try: |
134 | vif = session.xenapi.VIF.get_by_uuid(xs_vif_uuid) | |
135 | rec = session.xenapi.VIF.get_record(vif) | |
136 | return rec['other_config'].get('nicira-iface-id', xs_vif_uuid) | |
137 | except XenAPI.Failure: | |
201bf205 | 138 | vlog.warn("Could not find XAPI entry for VIF %s" % if_name) |
72238868 | 139 | return xs_vif_uuid |
df09921d | 140 | |
e75a1470 | 141 | |
c473936b GS |
142 | # By default, the "vm-id" external id in the Interface table is the |
143 | # same as "xs-vm-uuid". This may be overridden by defining a | |
144 | # "nicira-vm-id" key in the "other_config" field of the VM | |
145 | # record of XAPI. | |
146 | def get_vm_id(if_name, xs_vm_uuid): | |
147 | if not if_name.startswith("vif") and not if_name.startswith("tap"): | |
148 | # Treat whatever was passed into 'xs_vm_uuid' as a default | |
149 | # value for non-VIFs. | |
150 | return xs_vm_uuid | |
151 | ||
152 | if not init_session(): | |
153 | vlog.warn("Failed to get vm id for interface id %s because" | |
154 | " XAPI session could not be initialized" % if_name) | |
155 | return xs_vm_uuid | |
156 | ||
157 | try: | |
158 | vm = session.xenapi.VM.get_by_uuid(xs_vm_uuid) | |
159 | rec = session.xenapi.VM.get_record(vm) | |
160 | return rec['other_config'].get('nicira-vm-id', xs_vm_uuid) | |
161 | except XenAPI.Failure: | |
162 | vlog.warn("Could not find XAPI entry for VIF %s" % if_name) | |
163 | return xs_vm_uuid | |
164 | ||
165 | ||
8cdf0349 BP |
166 | def set_or_delete(d, key, value): |
167 | if value is None: | |
168 | if key in d: | |
169 | del d[key] | |
170 | return True | |
86014a28 | 171 | else: |
8cdf0349 BP |
172 | if d.get(key) != value: |
173 | d[key] = value | |
174 | return True | |
175 | return False | |
86014a28 | 176 | |
e75a1470 | 177 | |
8cdf0349 | 178 | def set_external_id(row, key, value): |
94e0c3ff | 179 | row.verify("external_ids") |
8cdf0349 BP |
180 | external_ids = row.external_ids |
181 | if set_or_delete(external_ids, key, value): | |
182 | row.external_ids = external_ids | |
5692e384 | 183 | |
e75a1470 | 184 | |
a49fe70c JP |
185 | # XenServer does not call interface-reconfigure on internal networks, |
186 | # which is where the fail-mode would normally be set. | |
8cdf0349 BP |
187 | def update_fail_mode(row): |
188 | rec = get_network_by_bridge(row.name) | |
5692e384 EJ |
189 | if not rec: |
190 | return | |
191 | ||
192 | fail_mode = rec['other_config'].get('vswitch-controller-fail-mode') | |
193 | ||
194 | if not fail_mode: | |
195 | pools = session.xenapi.pool.get_all() | |
196 | if len(pools) == 1: | |
197 | prec = session.xenapi.pool.get_record(pools[0]) | |
e75a1470 EJ |
198 | fail_mode = prec['other_config'].get( |
199 | 'vswitch-controller-fail-mode') | |
5692e384 EJ |
200 | |
201 | if fail_mode not in ['standalone', 'secure']: | |
202 | fail_mode = 'standalone' | |
203 | ||
94e0c3ff | 204 | row.verify("fail_mode") |
8cdf0349 BP |
205 | if row.fail_mode != fail_mode: |
206 | row.fail_mode = fail_mode | |
da54975c | 207 | |
e75a1470 | 208 | |
8cdf0349 BP |
209 | def update_in_band_mgmt(row): |
210 | rec = get_network_by_bridge(row.name) | |
da54975c AE |
211 | if not rec: |
212 | return | |
213 | ||
214 | dib = rec['other_config'].get('vswitch-disable-in-band') | |
da54975c | 215 | |
94e0c3ff | 216 | row.verify("other_config") |
8cdf0349 BP |
217 | other_config = row.other_config |
218 | if dib and dib not in ['true', 'false']: | |
201bf205 EJ |
219 | vlog.warn('"%s" isn\'t a valid setting for ' |
220 | "other_config:disable-in-band on %s" % (dib, row.name)) | |
8cdf0349 BP |
221 | elif set_or_delete(other_config, 'disable-in-band', dib): |
222 | row.other_config = other_config | |
b13300c7 | 223 | |
e75a1470 | 224 | |
b153e667 | 225 | def main(): |
91a9863c | 226 | global flush_cache, xapi_down |
5fa555b3 | 227 | |
b153e667 EJ |
228 | parser = argparse.ArgumentParser() |
229 | parser.add_argument("database", metavar="DATABASE", | |
230 | help="A socket on which ovsdb-server is listening.") | |
d2cee5a4 | 231 | parser.add_argument("--root-prefix", metavar="DIR", default='', |
b153e667 EJ |
232 | help="Use DIR as alternate root directory" |
233 | " (for testing).") | |
234 | ||
201bf205 | 235 | ovs.vlog.add_args(parser) |
b153e667 EJ |
236 | ovs.daemon.add_args(parser) |
237 | args = parser.parse_args() | |
201bf205 | 238 | ovs.vlog.handle_args(args) |
b153e667 EJ |
239 | ovs.daemon.handle_args(args) |
240 | ||
b153e667 | 241 | remote = args.database |
bf42f674 EJ |
242 | schema_helper = ovs.db.idl.SchemaHelper() |
243 | schema_helper.register_columns("Bridge", ["name", "external_ids", | |
244 | "other_config", "fail_mode"]) | |
245 | schema_helper.register_columns("Interface", ["name", "external_ids"]) | |
246 | idl = ovs.db.idl.Idl(remote, schema_helper) | |
df09921d JP |
247 | |
248 | ovs.daemon.daemonize() | |
29e21ea2 | 249 | |
8084c011 | 250 | ovs.unixctl.command_register("exit", "", 0, 0, unixctl_exit, None) |
a256b6e5 EJ |
251 | ovs.unixctl.command_register("flush-cache", "", 0, 0, unixctl_flush_cache, |
252 | None) | |
53cf9963 | 253 | error, unixctl_server = ovs.unixctl.server.UnixctlServer.create(None) |
8084c011 EJ |
254 | if error: |
255 | ovs.util.ovs_fatal(error, "could not create unixctl server", vlog) | |
256 | ||
29e21ea2 JP |
257 | # This daemon is usually started before XAPI, but to complete our |
258 | # tasks, we need it. Wait here until it's up. | |
d2cee5a4 | 259 | cookie_file = args.root_prefix + "/var/run/xapi_init_complete.cookie" |
1b0fdca5 | 260 | while not os.path.exists(cookie_file): |
29e21ea2 | 261 | time.sleep(1) |
5fa555b3 | 262 | |
330d7cf4 | 263 | bridges = {} # Map from bridge name to nicira-bridge-id |
e9ade0fe | 264 | iface_ids = {} # Map from xs-vif-uuid to iface-id |
c473936b | 265 | vm_ids = {} # Map from xs-vm-uuid to vm-id |
a8a4d956 | 266 | seqno = idl.change_seqno # Sequence number when we last processed the db |
df09921d | 267 | while True: |
8084c011 EJ |
268 | unixctl_server.run() |
269 | if exiting: | |
270 | break; | |
271 | ||
a8a4d956 | 272 | idl.run() |
91a9863c | 273 | if not xapi_down and not flush_cache and seqno == idl.change_seqno: |
d5beca68 | 274 | poller = ovs.poller.Poller() |
8084c011 | 275 | unixctl_server.wait(poller) |
d5beca68 BP |
276 | idl.wait(poller) |
277 | poller.block() | |
df09921d | 278 | continue |
5fa555b3 | 279 | |
91a9863c GS |
280 | if xapi_down: |
281 | vlog.warn("Xapi is probably down. Retry again after a second.") | |
282 | time.sleep(1) | |
283 | xapi_down = False | |
284 | ||
a256b6e5 EJ |
285 | if flush_cache: |
286 | vlog.info("Flushing cache as the result of unixctl.") | |
e75a1470 | 287 | bridges = {} |
e9ade0fe | 288 | iface_ids = {} |
c473936b | 289 | vm_ids = {} |
a256b6e5 | 290 | flush_cache = False |
a8a4d956 | 291 | seqno = idl.change_seqno |
5fa555b3 | 292 | |
8cdf0349 BP |
293 | txn = ovs.db.idl.Transaction(idl) |
294 | ||
df09921d | 295 | new_bridges = {} |
8cdf0349 | 296 | for row in idl.tables["Bridge"].rows.itervalues(): |
f04f9af0 BP |
297 | bridge_id = bridges.get(row.name) |
298 | if bridge_id is None: | |
299 | # Configure the new bridge. | |
8cdf0349 BP |
300 | update_fail_mode(row) |
301 | update_in_band_mgmt(row) | |
330d7cf4 | 302 | |
f04f9af0 BP |
303 | # Get the correct bridge_id, if we can. |
304 | bridge_id = get_bridge_id(row.name) | |
305 | if bridge_id is None: | |
306 | xs_network_uuids = row.external_ids.get("xs-network-uuids") | |
307 | if xs_network_uuids: | |
308 | bridge_ids = xs_network_uuids.split(";") | |
309 | if len(bridge_ids) == 1: | |
310 | bridge_id = bridge_ids[0] | |
311 | else: | |
312 | bridge_id = get_single_bridge_id(bridge_ids, | |
313 | row.name) | |
314 | set_external_id(row, "bridge-id", bridge_id) | |
330d7cf4 EJ |
315 | |
316 | if bridge_id is not None: | |
f04f9af0 | 317 | new_bridges[row.name] = bridge_id |
8cdf0349 BP |
318 | bridges = new_bridges |
319 | ||
320 | iface_by_name = {} | |
321 | for row in idl.tables["Interface"].rows.itervalues(): | |
322 | iface_by_name[row.name] = row | |
69c675a0 | 323 | |
e9ade0fe | 324 | new_iface_ids = {} |
c473936b | 325 | new_vm_ids = {} |
8cdf0349 BP |
326 | for row in idl.tables["Interface"].rows.itervalues(): |
327 | # Match up paired vif and tap devices. | |
328 | if row.name.startswith("vif"): | |
329 | vif = row | |
330 | tap = iface_by_name.get("tap%s" % row.name[3:]) | |
331 | elif row.name.startswith("tap"): | |
332 | tap = row | |
333 | vif = iface_by_name.get("vif%s" % row.name[3:]) | |
334 | else: | |
335 | tap = vif = None | |
336 | ||
337 | # Several tap external-ids need to be copied from the vif. | |
338 | if row == tap and vif: | |
339 | keys = ["attached-mac", | |
340 | "xs-network-uuid", | |
341 | "xs-vif-uuid", | |
342 | "xs-vm-uuid"] | |
343 | for k in keys: | |
344 | set_external_id(row, k, vif.external_ids.get(k)) | |
345 | ||
e9ade0fe | 346 | # Map from xs-vif-uuid to iface-id. |
8cdf0349 BP |
347 | # |
348 | # (A tap's xs-vif-uuid comes from its vif. That falls out | |
349 | # naturally from the copy loop above.) | |
e9ade0fe BP |
350 | xvu = row.external_ids.get("xs-vif-uuid") |
351 | if xvu: | |
352 | iface_id = (new_iface_ids.get(xvu) | |
353 | or iface_ids.get(xvu) | |
354 | or get_iface_id(row.name, xvu)) | |
355 | new_iface_ids[xvu] = iface_id | |
356 | else: | |
357 | # No xs-vif-uuid therefore no iface-id. | |
358 | iface_id = None | |
359 | set_external_id(row, "iface-id", iface_id) | |
8cdf0349 | 360 | |
c473936b GS |
361 | # Map from xs-vm-uuid to vm-id. |
362 | xvmu = row.external_ids.get("xs-vm-uuid") | |
363 | if xvmu: | |
364 | vm_id = (new_vm_ids.get(xvmu) | |
365 | or vm_ids.get(xvmu) | |
366 | or get_vm_id(row.name, xvmu)) | |
367 | new_vm_ids[xvmu] = vm_id | |
368 | else: | |
369 | vm_id = None | |
370 | set_external_id(row, "vm-id", vm_id) | |
371 | ||
8cdf0349 BP |
372 | # When there's a vif and a tap, the tap is active (used for |
373 | # traffic). When there's just a vif, the vif is active. | |
374 | # | |
375 | # A tap on its own shouldn't happen, and we don't know | |
376 | # anything about other kinds of devices, so we don't use | |
377 | # an iface-status for those devices at all. | |
378 | if vif and tap: | |
379 | set_external_id(tap, "iface-status", "active") | |
380 | set_external_id(vif, "iface-status", "inactive") | |
381 | elif vif: | |
382 | set_external_id(vif, "iface-status", "active") | |
383 | else: | |
384 | set_external_id(row, "iface-status", None) | |
e9ade0fe | 385 | iface_ids = new_iface_ids |
c473936b | 386 | vm_ids = new_vm_ids |
8cdf0349 | 387 | |
932f36eb | 388 | txn.add_comment("ovs-xapi-sync: Updating records from XAPI") |
8cdf0349 | 389 | txn.commit_block() |
69c675a0 | 390 | |
8084c011 EJ |
391 | unixctl_server.close() |
392 | idl.close() | |
393 | ||
e75a1470 | 394 | |
df09921d JP |
395 | if __name__ == '__main__': |
396 | try: | |
b153e667 | 397 | main() |
0d8568c1 EJ |
398 | except SystemExit: |
399 | # Let system.exit() calls complete normally | |
400 | raise | |
401 | except: | |
201bf205 | 402 | vlog.exception("traceback") |
998bb652 | 403 | sys.exit(ovs.daemon.RESTART_EXIT_CODE) |