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.
31 import ovs.unixctl.server
34 from neutronclient.v2_0 import client
35 from flask import Flask, jsonify
36 from flask import request, abort
39 vlog = ovs.vlog.Vlog("ovn-docker-underlay-driver")
46 PLUGIN_DIR = "/etc/docker/plugins"
47 PLUGIN_FILE = "/etc/docker/plugins/openvswitch.spec"
54 child = subprocess.Popen(cmd, stdout=subprocess.PIPE)
55 output = child.communicate()
57 raise RuntimeError("Fatal error executing %s" % (cmd))
58 if len(output) == 0 or output[0] == None:
61 output = output[0].strip()
65 def call_prog(prog, args_list):
66 cmd = [prog, "--timeout=5", "-vconsole:off"] + args_list
67 return call_popen(cmd)
71 return call_prog("ovs-vsctl", list(args))
75 if os.path.isfile(PLUGIN_FILE):
76 os.remove(PLUGIN_FILE)
79 def ovn_init_underlay(args):
80 global USERNAME, PASSWORD, TENANT_ID, AUTH_URL, AUTH_STRATEGY, VIF_ID
84 sys.exit("OVS bridge name not provided")
85 OVN_BRIDGE = args.bridge
87 VIF_ID = os.environ.get('OS_VIF_ID', '')
89 sys.exit("env OS_VIF_ID not set")
90 USERNAME = os.environ.get('OS_USERNAME', '')
92 sys.exit("env OS_USERNAME not set")
93 TENANT_ID = os.environ.get('OS_TENANT_ID', '')
95 sys.exit("env OS_TENANT_ID not set")
96 AUTH_URL = os.environ.get('OS_AUTH_URL', '')
98 sys.exit("env OS_AUTH_URL not set")
99 AUTH_STRATEGY = "keystone"
101 PASSWORD = os.environ.get('OS_PASSWORD', '')
103 PASSWORD = getpass.getpass()
107 parser = argparse.ArgumentParser()
108 parser.add_argument('--bridge', help="The Bridge to which containers "
109 "interfaces connect to.")
111 ovs.vlog.add_args(parser)
112 ovs.daemon.add_args(parser)
113 args = parser.parse_args()
114 ovs.vlog.handle_args(args)
115 ovs.daemon.handle_args(args)
116 ovn_init_underlay(args)
118 if not os.path.isdir(PLUGIN_DIR):
119 os.makedirs(PLUGIN_DIR)
121 ovs.daemon.daemonize()
123 fo = open(PLUGIN_FILE, "w")
124 fo.write("tcp://127.0.0.1:5000")
126 except Exception as e:
127 ovs.util.ovs_fatal(0, "Failed to write to spec file (%s)" % str(e),
130 atexit.register(cleanup)
133 @app.route('/Plugin.Activate', methods=['POST'])
134 def plugin_activate():
135 return jsonify({"Implements": ["NetworkDriver"]})
138 @app.route('/NetworkDriver.GetCapabilities', methods=['POST'])
139 def get_capability():
140 return jsonify({"Scope": "global"})
143 @app.route('/NetworkDriver.DiscoverNew', methods=['POST'])
148 @app.route('/NetworkDriver.DiscoverDelete', methods=['POST'])
149 def delete_discovery():
155 neutron = client.Client(username=USERNAME,
159 endpoint_url=ENDPOINT_URL,
160 auth_strategy=AUTH_STRATEGY)
161 except Exception as e:
162 raise RuntimeError("Failed to login into Neutron(%s)" % str(e))
166 def get_networkuuid_by_name(neutron, name):
167 param = {'fields': 'id', 'name': name}
168 ret = neutron.list_networks(**param)
169 if len(ret['networks']) > 1:
170 raise RuntimeError("More than one network for the given name")
171 elif len(ret['networks']) == 0:
174 network = ret['networks'][0]['id']
178 def get_subnetuuid_by_name(neutron, name):
179 param = {'fields': 'id', 'name': name}
180 ret = neutron.list_subnets(**param)
181 if len(ret['subnets']) > 1:
182 raise RuntimeError("More than one subnet for the given name")
183 elif len(ret['subnets']) == 0:
186 subnet = ret['subnets'][0]['id']
190 @app.route('/NetworkDriver.CreateNetwork', methods=['POST'])
191 def create_network():
195 data = json.loads(request.data)
197 # NetworkID will have docker generated network uuid and it
198 # becomes 'name' in a neutron network record.
199 network = data.get("NetworkID", "")
203 # Limit subnet handling to ipv4 till ipv6 usecase is clear.
204 ipv4_data = data.get("IPv4Data", "")
206 error = "create_network: No ipv4 subnet provided"
207 return jsonify({'Err': error})
209 subnet = ipv4_data[0].get("Pool", "")
211 error = "create_network: no subnet in ipv4 data from libnetwork"
212 return jsonify({'Err': error})
214 gateway_ip = ipv4_data[0].get("Gateway", "").rsplit('/', 1)[0]
216 error = "create_network: no gateway in ipv4 data from libnetwork"
217 return jsonify({'Err': error})
220 neutron = neutron_login()
221 except Exception as e:
222 error = "create_network: neutron login. (%s)" % (str(e))
223 return jsonify({'Err': error})
226 if get_networkuuid_by_name(neutron, network):
227 error = "create_network: network has already been created"
228 return jsonify({'Err': error})
229 except Exception as e:
230 error = "create_network: neutron network uuid by name. (%s)" % (str(e))
231 return jsonify({'Err': error})
234 body = {'network': {'name': network, 'admin_state_up': True}}
235 ret = neutron.create_network(body)
236 network_id = ret['network']['id']
237 except Exception as e:
238 error = "create_network: neutron net-create call. (%s)" % str(e)
239 return jsonify({'Err': error})
241 subnet_name = "docker-%s" % (network)
244 body = {'subnet': {'network_id': network_id,
247 'gateway_ip': gateway_ip,
248 'name': subnet_name}}
249 created_subnet = neutron.create_subnet(body)
250 except Exception as e:
251 error = "create_network: neutron subnet-create call. (%s)" % str(e)
252 return jsonify({'Err': error})
257 @app.route('/NetworkDriver.DeleteNetwork', methods=['POST'])
258 def delete_network():
262 data = json.loads(request.data)
264 nid = data.get("NetworkID", "")
269 neutron = neutron_login()
270 except Exception as e:
271 error = "delete_network: neutron login. (%s)" % (str(e))
272 return jsonify({'Err': error})
275 network = get_networkuuid_by_name(neutron, nid)
277 error = "delete_network: failed in network by name. (%s)" % (nid)
278 return jsonify({'Err': error})
279 except Exception as e:
280 error = "delete_network: network uuid by name. (%s)" % (str(e))
281 return jsonify({'Err': error})
284 neutron.delete_network(network)
285 except Exception as e:
286 error = "delete_network: neutron net-delete. (%s)" % str(e)
287 return jsonify({'Err': error})
294 vlans = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".",
295 "external_ids:vlans").strip('"')
298 ovs_vsctl("set", "Open_vSwitch", ".",
299 "external_ids:vlans=" + str(reserved_vlan))
302 vlan_set = str(vlans).split(',')
304 for vlan in range(1, 4095):
305 if str(vlan) not in vlan_set:
306 vlan_set.append(str(vlan))
308 vlans = re.sub(r'[ \[\]\']', '', str(vlan_set))
309 ovs_vsctl("set", "Open_vSwitch", ".",
310 "external_ids:vlans=" + vlans)
313 if not reserved_vlan:
314 raise RuntimeError("No more vlans available on this host")
317 def unreserve_vlan(reserved_vlan):
318 vlans = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".",
319 "external_ids:vlans").strip('"')
323 vlan_set = str(vlans).split(',')
324 if str(reserved_vlan) not in vlan_set:
327 vlan_set.remove(str(reserved_vlan))
328 vlans = re.sub(r'[ \[\]\']', '', str(vlan_set))
330 ovs_vsctl("set", "Open_vSwitch", ".", "external_ids:vlans=" + vlans)
332 ovs_vsctl("remove", "Open_vSwitch", ".", "external_ids", "vlans")
335 def create_port_underlay(neutron, network, eid, ip_address, mac_address):
336 reserved_vlan = reserve_vlan()
338 body = {'port': {'network_id': network,
339 'binding:profile': {'parent_name': VIF_ID,
340 'tag': int(reserved_vlan)},
341 'mac_address': mac_address,
342 'fixed_ips': [{'ip_address': ip_address}],
344 'admin_state_up': True}}
346 body = {'port': {'network_id': network,
347 'binding:profile': {'parent_name': VIF_ID,
348 'tag': int(reserved_vlan)},
349 'fixed_ips': [{'ip_address': ip_address}],
351 'admin_state_up': True}}
354 ret = neutron.create_port(body)
355 mac_address = ret['port']['mac_address']
356 except Exception as e:
357 unreserve_vlan(reserved_vlan)
358 raise RuntimeError("Failed in creation of neutron port (%s)." % str(e))
360 ovs_vsctl("set", "Open_vSwitch", ".",
361 "external_ids:" + eid + "_vlan=" + str(reserved_vlan))
366 def get_endpointuuid_by_name(neutron, name):
367 param = {'fields': 'id', 'name': name}
368 ret = neutron.list_ports(**param)
369 if len(ret['ports']) > 1:
370 raise RuntimeError("More than one endpoint for the given name")
371 elif len(ret['ports']) == 0:
374 endpoint = ret['ports'][0]['id']
378 @app.route('/NetworkDriver.CreateEndpoint', methods=['POST'])
379 def create_endpoint():
383 data = json.loads(request.data)
385 nid = data.get("NetworkID", "")
389 eid = data.get("EndpointID", "")
393 interface = data.get("Interface", "")
395 error = "create_endpoint: no interfaces supplied by libnetwork"
396 return jsonify({'Err': error})
398 ip_address_and_mask = interface.get("Address", "")
399 if not ip_address_and_mask:
400 error = "create_endpoint: ip address not provided by libnetwork"
401 return jsonify({'Err': error})
403 ip_address = ip_address_and_mask.rsplit('/', 1)[0]
404 mac_address_input = interface.get("MacAddress", "")
405 mac_address_output = ""
408 neutron = neutron_login()
409 except Exception as e:
410 error = "create_endpoint: neutron login. (%s)" % (str(e))
411 return jsonify({'Err': error})
414 endpoint = get_endpointuuid_by_name(neutron, eid)
416 error = "create_endpoint: Endpoint has already been created"
417 return jsonify({'Err': error})
418 except Exception as e:
419 error = "create_endpoint: endpoint uuid by name. (%s)" % (str(e))
420 return jsonify({'Err': error})
423 network = get_networkuuid_by_name(neutron, nid)
425 error = "Failed to get neutron network record for (%s)" % (nid)
426 return jsonify({'Err': error})
427 except Exception as e:
428 error = "create_endpoint: network uuid by name. (%s)" % (str(e))
429 return jsonify({'Err': error})
432 mac_address = create_port_underlay(neutron, network, eid, ip_address,
434 except Exception as e:
435 error = "create_endpoint: neutron port-create (%s)" % (str(e))
436 return jsonify({'Err': error})
438 if not mac_address_input:
439 mac_address_output = mac_address
441 return jsonify({"Interface": {
444 "MacAddress": mac_address_output
448 @app.route('/NetworkDriver.EndpointOperInfo', methods=['POST'])
453 data = json.loads(request.data)
455 nid = data.get("NetworkID", "")
459 eid = data.get("EndpointID", "")
464 neutron = neutron_login()
465 except Exception as e:
466 error = "%s" % (str(e))
467 return jsonify({'Err': error})
470 endpoint = get_endpointuuid_by_name(neutron, eid)
472 error = "show_endpoint: Failed to get endpoint by name"
473 return jsonify({'Err': error})
474 except Exception as e:
475 error = "show_endpoint: get endpoint by name. (%s)" % (str(e))
476 return jsonify({'Err': error})
479 ret = neutron.show_port(endpoint)
480 mac_address = ret['port']['mac_address']
481 ip_address = ret['port']['fixed_ips'][0]['ip_address']
482 except Exception as e:
483 error = "show_endpoint: show port (%s)" % (str(e))
484 return jsonify({'Err': error})
486 veth_outside = eid[0:15]
487 return jsonify({"Value": {"ip_address": ip_address,
488 "mac_address": mac_address,
489 "veth_outside": veth_outside
493 @app.route('/NetworkDriver.DeleteEndpoint', methods=['POST'])
494 def delete_endpoint():
498 data = json.loads(request.data)
500 nid = data.get("NetworkID", "")
504 eid = data.get("EndpointID", "")
509 neutron = neutron_login()
510 except Exception as e:
511 error = "delete_endpoint: neutron login (%s)" % (str(e))
512 return jsonify({'Err': error})
514 endpoint = get_endpointuuid_by_name(neutron, eid)
518 reserved_vlan = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".",
519 "external_ids:" + eid + "_vlan").strip('"')
521 unreserve_vlan(reserved_vlan)
522 ovs_vsctl("remove", "Open_vSwitch", ".", "external_ids",
526 neutron.delete_port(endpoint)
527 except Exception as e:
528 error = "delete_endpoint: neutron port-delete. (%s)" % (str(e))
529 return jsonify({'Err': error})
534 @app.route('/NetworkDriver.Join', methods=['POST'])
539 data = json.loads(request.data)
541 nid = data.get("NetworkID", "")
545 eid = data.get("EndpointID", "")
549 sboxkey = data.get("SandboxKey", "")
553 # sboxkey is of the form: /var/run/docker/netns/CONTAINER_ID
554 vm_id = sboxkey.rsplit('/')[-1]
557 neutron = neutron_login()
558 except Exception as e:
559 error = "network_join: neutron login. (%s)" % (str(e))
560 return jsonify({'Err': error})
562 subnet_name = "docker-%s" % (nid)
564 subnet = get_subnetuuid_by_name(neutron, subnet_name)
566 error = "network_join: can't find subnet in neutron"
567 return jsonify({'Err': error})
568 except Exception as e:
569 error = "network_join: subnet uuid by name. (%s)" % (str(e))
570 return jsonify({'Err': error})
573 ret = neutron.show_subnet(subnet)
574 gateway_ip = ret['subnet']['gateway_ip']
576 error = "network_join: no gateway_ip for the subnet"
577 return jsonify({'Err': error})
578 except Exception as e:
579 error = "network_join: neutron show subnet. (%s)" % (str(e))
580 return jsonify({'Err': error})
583 endpoint = get_endpointuuid_by_name(neutron, eid)
585 error = "network_join: Failed to get endpoint by name"
586 return jsonify({'Err': error})
587 except Exception as e:
588 error = "network_join: neutron endpoint by name. (%s)" % (str(e))
589 return jsonify({'Err': error})
592 ret = neutron.show_port(endpoint)
593 mac_address = ret['port']['mac_address']
594 except Exception as e:
595 error = "network_join: neutron show port. (%s)" % (str(e))
596 return jsonify({'Err': error})
598 veth_outside = eid[0:15]
599 veth_inside = eid[0:13] + "_c"
600 command = "ip link add %s type veth peer name %s" \
601 % (veth_inside, veth_outside)
603 call_popen(shlex.split(command))
604 except Exception as e:
605 error = "network_join: failed to create veth pair. (%s)" % (str(e))
606 return jsonify({'Err': error})
608 command = "ip link set dev %s address %s" \
609 % (veth_inside, mac_address)
612 call_popen(shlex.split(command))
613 except Exception as e:
614 error = "network_join: failed to set veth mac address. (%s)" % (str(e))
615 return jsonify({'Err': error})
617 command = "ip link set %s up" % (veth_outside)
620 call_popen(shlex.split(command))
621 except Exception as e:
622 error = "network_join: failed to up the veth iface. (%s)" % (str(e))
623 return jsonify({'Err': error})
626 reserved_vlan = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".",
627 "external_ids:" + eid + "_vlan").strip('"')
628 if not reserved_vlan:
629 error = "network_join: no reserved vlan for this endpoint"
630 return jsonify({'Err': error})
631 ovs_vsctl("add-port", OVN_BRIDGE, veth_outside, "tag=" + reserved_vlan)
632 except Exception as e:
633 error = "network_join: failed to create a OVS port. (%s)" % (str(e))
634 return jsonify({'Err': error})
636 return jsonify({"InterfaceName": {
637 "SrcName": veth_inside,
640 "Gateway": gateway_ip,
644 @app.route('/NetworkDriver.Leave', methods=['POST'])
649 data = json.loads(request.data)
651 nid = data.get("NetworkID", "")
655 eid = data.get("EndpointID", "")
659 veth_outside = eid[0:15]
660 command = "ip link delete %s" % (veth_outside)
662 call_popen(shlex.split(command))
663 except Exception as e:
664 error = "network_leave: failed to delete veth pair. (%s)" % (str(e))
665 return jsonify({'Err': error})
668 ovs_vsctl("--if-exists", "del-port", veth_outside)
669 except Exception as e:
670 error = "network_leave: Failed to delete port (%s)" % (str(e))
671 return jsonify({'Err': error})
675 if __name__ == '__main__':
677 app.run(host='127.0.0.1')