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')