]> git.proxmox.com Git - mirror_ovs.git/blob - ovn/utilities/ovn-docker-underlay-driver
ovn-sbctl: Remove unused enum.
[mirror_ovs.git] / ovn / utilities / ovn-docker-underlay-driver
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
16 import argparse
17 import atexit
18 import getpass
19 import json
20 import os
21 import re
22 import shlex
23 import subprocess
24 import sys
25 import time
26 import uuid
27
28 import ovs.dirs
29 import ovs.util
30 import ovs.daemon
31 import ovs.unixctl.server
32 import ovs.vlog
33
34 from neutronclient.v2_0 import client
35 from flask import Flask, jsonify
36 from flask import request, abort
37
38 app = Flask(__name__)
39 vlog = ovs.vlog.Vlog("ovn-docker-underlay-driver")
40
41 AUTH_STRATEGY = ""
42 AUTH_URL = ""
43 ENDPOINT_URL = ""
44 OVN_BRIDGE = ""
45 PASSWORD = ""
46 PLUGIN_DIR = "/etc/docker/plugins"
47 PLUGIN_FILE = "/etc/docker/plugins/openvswitch.spec"
48 TENANT_ID = ""
49 USERNAME = ""
50 VIF_ID = ""
51
52
53 def call_popen(cmd):
54 child = subprocess.Popen(cmd, stdout=subprocess.PIPE)
55 output = child.communicate()
56 if child.returncode:
57 raise RuntimeError("Fatal error executing %s" % (cmd))
58 if len(output) == 0 or output[0] == None:
59 output = ""
60 else:
61 output = output[0].strip()
62 return output
63
64
65 def call_prog(prog, args_list):
66 cmd = [prog, "--timeout=5", "-vconsole:off"] + args_list
67 return call_popen(cmd)
68
69
70 def ovs_vsctl(*args):
71 return call_prog("ovs-vsctl", list(args))
72
73
74 def cleanup():
75 if os.path.isfile(PLUGIN_FILE):
76 os.remove(PLUGIN_FILE)
77
78
79 def ovn_init_underlay(args):
80 global USERNAME, PASSWORD, TENANT_ID, AUTH_URL, AUTH_STRATEGY, VIF_ID
81 global OVN_BRIDGE
82
83 if not args.bridge:
84 sys.exit("OVS bridge name not provided")
85 OVN_BRIDGE = args.bridge
86
87 VIF_ID = os.environ.get('OS_VIF_ID', '')
88 if not VIF_ID:
89 sys.exit("env OS_VIF_ID not set")
90 USERNAME = os.environ.get('OS_USERNAME', '')
91 if not USERNAME:
92 sys.exit("env OS_USERNAME not set")
93 TENANT_ID = os.environ.get('OS_TENANT_ID', '')
94 if not TENANT_ID:
95 sys.exit("env OS_TENANT_ID not set")
96 AUTH_URL = os.environ.get('OS_AUTH_URL', '')
97 if not AUTH_URL:
98 sys.exit("env OS_AUTH_URL not set")
99 AUTH_STRATEGY = "keystone"
100
101 PASSWORD = os.environ.get('OS_PASSWORD', '')
102 if not PASSWORD:
103 PASSWORD = getpass.getpass()
104
105
106 def prepare():
107 parser = argparse.ArgumentParser()
108 parser.add_argument('--bridge', help="The Bridge to which containers "
109 "interfaces connect to.")
110
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)
117
118 if not os.path.isdir(PLUGIN_DIR):
119 os.makedirs(PLUGIN_DIR)
120
121 ovs.daemon.daemonize()
122 try:
123 fo = open(PLUGIN_FILE, "w")
124 fo.write("tcp://127.0.0.1:5000")
125 fo.close()
126 except Exception as e:
127 ovs.util.ovs_fatal(0, "Failed to write to spec file (%s)" % str(e),
128 vlog)
129
130 atexit.register(cleanup)
131
132
133 @app.route('/Plugin.Activate', methods=['POST'])
134 def plugin_activate():
135 return jsonify({"Implements": ["NetworkDriver"]})
136
137
138 @app.route('/NetworkDriver.GetCapabilities', methods=['POST'])
139 def get_capability():
140 return jsonify({"Scope": "global"})
141
142
143 @app.route('/NetworkDriver.DiscoverNew', methods=['POST'])
144 def new_discovery():
145 return jsonify({})
146
147
148 @app.route('/NetworkDriver.DiscoverDelete', methods=['POST'])
149 def delete_discovery():
150 return jsonify({})
151
152
153 def neutron_login():
154 try:
155 neutron = client.Client(username=USERNAME,
156 password=PASSWORD,
157 tenant_id=TENANT_ID,
158 auth_url=AUTH_URL,
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))
163 return neutron
164
165
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:
172 network = None
173 else:
174 network = ret['networks'][0]['id']
175 return network
176
177
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:
184 subnet = None
185 else:
186 subnet = ret['subnets'][0]['id']
187 return subnet
188
189
190 @app.route('/NetworkDriver.CreateNetwork', methods=['POST'])
191 def create_network():
192 if not request.data:
193 abort(400)
194
195 data = json.loads(request.data)
196
197 # NetworkID will have docker generated network uuid and it
198 # becomes 'name' in a neutron network record.
199 network = data.get("NetworkID", "")
200 if not network:
201 abort(400)
202
203 # Limit subnet handling to ipv4 till ipv6 usecase is clear.
204 ipv4_data = data.get("IPv4Data", "")
205 if not ipv4_data:
206 error = "create_network: No ipv4 subnet provided"
207 return jsonify({'Err': error})
208
209 subnet = ipv4_data[0].get("Pool", "")
210 if not subnet:
211 error = "create_network: no subnet in ipv4 data from libnetwork"
212 return jsonify({'Err': error})
213
214 gateway_ip = ipv4_data[0].get("Gateway", "").rsplit('/', 1)[0]
215 if not gateway_ip:
216 error = "create_network: no gateway in ipv4 data from libnetwork"
217 return jsonify({'Err': error})
218
219 try:
220 neutron = neutron_login()
221 except Exception as e:
222 error = "create_network: neutron login. (%s)" % (str(e))
223 return jsonify({'Err': error})
224
225 try:
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})
232
233 try:
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})
240
241 subnet_name = "docker-%s" % (network)
242
243 try:
244 body = {'subnet': {'network_id': network_id,
245 'ip_version': 4,
246 'cidr': subnet,
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})
253
254 return jsonify({})
255
256
257 @app.route('/NetworkDriver.DeleteNetwork', methods=['POST'])
258 def delete_network():
259 if not request.data:
260 abort(400)
261
262 data = json.loads(request.data)
263
264 nid = data.get("NetworkID", "")
265 if not nid:
266 abort(400)
267
268 try:
269 neutron = neutron_login()
270 except Exception as e:
271 error = "delete_network: neutron login. (%s)" % (str(e))
272 return jsonify({'Err': error})
273
274 try:
275 network = get_networkuuid_by_name(neutron, nid)
276 if not network:
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})
282
283 try:
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})
288
289 return jsonify({})
290
291
292 def reserve_vlan():
293 reserved_vlan = 0
294 vlans = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".",
295 "external_ids:vlans").strip('"')
296 if not vlans:
297 reserved_vlan = 1
298 ovs_vsctl("set", "Open_vSwitch", ".",
299 "external_ids:vlans=" + str(reserved_vlan))
300 return reserved_vlan
301
302 vlan_set = str(vlans).split(',')
303
304 for vlan in range(1, 4095):
305 if str(vlan) not in vlan_set:
306 vlan_set.append(str(vlan))
307 reserved_vlan = vlan
308 vlans = re.sub(r'[ \[\]\']', '', str(vlan_set))
309 ovs_vsctl("set", "Open_vSwitch", ".",
310 "external_ids:vlans=" + vlans)
311 return reserved_vlan
312
313 if not reserved_vlan:
314 raise RuntimeError("No more vlans available on this host")
315
316
317 def unreserve_vlan(reserved_vlan):
318 vlans = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".",
319 "external_ids:vlans").strip('"')
320 if not vlans:
321 return
322
323 vlan_set = str(vlans).split(',')
324 if str(reserved_vlan) not in vlan_set:
325 return
326
327 vlan_set.remove(str(reserved_vlan))
328 vlans = re.sub(r'[ \[\]\']', '', str(vlan_set))
329 if vlans:
330 ovs_vsctl("set", "Open_vSwitch", ".", "external_ids:vlans=" + vlans)
331 else:
332 ovs_vsctl("remove", "Open_vSwitch", ".", "external_ids", "vlans")
333
334
335 def create_port_underlay(neutron, network, eid, ip_address, mac_address):
336 reserved_vlan = reserve_vlan()
337 if mac_address:
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}],
343 'name': eid,
344 'admin_state_up': True}}
345 else:
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}],
350 'name': eid,
351 'admin_state_up': True}}
352
353 try:
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))
359
360 ovs_vsctl("set", "Open_vSwitch", ".",
361 "external_ids:" + eid + "_vlan=" + str(reserved_vlan))
362
363 return mac_address
364
365
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:
372 endpoint = None
373 else:
374 endpoint = ret['ports'][0]['id']
375 return endpoint
376
377
378 @app.route('/NetworkDriver.CreateEndpoint', methods=['POST'])
379 def create_endpoint():
380 if not request.data:
381 abort(400)
382
383 data = json.loads(request.data)
384
385 nid = data.get("NetworkID", "")
386 if not nid:
387 abort(400)
388
389 eid = data.get("EndpointID", "")
390 if not eid:
391 abort(400)
392
393 interface = data.get("Interface", "")
394 if not interface:
395 error = "create_endpoint: no interfaces supplied by libnetwork"
396 return jsonify({'Err': error})
397
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})
402
403 ip_address = ip_address_and_mask.rsplit('/', 1)[0]
404 mac_address_input = interface.get("MacAddress", "")
405 mac_address_output = ""
406
407 try:
408 neutron = neutron_login()
409 except Exception as e:
410 error = "create_endpoint: neutron login. (%s)" % (str(e))
411 return jsonify({'Err': error})
412
413 try:
414 endpoint = get_endpointuuid_by_name(neutron, eid)
415 if endpoint:
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})
421
422 try:
423 network = get_networkuuid_by_name(neutron, nid)
424 if not network:
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})
430
431 try:
432 mac_address = create_port_underlay(neutron, network, eid, ip_address,
433 mac_address_input)
434 except Exception as e:
435 error = "create_endpoint: neutron port-create (%s)" % (str(e))
436 return jsonify({'Err': error})
437
438 if not mac_address_input:
439 mac_address_output = mac_address
440
441 return jsonify({"Interface": {
442 "Address": "",
443 "AddressIPv6": "",
444 "MacAddress": mac_address_output
445 }})
446
447
448 @app.route('/NetworkDriver.EndpointOperInfo', methods=['POST'])
449 def show_endpoint():
450 if not request.data:
451 abort(400)
452
453 data = json.loads(request.data)
454
455 nid = data.get("NetworkID", "")
456 if not nid:
457 abort(400)
458
459 eid = data.get("EndpointID", "")
460 if not eid:
461 abort(400)
462
463 try:
464 neutron = neutron_login()
465 except Exception as e:
466 error = "%s" % (str(e))
467 return jsonify({'Err': error})
468
469 try:
470 endpoint = get_endpointuuid_by_name(neutron, eid)
471 if not endpoint:
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})
477
478 try:
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})
485
486 veth_outside = eid[0:15]
487 return jsonify({"Value": {"ip_address": ip_address,
488 "mac_address": mac_address,
489 "veth_outside": veth_outside
490 }})
491
492
493 @app.route('/NetworkDriver.DeleteEndpoint', methods=['POST'])
494 def delete_endpoint():
495 if not request.data:
496 abort(400)
497
498 data = json.loads(request.data)
499
500 nid = data.get("NetworkID", "")
501 if not nid:
502 abort(400)
503
504 eid = data.get("EndpointID", "")
505 if not eid:
506 abort(400)
507
508 try:
509 neutron = neutron_login()
510 except Exception as e:
511 error = "delete_endpoint: neutron login (%s)" % (str(e))
512 return jsonify({'Err': error})
513
514 endpoint = get_endpointuuid_by_name(neutron, eid)
515 if not endpoint:
516 return jsonify({})
517
518 reserved_vlan = ovs_vsctl("--if-exists", "get", "Open_vSwitch", ".",
519 "external_ids:" + eid + "_vlan").strip('"')
520 if reserved_vlan:
521 unreserve_vlan(reserved_vlan)
522 ovs_vsctl("remove", "Open_vSwitch", ".", "external_ids",
523 eid + "_vlan")
524
525 try:
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})
530
531 return jsonify({})
532
533
534 @app.route('/NetworkDriver.Join', methods=['POST'])
535 def network_join():
536 if not request.data:
537 abort(400)
538
539 data = json.loads(request.data)
540
541 nid = data.get("NetworkID", "")
542 if not nid:
543 abort(400)
544
545 eid = data.get("EndpointID", "")
546 if not eid:
547 abort(400)
548
549 sboxkey = data.get("SandboxKey", "")
550 if not sboxkey:
551 abort(400)
552
553 # sboxkey is of the form: /var/run/docker/netns/CONTAINER_ID
554 vm_id = sboxkey.rsplit('/')[-1]
555
556 try:
557 neutron = neutron_login()
558 except Exception as e:
559 error = "network_join: neutron login. (%s)" % (str(e))
560 return jsonify({'Err': error})
561
562 subnet_name = "docker-%s" % (nid)
563 try:
564 subnet = get_subnetuuid_by_name(neutron, subnet_name)
565 if not subnet:
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})
571
572 try:
573 ret = neutron.show_subnet(subnet)
574 gateway_ip = ret['subnet']['gateway_ip']
575 if not 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})
581
582 try:
583 endpoint = get_endpointuuid_by_name(neutron, eid)
584 if not endpoint:
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})
590
591 try:
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})
597
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)
602 try:
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})
607
608 command = "ip link set dev %s address %s" \
609 % (veth_inside, mac_address)
610
611 try:
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})
616
617 command = "ip link set %s up" % (veth_outside)
618
619 try:
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})
624
625 try:
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})
635
636 return jsonify({"InterfaceName": {
637 "SrcName": veth_inside,
638 "DstPrefix": "eth"
639 },
640 "Gateway": gateway_ip,
641 "GatewayIPv6": ""})
642
643
644 @app.route('/NetworkDriver.Leave', methods=['POST'])
645 def network_leave():
646 if not request.data:
647 abort(400)
648
649 data = json.loads(request.data)
650
651 nid = data.get("NetworkID", "")
652 if not nid:
653 abort(400)
654
655 eid = data.get("EndpointID", "")
656 if not eid:
657 abort(400)
658
659 veth_outside = eid[0:15]
660 command = "ip link delete %s" % (veth_outside)
661 try:
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})
666
667 try:
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})
672
673 return jsonify({})
674
675 if __name__ == '__main__':
676 prepare()
677 app.run(host='127.0.0.1')