]> git.proxmox.com Git - mirror_ovs.git/blame - ovn/utilities/ovn-docker-overlay-driver
ovn-trace: Print stage name even without match.
[mirror_ovs.git] / ovn / utilities / ovn-docker-overlay-driver
CommitLineData
eaa923e3
GS
1#! /usr/bin/python
2# Copyright (C) 2015 Nicira, Inc.
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
16import argparse
17import ast
18import atexit
19import json
20import os
21import random
22import re
23import shlex
24import subprocess
25import sys
26
27import ovs.dirs
28import ovs.util
29import ovs.daemon
30import ovs.vlog
31
32from flask import Flask, jsonify
33from flask import request, abort
34
35app = Flask(__name__)
36vlog = ovs.vlog.Vlog("ovn-docker-overlay-driver")
37
38OVN_BRIDGE = "br-int"
d61fbedc 39OVN_NB = ""
eaa923e3
GS
40PLUGIN_DIR = "/etc/docker/plugins"
41PLUGIN_FILE = "/etc/docker/plugins/openvswitch.spec"
42
43
44def call_popen(cmd):
45 child = subprocess.Popen(cmd, stdout=subprocess.PIPE)
46 output = child.communicate()
47 if child.returncode:
48 raise RuntimeError("Fatal error executing %s" % (cmd))
49 if len(output) == 0 or output[0] == None:
50 output = ""
51 else:
52 output = output[0].strip()
53 return output
54
55
56def call_prog(prog, args_list):
57 cmd = [prog, "--timeout=5", "-vconsole:off"] + args_list
58 return call_popen(cmd)
59
60
61def ovs_vsctl(*args):
62 return call_prog("ovs-vsctl", list(args))
63
64
65def ovn_nbctl(*args):
66 args_list = list(args)
d61fbedc 67 database_option = "%s=%s" % ("--db", OVN_NB)
eaa923e3
GS
68 args_list.insert(0, database_option)
69 return call_prog("ovn-nbctl", args_list)
70
71
72def cleanup():
73 if os.path.isfile(PLUGIN_FILE):
74 os.remove(PLUGIN_FILE)
75
76
77def ovn_init_overlay():
78 br_list = ovs_vsctl("list-br").split()
79 if OVN_BRIDGE not in br_list:
80 ovs_vsctl("--", "--may-exist", "add-br", OVN_BRIDGE,
81 "--", "set", "bridge", OVN_BRIDGE,
82 "external_ids:bridge-id=" + OVN_BRIDGE,
83 "other-config:disable-in-band=true", "fail-mode=secure")
84
d61fbedc
GS
85 global OVN_NB
86 OVN_NB = ovs_vsctl("get", "Open_vSwitch", ".",
87 "external_ids:ovn-nb").strip('"')
88 if not OVN_NB:
eaa923e3
GS
89 sys.exit("OVN central database's ip address not set")
90
91 ovs_vsctl("set", "open_vswitch", ".",
92 "external_ids:ovn-bridge=" + OVN_BRIDGE)
93
94
95def prepare():
96 parser = argparse.ArgumentParser()
97
98 ovs.vlog.add_args(parser)
99 ovs.daemon.add_args(parser)
100 args = parser.parse_args()
101 ovs.vlog.handle_args(args)
102 ovs.daemon.handle_args(args)
103 ovn_init_overlay()
104
105 if not os.path.isdir(PLUGIN_DIR):
106 os.makedirs(PLUGIN_DIR)
107
108 ovs.daemon.daemonize()
109 try:
110 fo = open(PLUGIN_FILE, "w")
111 fo.write("tcp://0.0.0.0:5000")
112 fo.close()
113 except Exception as e:
114 ovs.util.ovs_fatal(0, "Failed to write to spec file (%s)" % str(e),
115 vlog)
116
117 atexit.register(cleanup)
118
119
120@app.route('/Plugin.Activate', methods=['POST'])
121def plugin_activate():
122 return jsonify({"Implements": ["NetworkDriver"]})
123
124
125@app.route('/NetworkDriver.GetCapabilities', methods=['POST'])
126def get_capability():
127 return jsonify({"Scope": "global"})
128
129
130@app.route('/NetworkDriver.DiscoverNew', methods=['POST'])
131def new_discovery():
132 return jsonify({})
133
134
135@app.route('/NetworkDriver.DiscoverDelete', methods=['POST'])
136def delete_discovery():
137 return jsonify({})
138
139
140@app.route('/NetworkDriver.CreateNetwork', methods=['POST'])
141def create_network():
142 if not request.data:
143 abort(400)
144
145 data = json.loads(request.data)
146
147 # NetworkID will have docker generated network uuid and it
148 # becomes 'name' in a OVN Logical switch record.
149 network = data.get("NetworkID", "")
150 if not network:
151 abort(400)
152
153 # Limit subnet handling to ipv4 till ipv6 usecase is clear.
154 ipv4_data = data.get("IPv4Data", "")
155 if not ipv4_data:
156 error = "create_network: No ipv4 subnet provided"
157 return jsonify({'Err': error})
158
159 subnet = ipv4_data[0].get("Pool", "")
160 if not subnet:
161 error = "create_network: no subnet in ipv4 data from libnetwork"
162 return jsonify({'Err': error})
163
164 gateway_ip = ipv4_data[0].get("Gateway", "").rsplit('/', 1)[0]
165 if not gateway_ip:
166 error = "create_network: no gateway in ipv4 data from libnetwork"
167 return jsonify({'Err': error})
168
169 try:
ea46a4e9 170 ovn_nbctl("ls-add", network, "--", "set", "Logical_Switch",
eaa923e3
GS
171 network, "external_ids:subnet=" + subnet,
172 "external_ids:gateway_ip=" + gateway_ip)
173 except Exception as e:
ea46a4e9 174 error = "create_network: ls-add %s" % (str(e))
eaa923e3
GS
175 return jsonify({'Err': error})
176
177 return jsonify({})
178
179
180@app.route('/NetworkDriver.DeleteNetwork', methods=['POST'])
181def delete_network():
182 if not request.data:
183 abort(400)
184
185 data = json.loads(request.data)
186
187 nid = data.get("NetworkID", "")
188 if not nid:
189 abort(400)
190
191 try:
ea46a4e9 192 ovn_nbctl("ls-del", nid)
eaa923e3 193 except Exception as e:
ea46a4e9 194 error = "delete_network: ls-del %s" % (str(e))
eaa923e3
GS
195 return jsonify({'Err': error})
196
197 return jsonify({})
198
199
200@app.route('/NetworkDriver.CreateEndpoint', methods=['POST'])
201def create_endpoint():
202 if not request.data:
203 abort(400)
204
205 data = json.loads(request.data)
206
207 nid = data.get("NetworkID", "")
208 if not nid:
209 abort(400)
210
211 eid = data.get("EndpointID", "")
212 if not eid:
213 abort(400)
214
215 interface = data.get("Interface", "")
216 if not interface:
217 error = "create_endpoint: no interfaces structure supplied by " \
218 "libnetwork"
219 return jsonify({'Err': error})
220
221 ip_address_and_mask = interface.get("Address", "")
222 if not ip_address_and_mask:
223 error = "create_endpoint: ip address not provided by libnetwork"
224 return jsonify({'Err': error})
225
226 ip_address = ip_address_and_mask.rsplit('/', 1)[0]
227 mac_address_input = interface.get("MacAddress", "")
228 mac_address_output = ""
229
230 try:
31ed1192 231 ovn_nbctl("lsp-add", nid, eid)
eaa923e3 232 except Exception as e:
31ed1192 233 error = "create_endpoint: lsp-add (%s)" % (str(e))
eaa923e3
GS
234 return jsonify({'Err': error})
235
236 if not mac_address_input:
237 mac_address = "02:%02x:%02x:%02x:%02x:%02x" % (random.randint(0, 255),
238 random.randint(0, 255),
239 random.randint(0, 255),
240 random.randint(0, 255),
241 random.randint(0, 255))
242 else:
243 mac_address = mac_address_input
244
245 try:
31ed1192 246 ovn_nbctl("lsp-set-addresses", eid,
eaa923e3
GS
247 mac_address + " " + ip_address)
248 except Exception as e:
31ed1192 249 error = "create_endpoint: lsp-set-addresses (%s)" % (str(e))
eaa923e3
GS
250 return jsonify({'Err': error})
251
252 # Only return a mac address if one did not come as request.
253 mac_address_output = ""
254 if not mac_address_input:
255 mac_address_output = mac_address
256
257 return jsonify({"Interface": {
258 "Address": "",
259 "AddressIPv6": "",
260 "MacAddress": mac_address_output
261 }})
262
263
80f408f4
JP
264def get_lsp_addresses(eid):
265 ret = ovn_nbctl("--if-exists", "get", "Logical_Switch_Port", eid,
266 "addresses")
eaa923e3
GS
267 if not ret:
268 error = "endpoint not found in OVN database"
269 return (None, None, error)
270 addresses = ast.literal_eval(ret)
271 if len(addresses) == 0:
272 error = "unexpected return while fetching addresses"
273 return (None, None, error)
274 (mac_address, ip_address) = addresses[0].split()
275 return (mac_address, ip_address, None)
276
277
278@app.route('/NetworkDriver.EndpointOperInfo', methods=['POST'])
279def show_endpoint():
280 if not request.data:
281 abort(400)
282
283 data = json.loads(request.data)
284
285 nid = data.get("NetworkID", "")
286 if not nid:
287 abort(400)
288
289 eid = data.get("EndpointID", "")
290 if not eid:
291 abort(400)
292
293 try:
80f408f4 294 (mac_address, ip_address, error) = get_lsp_addresses(eid)
eaa923e3
GS
295 if error:
296 jsonify({'Err': error})
297 except Exception as e:
31ed1192
JP
298 error = "show_endpoint: get Logical_Switch_Port addresses. (%s)" \
299 % (str(e))
eaa923e3
GS
300 return jsonify({'Err': error})
301
302 veth_outside = eid[0:15]
303 return jsonify({"Value": {"ip_address": ip_address,
304 "mac_address": mac_address,
305 "veth_outside": veth_outside
306 }})
307
308
309@app.route('/NetworkDriver.DeleteEndpoint', methods=['POST'])
310def delete_endpoint():
311 if not request.data:
312 abort(400)
313
314 data = json.loads(request.data)
315
316 nid = data.get("NetworkID", "")
317 if not nid:
318 abort(400)
319
320 eid = data.get("EndpointID", "")
321 if not eid:
322 abort(400)
323
324 try:
31ed1192 325 ovn_nbctl("lsp-del", eid)
eaa923e3 326 except Exception as e:
31ed1192 327 error = "delete_endpoint: lsp-del %s" % (str(e))
eaa923e3
GS
328 return jsonify({'Err': error})
329
330 return jsonify({})
331
332
333@app.route('/NetworkDriver.Join', methods=['POST'])
334def network_join():
335 if not request.data:
336 abort(400)
337
338 data = json.loads(request.data)
339
340 nid = data.get("NetworkID", "")
341 if not nid:
342 abort(400)
343
344 eid = data.get("EndpointID", "")
345 if not eid:
346 abort(400)
347
348 sboxkey = data.get("SandboxKey", "")
349 if not sboxkey:
350 abort(400)
351
352 # sboxkey is of the form: /var/run/docker/netns/CONTAINER_ID
353 vm_id = sboxkey.rsplit('/')[-1]
354
355 try:
80f408f4 356 (mac_address, ip_address, error) = get_lsp_addresses(eid)
eaa923e3
GS
357 if error:
358 jsonify({'Err': error})
359 except Exception as e:
360 error = "network_join: %s" % (str(e))
361 return jsonify({'Err': error})
362
363 veth_outside = eid[0:15]
364 veth_inside = eid[0:13] + "_c"
365 command = "ip link add %s type veth peer name %s" \
366 % (veth_inside, veth_outside)
367 try:
368 call_popen(shlex.split(command))
369 except Exception as e:
370 error = "network_join: failed to create veth pair (%s)" % (str(e))
371 return jsonify({'Err': error})
372
373 command = "ip link set dev %s address %s" \
374 % (veth_inside, mac_address)
375
376 try:
377 call_popen(shlex.split(command))
378 except Exception as e:
379 error = "network_join: failed to set veth mac address (%s)" % (str(e))
380 return jsonify({'Err': error})
381
382 command = "ip link set %s up" % (veth_outside)
383
384 try:
385 call_popen(shlex.split(command))
386 except Exception as e:
387 error = "network_join: failed to up the veth interface (%s)" % (str(e))
388 return jsonify({'Err': error})
389
390 try:
391 ovs_vsctl("add-port", OVN_BRIDGE, veth_outside)
392 ovs_vsctl("set", "interface", veth_outside,
393 "external_ids:attached-mac=" + mac_address,
394 "external_ids:iface-id=" + eid,
395 "external_ids:vm-id=" + vm_id,
396 "external_ids:iface-status=active")
397 except Exception as e:
398 error = "network_join: failed to create a port (%s)" % (str(e))
399 return jsonify({'Err': error})
400
401 return jsonify({"InterfaceName": {
402 "SrcName": veth_inside,
403 "DstPrefix": "eth"
404 },
405 "Gateway": "",
406 "GatewayIPv6": ""})
407
408
409@app.route('/NetworkDriver.Leave', methods=['POST'])
410def network_leave():
411 if not request.data:
412 abort(400)
413
414 data = json.loads(request.data)
415
416 nid = data.get("NetworkID", "")
417 if not nid:
418 abort(400)
419
420 eid = data.get("EndpointID", "")
421 if not eid:
422 abort(400)
423
424 veth_outside = eid[0:15]
425 command = "ip link delete %s" % (veth_outside)
426 try:
427 call_popen(shlex.split(command))
428 except Exception as e:
429 error = "network_leave: failed to delete veth pair (%s)" % (str(e))
430 return jsonify({'Err': error})
431
432 try:
433 ovs_vsctl("--if-exists", "del-port", veth_outside)
434 except Exception as e:
435 error = "network_leave: failed to delete port (%s)" % (str(e))
436 return jsonify({'Err': error})
437
438 return jsonify({})
439
440if __name__ == '__main__':
441 prepare()
442 app.run(host='0.0.0.0')