2 # Copyright (C) 2015 Nicira, Inc.
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:
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
32 from flask import Flask, jsonify
33 from flask import request, abort
36 vlog = ovs.vlog.Vlog("ovn-docker-overlay-driver")
40 PLUGIN_DIR = "/etc/docker/plugins"
41 PLUGIN_FILE = "/etc/docker/plugins/openvswitch.spec"
45 child = subprocess.Popen(cmd, stdout=subprocess.PIPE)
46 output = child.communicate()
48 raise RuntimeError("Fatal error executing %s" % (cmd))
49 if len(output) == 0 or output[0] == None:
52 output = output[0].strip()
56 def call_prog(prog, args_list):
57 cmd = [prog, "--timeout=5", "-vconsole:off"] + args_list
58 return call_popen(cmd)
62 return call_prog("ovs-vsctl", list(args))
66 args_list = list(args)
67 database_option = "%s=%s" % ("--db", OVN_REMOTE)
68 args_list.insert(0, database_option)
69 return call_prog("ovn-nbctl", args_list)
73 if os.path.isfile(PLUGIN_FILE):
74 os.remove(PLUGIN_FILE)
77 def 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")
86 OVN_REMOTE = ovs_vsctl("get", "Open_vSwitch", ".",
87 "external_ids:ovn-remote").strip('"')
89 sys.exit("OVN central database's ip address not set")
91 ovs_vsctl("set", "open_vswitch", ".",
92 "external_ids:ovn-bridge=" + OVN_BRIDGE)
96 parser = argparse.ArgumentParser()
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)
105 if not os.path.isdir(PLUGIN_DIR):
106 os.makedirs(PLUGIN_DIR)
108 ovs.daemon.daemonize()
110 fo = open(PLUGIN_FILE, "w")
111 fo.write("tcp://0.0.0.0:5000")
113 except Exception as e:
114 ovs.util.ovs_fatal(0, "Failed to write to spec file (%s)" % str(e),
117 atexit.register(cleanup)
120 @app.route('/Plugin.Activate', methods=['POST'])
121 def plugin_activate():
122 return jsonify({"Implements": ["NetworkDriver"]})
125 @app.route('/NetworkDriver.GetCapabilities', methods=['POST'])
126 def get_capability():
127 return jsonify({"Scope": "global"})
130 @app.route('/NetworkDriver.DiscoverNew', methods=['POST'])
135 @app.route('/NetworkDriver.DiscoverDelete', methods=['POST'])
136 def delete_discovery():
140 @app.route('/NetworkDriver.CreateNetwork', methods=['POST'])
141 def create_network():
145 data = json.loads(request.data)
147 # NetworkID will have docker generated network uuid and it
148 # becomes 'name' in a OVN Logical switch record.
149 network = data.get("NetworkID", "")
153 # Limit subnet handling to ipv4 till ipv6 usecase is clear.
154 ipv4_data = data.get("IPv4Data", "")
156 error = "create_network: No ipv4 subnet provided"
157 return jsonify({'Err': error})
159 subnet = ipv4_data[0].get("Pool", "")
161 error = "create_network: no subnet in ipv4 data from libnetwork"
162 return jsonify({'Err': error})
164 gateway_ip = ipv4_data[0].get("Gateway", "").rsplit('/', 1)[0]
166 error = "create_network: no gateway in ipv4 data from libnetwork"
167 return jsonify({'Err': error})
170 ovn_nbctl("lswitch-add", network, "--", "set", "Logical_Switch",
171 network, "external_ids:subnet=" + subnet,
172 "external_ids:gateway_ip=" + gateway_ip)
173 except Exception as e:
174 error = "create_network: lswitch-add %s" % (str(e))
175 return jsonify({'Err': error})
180 @app.route('/NetworkDriver.DeleteNetwork', methods=['POST'])
181 def delete_network():
185 data = json.loads(request.data)
187 nid = data.get("NetworkID", "")
192 ovn_nbctl("lswitch-del", nid)
193 except Exception as e:
194 error = "delete_network: lswitch-del %s" % (str(e))
195 return jsonify({'Err': error})
200 @app.route('/NetworkDriver.CreateEndpoint', methods=['POST'])
201 def create_endpoint():
205 data = json.loads(request.data)
207 nid = data.get("NetworkID", "")
211 eid = data.get("EndpointID", "")
215 interface = data.get("Interface", "")
217 error = "create_endpoint: no interfaces structure supplied by " \
219 return jsonify({'Err': error})
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})
226 ip_address = ip_address_and_mask.rsplit('/', 1)[0]
227 mac_address_input = interface.get("MacAddress", "")
228 mac_address_output = ""
231 ovn_nbctl("lport-add", nid, eid)
232 except Exception as e:
233 error = "create_endpoint: lport-add (%s)" % (str(e))
234 return jsonify({'Err': error})
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))
243 mac_address = mac_address_input
246 ovn_nbctl("lport-set-addresses", eid,
247 mac_address + " " + ip_address)
248 except Exception as e:
249 error = "create_endpoint: lport-set-addresses (%s)" % (str(e))
250 return jsonify({'Err': error})
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
257 return jsonify({"Interface": {
260 "MacAddress": mac_address_output
264 def get_logical_port_addresses(eid):
265 ret = ovn_nbctl("--if-exists", "get", "Logical_port", eid, "addresses")
267 error = "endpoint not found in OVN database"
268 return (None, None, error)
269 addresses = ast.literal_eval(ret)
270 if len(addresses) == 0:
271 error = "unexpected return while fetching addresses"
272 return (None, None, error)
273 (mac_address, ip_address) = addresses[0].split()
274 return (mac_address, ip_address, None)
277 @app.route('/NetworkDriver.EndpointOperInfo', methods=['POST'])
282 data = json.loads(request.data)
284 nid = data.get("NetworkID", "")
288 eid = data.get("EndpointID", "")
293 (mac_address, ip_address, error) = get_logical_port_addresses(eid)
295 jsonify({'Err': error})
296 except Exception as e:
297 error = "show_endpoint: get Logical_port addresses. (%s)" % (str(e))
298 return jsonify({'Err': error})
300 veth_outside = eid[0:15]
301 return jsonify({"Value": {"ip_address": ip_address,
302 "mac_address": mac_address,
303 "veth_outside": veth_outside
307 @app.route('/NetworkDriver.DeleteEndpoint', methods=['POST'])
308 def delete_endpoint():
312 data = json.loads(request.data)
314 nid = data.get("NetworkID", "")
318 eid = data.get("EndpointID", "")
323 ovn_nbctl("lport-del", eid)
324 except Exception as e:
325 error = "delete_endpoint: lport-del %s" % (str(e))
326 return jsonify({'Err': error})
331 @app.route('/NetworkDriver.Join', methods=['POST'])
336 data = json.loads(request.data)
338 nid = data.get("NetworkID", "")
342 eid = data.get("EndpointID", "")
346 sboxkey = data.get("SandboxKey", "")
350 # sboxkey is of the form: /var/run/docker/netns/CONTAINER_ID
351 vm_id = sboxkey.rsplit('/')[-1]
354 (mac_address, ip_address, error) = get_logical_port_addresses(eid)
356 jsonify({'Err': error})
357 except Exception as e:
358 error = "network_join: %s" % (str(e))
359 return jsonify({'Err': error})
361 veth_outside = eid[0:15]
362 veth_inside = eid[0:13] + "_c"
363 command = "ip link add %s type veth peer name %s" \
364 % (veth_inside, veth_outside)
366 call_popen(shlex.split(command))
367 except Exception as e:
368 error = "network_join: failed to create veth pair (%s)" % (str(e))
369 return jsonify({'Err': error})
371 command = "ip link set dev %s address %s" \
372 % (veth_inside, mac_address)
375 call_popen(shlex.split(command))
376 except Exception as e:
377 error = "network_join: failed to set veth mac address (%s)" % (str(e))
378 return jsonify({'Err': error})
380 command = "ip link set %s up" % (veth_outside)
383 call_popen(shlex.split(command))
384 except Exception as e:
385 error = "network_join: failed to up the veth interface (%s)" % (str(e))
386 return jsonify({'Err': error})
389 ovs_vsctl("add-port", OVN_BRIDGE, veth_outside)
390 ovs_vsctl("set", "interface", veth_outside,
391 "external_ids:attached-mac=" + mac_address,
392 "external_ids:iface-id=" + eid,
393 "external_ids:vm-id=" + vm_id,
394 "external_ids:iface-status=active")
395 except Exception as e:
396 error = "network_join: failed to create a port (%s)" % (str(e))
397 return jsonify({'Err': error})
399 return jsonify({"InterfaceName": {
400 "SrcName": veth_inside,
407 @app.route('/NetworkDriver.Leave', methods=['POST'])
412 data = json.loads(request.data)
414 nid = data.get("NetworkID", "")
418 eid = data.get("EndpointID", "")
422 veth_outside = eid[0:15]
423 command = "ip link delete %s" % (veth_outside)
425 call_popen(shlex.split(command))
426 except Exception as e:
427 error = "network_leave: failed to delete veth pair (%s)" % (str(e))
428 return jsonify({'Err': error})
431 ovs_vsctl("--if-exists", "del-port", veth_outside)
432 except Exception as e:
433 error = "network_leave: failed to delete port (%s)" % (str(e))
434 return jsonify({'Err': error})
438 if __name__ == '__main__':
440 app.run(host='0.0.0.0')