Thanks Gurucharan, for this driver. I will try this pure overlay driver in my next iteration in Nov adding l3 without neutron. For docker 1.8 at this time I am leveraging neutron api for most network mgmt and some flask based for api and some shell at this time for a next week target (not openstack summit). The docker network commands look promising. Can't wait to try this out..
-Murali On Mon, Oct 19, 2015 at 2:52 PM, Gurucharan Shetty <shet...@nicira.com> wrote: > Docker removed 'experimental' tag for their multi-host > networking constructs last week and did a code freeze for > Docker 1.9. > > This commit adds two drivers for OVN integration > with Docker. The first driver is a pure overlay driver > that does not need OpenStack integration. The second driver > needs OVN+OpenStack. > > The description of the Docker API exists here: > https://github.com/docker/libnetwork/blob/master/docs/remote.md > > Signed-off-by: Gurucharan Shetty <gshe...@nicira.com> > --- > v1->v2: > Some style adustments with error messages. > Consolidation of some duplicate code to function: > get_logical_port_addresses > --- > INSTALL.Docker.md | 301 ++++++++++---- > ovn/utilities/automake.mk | 8 + > ovn/utilities/ovn-docker-overlay-driver | 442 ++++++++++++++++++++ > ovn/utilities/ovn-docker-underlay-driver | 675 > +++++++++++++++++++++++++++++++ > rhel/openvswitch-fedora.spec.in | 2 + > 5 files changed, 1358 insertions(+), 70 deletions(-) > create mode 100755 ovn/utilities/ovn-docker-overlay-driver > create mode 100755 ovn/utilities/ovn-docker-underlay-driver > > diff --git a/INSTALL.Docker.md b/INSTALL.Docker.md > index 9e14043..d523ecd 100644 > --- a/INSTALL.Docker.md > +++ b/INSTALL.Docker.md > @@ -1,109 +1,270 @@ > How to Use Open vSwitch with Docker > ==================================== > > -This document describes how to use Open vSwitch with Docker 1.2.0 or > +This document describes how to use Open vSwitch with Docker 1.9.0 or > later. This document assumes that you installed Open vSwitch by following > [INSTALL.md] or by using the distribution packages such as .deb or .rpm. > Consult www.docker.com for instructions on how to install Docker. > > -Limitations > ------------ > -Currently there is no native integration of Open vSwitch in Docker, i.e., > -one cannot use the Docker client to automatically add a container's > -network interface to an Open vSwitch bridge during the creation of the > -container. This document describes addition of new network interfaces to > an > -already created container and in turn attaching that interface as a port > to an > -Open vSwitch bridge. If and when there is a native integration of Open > vSwitch > -with Docker, the ovs-docker utility described in this document is > expected to > -be retired. > +Docker 1.9.0 comes with support for multi-host networking. Integration > +of Docker networking and Open vSwitch can be achieved via Open vSwitch > +virtual network (OVN). > + > > Setup > ------ > -* Create your container, e.g.: > +===== > + > +For multi-host networking with OVN and Docker, Docker has to be started > +with a destributed key-value store. For e.g., if you decide to use consul > +as your distributed key-value store, and your host IP address is $HOST_IP, > +start your Docker daemon with: > + > +``` > +docker daemon --cluster-store=consul://127.0.0.1:8500 > --cluster-advertise=$IP:0 > +``` > + > +OVN provides network virtualization to containers. OVN's integration with > +Docker currently works in two modes - the "underlay" mode or the "overlay" > +mode. > + > +In the "underlay" mode, OVN requires a OpenStack setup to provide > container > +networking. In this mode, one can create logical networks and can have > +containers running inside VMs, standalone VMs (without having any > containers > +running inside them) and physical machines connected to the same logical > +network. This is a multi-tenant, multi-host solution. > + > +In the "overlay" mode, OVN can create a logical network amongst containers > +running on multiple hosts. This is a single-tenant (extendable to > +multi-tenants depending on the security characteristics of the workloads), > +multi-host solution. In this mode, you do not need a pre-created > OpenStack > +setup. > + > +For both the modes to work, a user has to install and start Open vSwitch > in > +each VM/host that he plans to run his containers. > + > + > +The "overlay" mode > +================== > + > +OVN in "overlay" mode needs a minimum Open vSwitch version of 2.5. > + > +* Start the central components. > + > +OVN architecture has a central component which stores your networking > intent > +in a database. So on any machine, with an IP Address of $CENTRAL_IP, > where you > +have installed and started Open vSwitch, you will need to start some > +central components. > + > +Begin by making ovsdb-server listen on a TCP port by running: > + > +``` > +ovs-appctl -t ovsdb-server ovsdb-server/add-remote ptcp:6640 > +``` > + > +Start ovn_northd daemon. This daemon translates networking intent from > Docker > +stored in OVN_Northbound databse to logical flows in OVN_Southbound > database. > + > +``` > +/usr/share/openvswitch/scripts/ovn-ctl start_northd > +``` > + > +* One time setup. > + > +On each host, where you plan to spawn your containers, you will need to > +run the following commands once. > + > +$LOCAL_IP in the below command is the IP address via which other hosts > +can reach this host. This acts as your local tunnel endpoint. > + > +$ENCAP_TYPE is the type of tunnel that you would like to use for overlay > +networking. The options are "geneve" or "stt". > + > +``` > +ovs-vsctl set Open_vSwitch . > external_ids:ovn-remote="tcp:$CENTRAL_IP:6640 \ > + external_ids:ovn-encap-ip=$LOCAL_IP > external_ids:ovn-encap-type="geneve" > +``` > + > +And finally, start the ovn-controller. > + > +``` > +/usr/share/openvswitch/scripts/ovn-ctl start_controller > +``` > + > +* Start the Open vSwitch network driver. > + > +By default Docker uses Linux bridge for networking. But it has support > +for external drivers. To use Open vSwitch instead of the Linux bridge, > +you will need to start the Open vSwitch driver. > + > +The Open vSwitch driver uses the Python's flask module to listen to > +Docker's networking api calls. So, if your host does not have Python's > +flask module, install it with: > + > +``` > +easy_install -U pip > +pip install Flask > +``` > + > +Start the Open vSwitch driver on every host where you plan to create your > +containers. > + > +``` > +ovn-docker-overlay-driver --overlay-mode --detach > +``` > + > +Docker has inbuilt primitives that closely match OVN's logical switches > +and logical port concepts. Please consult Docker's documentation for > +all the possible commands. Here are some examples. > + > +* Create your logical switch. > + > +To create a logical switch with name 'foo', on subnet '192.168.1.0/24' > run: > + > +``` > +NID=`docker network create -d openvswitch --subnet=192.168.1.0/24 foo` > +``` > + > +* List your logical switches. > + > +``` > +docker network ls > +``` > + > +You can also look at this logical switch in OVN's northbound database by > +running the following command. > + > +``` > +ovn-nbctl --db=tcp:$CENTRAL_IP:6640 lswitch-list > +``` > + > +* Docker creates your logical port and attaches it to the logical network > +in a single step. > + > +For e.g., to attach a logical port to network 'foo' inside cotainer > busybox, > +run: > + > +``` > +docker run -itd --net=foo --name=busybox busybox > +``` > + > +* List all your logical ports. > + > +Docker currently does not have a CLI command to list all your logical > ports. > +But you can look at them in the OVN database, by running: > > ``` > -% docker run -d ubuntu:14.04 /bin/sh -c \ > -"while true; do echo hello world; sleep 1; done" > +ovn-nbctl --db=tcp:$CENTRAL_IP:6640 lport-list $NID > ``` > > -The above command creates a container with one network interface 'eth0' > -and attaches it to a Linux bridge called 'docker0'. 'eth0' by default > -gets an IP address in the 172.17.0.0/16 space. Docker sets up iptables > -NAT rules to let this interface talk to the outside world. Also since > -it is connected to 'docker0' bridge, it can talk to all other containers > -connected to the same bridge. If you prefer that no network interface be > -created by default, you can start your container with > -the option '--net=none', e,g.: > +* You can also create a logical port and attach it to a running container. > > ``` > -% docker run -d --net=none ubuntu:14.04 /bin/sh -c \ > -"while true; do echo hello world; sleep 1; done" > +docker network create -d openvswitch --subnet=192.168.2.0/24 bar > +docker network connect bar busybox > ``` > > -The above commands will return a container id. You will need to pass this > -value to the utility 'ovs-docker' to create network interfaces attached > to an > -Open vSwitch bridge as a port. This document will reference this value > -as $CONTAINER_ID in the next steps. > +You can delete your logical port and detach it from a running container by > +running: > + > +``` > +docker network disconnect bar busybox > +``` > > -* Add a new network interface to the container and attach it to an Open > vSwitch > - bridge. e.g.: > +* You can delete your logical switch by running: > > -`% ovs-docker add-port br-int eth1 $CONTAINER_ID` > +``` > +docker network rm bar > +``` > > -The above command will create a network interface 'eth1' inside the > container > -and then attaches it to the Open vSwitch bridge 'br-int'. This is done by > -creating a veth pair. One end of the interface becomes 'eth1' inside the > -container and the other end attaches to 'br-int'. > > -The script also lets one to add IP address, MAC address, Gateway address > and > -MTU for the interface. e.g.: > +The "underlay" mode > +=================== > + > +This mode requires that you have a OpenStack setup pre-installed with OVN > +providing the underlay networking. > + > +* One time setup. > + > +A OpenStack tenant creates a VM with a single network interface (or > multiple) > +that belongs to management logical networks. The tenant needs to fetch > the > +port-id associated with the interface via which he plans to send the > container > +traffic inside the spawned VM. This can be obtained by running the > +below command to fetch the 'id' associated with the VM. > > ``` > -% ovs-docker add-port br-int eth1 $CONTAINER_ID --ipaddress= > 192.168.1.2/24 \ > ---macaddress=a2:c3:0d:49:7f:f8 --gateway=192.168.1.1 --mtu=1450 > +nova list > ``` > > -* A previously added network interface can be deleted. e.g.: > +and then by running: > > -`% ovs-docker del-port br-int eth1 $CONTAINER_ID` > +``` > +neutron port-list --device_id=$id > +``` > > -All the previously added Open vSwitch interfaces inside a container can be > -deleted. e.g.: > +Inside the VM, download the OpenStack RC file that contains the tenant > +information (henceforth referred to as 'openrc.sh'). Edit the file and > add the > +previously obtained port-id information to the file by appending the > following > +line: export OS_VIF_ID=$port_id. After this edit, the file will look > something > +like: > > -`% ovs-docker del-ports br-int $CONTAINER_ID` > +``` > +#!/bin/bash > +export OS_AUTH_URL=http://10.33.75.122:5000/v2.0 > +export OS_TENANT_ID=fab106b215d943c3bad519492278443d > +export OS_TENANT_NAME="demo" > +export OS_USERNAME="demo" > +export OS_VIF_ID=e798c371-85f4-4f2d-ad65-d09dd1d3c1c9 > +``` > + > +* Create the Open vSwitch bridge. > + > +If your VM has one ethernet interface (e.g.: 'eth0'), you will need to add > +that device as a port to an Open vSwitch bridge 'breth0' and move its IP > +address and route related information to that bridge. (If it has multiple > +network interfaces, you will need to create and attach an Open vSwitch > bridge > +for the interface via which you plan to send your container traffic.) > + > +If you use DHCP to obtain an IP address, then you should kill the DHCP > client > +that was listening on the physical Ethernet interface (e.g. eth0) and > start > +one listening on the Open vSwitch bridge (e.g. breth0). > > -It is important that the same $CONTAINER_ID be passed to both add-port > -and del-port[s] commands. > +Depending on your VM, you can make the above step persistent across > reboots. > +For e.g.:, if your VM is Debian/Ubuntu, you can read > +[openvswitch-switch.README.Debian] > +If your VM is RHEL based, you can read [README.RHEL] > > -* More network control. > > -Once a container interface is added to an Open vSwitch bridge, one can > -set VLANs, create Tunnels, add OpenFlow rules etc for more network > control. > -Many times, it is important that the underlying network infrastructure is > -plumbed (or programmed) before the application inside the container > starts. > -To handle this, one can create a micro-container, attach an Open vSwitch > -interface to that container, set the UUIDS in OVSDB as mentioned in > -[IntegrationGuide.md] and then program the bridge to handle traffic > coming out > -of that container. Now, you can start the main container asking it > -to share the network of the micro-container. When your application starts, > -the underlying network infrastructure would be ready. e.g.: > +* Start the Open vSwitch network driver. > > +The Open vSwitch driver uses the Python's flask module to listen to > +Docker's networking api calls. The driver also uses OpenStack's > +python-neutronclient libraries. So, if your host does not have Python's > +flask module or python-neutronclient install them with: > + > +``` > +easy_install -U pip > +pip install python-neutronclient > +pip install Flask > ``` > -% docker run -d --net=container:$MICROCONTAINER_ID ubuntu:14.04 /bin/sh > -c \ > -"while true; do echo hello world; sleep 1; done" > + > +Source the openrc file. e.g.: > +```` > +source openrc.sh > ``` > > -Please read the man pages of ovs-vsctl, ovs-ofctl, ovs-vswitchd, > -ovsdb-server and ovs-vswitchd.conf.db etc for more details about Open > vSwitch. > +Start the network driver and provide your OpenStack tenant password > +when prompted. > > -Docker networking is quite flexible and can be used in multiple ways. > For more > -information, please read: > -https://docs.docker.com/articles/networking > +``` > +ovn-docker-underlay-driver --bridge breth0 --detach > +``` > > -Bug Reporting > -------------- > +From here-on you can use the same Docker commands as described in the > +section 'The "overlay" mode'. > > -Please report problems to b...@openvswitch.org. > +Please read 'man ovn-architecture' to understand OVN's architecture in > +detail. > > -[INSTALL.md]:INSTALL.md > -[IntegrationGuide.md]:IntegrationGuide.md > +[INSTALL.md]: INSTALL.md > +[openvswitch-switch.README.Debian]: > debian/openvswitch-switch.README.Debian > +[README.RHEL]: rhel/README.RHEL > diff --git a/ovn/utilities/automake.mk b/ovn/utilities/automake.mk > index b247a54..50fb4e7 100644 > --- a/ovn/utilities/automake.mk > +++ b/ovn/utilities/automake.mk > @@ -8,9 +8,16 @@ man_MANS += \ > > MAN_ROOTS += ovn/utilities/ovn-sbctl.8.in > > +# Docker drivers > +bin_SCRIPTS += \ > + ovn/utilities/ovn-docker-overlay-driver \ > + ovn/utilities/ovn-docker-underlay-driver > + > EXTRA_DIST += \ > ovn/utilities/ovn-ctl \ > ovn/utilities/ovn-ctl.8.xml \ > + ovn/utilities/ovn-docker-overlay-driver \ > + ovn/utilities/ovn-docker-underlay-driver \ > ovn/utilities/ovn-nbctl.8.xml > > DISTCLEANFILES += \ > @@ -27,3 +34,4 @@ ovn_utilities_ovn_nbctl_LDADD = ovn/lib/libovn.la ovsdb/ > libovsdb.la lib/libopenv > bin_PROGRAMS += ovn/utilities/ovn-sbctl > ovn_utilities_ovn_sbctl_SOURCES = ovn/utilities/ovn-sbctl.c > ovn_utilities_ovn_sbctl_LDADD = ovn/lib/libovn.la ovsdb/libovsdb.la lib/ > libopenvswitch.la > + > diff --git a/ovn/utilities/ovn-docker-overlay-driver > b/ovn/utilities/ovn-docker-overlay-driver > new file mode 100755 > index 0000000..71eac93 > --- /dev/null > +++ b/ovn/utilities/ovn-docker-overlay-driver > @@ -0,0 +1,442 @@ > +#! /usr/bin/python > +# Copyright (C) 2015 Nicira, Inc. > +# > +# Licensed under the Apache License, Version 2.0 (the "License"); > +# you may not use this file except in compliance with the License. > +# You may obtain a copy of the License at: > +# > +# http://www.apache.org/licenses/LICENSE-2.0 > +# > +# Unless required by applicable law or agreed to in writing, software > +# distributed under the License is distributed on an "AS IS" BASIS, > +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > +# See the License for the specific language governing permissions and > +# limitations under the License. > + > +import argparse > +import ast > +import atexit > +import json > +import os > +import random > +import re > +import shlex > +import subprocess > +import sys > + > +import ovs.dirs > +import ovs.util > +import ovs.daemon > +import ovs.vlog > + > +from flask import Flask, jsonify > +from flask import request, abort > + > +app = Flask(__name__) > +vlog = ovs.vlog.Vlog("ovn-docker-overlay-driver") > + > +OVN_BRIDGE = "br-int" > +OVN_REMOTE = "" > +PLUGIN_DIR = "/etc/docker/plugins" > +PLUGIN_FILE = "/etc/docker/plugins/openvswitch.spec" > + > + > +def call_popen(cmd): > + child = subprocess.Popen(cmd, stdout=subprocess.PIPE) > + output = child.communicate() > + if child.returncode: > + raise RuntimeError("Fatal error executing %s" % (cmd)) > + if len(output) == 0 or output[0] == None: > + output = "" > + else: > + output = output[0].strip() > + return output > + > + > +def call_prog(prog, args_list): > + cmd = [prog, "--timeout=5", "-vconsole:off"] + args_list > + return call_popen(cmd) > + > + > +def ovs_vsctl(args): > + return call_prog("ovs-vsctl", shlex.split(args)) > + > + > +def ovn_nbctl(args): > + args_list = shlex.split(args) > + database_option = "%s=%s" % ("--db", OVN_REMOTE) > + args_list.insert(0, database_option) > + return call_prog("ovn-nbctl", args_list) > + > + > +def cleanup(): > + if os.path.isfile(PLUGIN_FILE): > + os.remove(PLUGIN_FILE) > + > + > +def ovn_init_overlay(): > + br_list = ovs_vsctl("list-br").split() > + if OVN_BRIDGE not in br_list: > + ovs_vsctl("-- --may-exist add-br %s " > + "-- br-set-external-id %s bridge-id %s " > + "-- set bridge %s other-config:disable-in-band=true " > + "-- set bridge %s fail-mode=secure" > + % (OVN_BRIDGE, OVN_BRIDGE, OVN_BRIDGE, OVN_BRIDGE, > + OVN_BRIDGE)) > + > + global OVN_REMOTE > + OVN_REMOTE = ovs_vsctl("get Open_vSwitch . " > + "external_ids:ovn-remote").strip('"') > + if not OVN_REMOTE: > + sys.exit("OVN central database's ip address not set") > + > + ovs_vsctl("set open_vswitch . external_ids:ovn-bridge=%s " > + % OVN_BRIDGE) > + > + > +def prepare(): > + parser = argparse.ArgumentParser() > + > + ovs.vlog.add_args(parser) > + ovs.daemon.add_args(parser) > + args = parser.parse_args() > + ovs.vlog.handle_args(args) > + ovs.daemon.handle_args(args) > + ovn_init_overlay() > + > + if not os.path.isdir(PLUGIN_DIR): > + os.makedirs(PLUGIN_DIR) > + > + ovs.daemon.daemonize() > + try: > + fo = open(PLUGIN_FILE, "w") > + fo.write("tcp://0.0.0.0:5000") > + fo.close() > + except Exception as e: > + ovs.util.ovs_fatal(0, "Failed to write to spec file (%s)" % > str(e), > + vlog) > + > + atexit.register(cleanup) > + > + > +@app.route('/Plugin.Activate', methods=['POST']) > +def plugin_activate(): > + return jsonify({"Implements": ["NetworkDriver"]}) > + > + > +@app.route('/NetworkDriver.GetCapabilities', methods=['POST']) > +def get_capability(): > + return jsonify({"Scope": "global"}) > + > + > +@app.route('/NetworkDriver.DiscoverNew', methods=['POST']) > +def new_discovery(): > + return jsonify({}) > + > + > +@app.route('/NetworkDriver.DiscoverDelete', methods=['POST']) > +def delete_discovery(): > + return jsonify({}) > + > + > +@app.route('/NetworkDriver.CreateNetwork', methods=['POST']) > +def create_network(): > + if not request.data: > + abort(400) > + > + data = json.loads(request.data) > + > + # NetworkID will have docker generated network uuid and it > + # becomes 'name' in a OVN Logical switch record. > + network = data.get("NetworkID", "") > + if not network: > + abort(400) > + > + # Limit subnet handling to ipv4 till ipv6 usecase is clear. > + ipv4_data = data.get("IPv4Data", "") > + if not ipv4_data: > + error = "create_network: No ipv4 subnet provided" > + return jsonify({'Err': error}) > + > + subnet = ipv4_data[0].get("Pool", "") > + if not subnet: > + error = "create_network: no subnet in ipv4 data from libnetwork" > + return jsonify({'Err': error}) > + > + gateway_ip = ipv4_data[0].get("Gateway", "").rsplit('/', 1)[0] > + if not gateway_ip: > + error = "create_network: no gateway in ipv4 data from libnetwork" > + return jsonify({'Err': error}) > + > + try: > + ovn_nbctl("lswitch-add %s -- set Logical_Switch %s " > + "external_ids:subnet=%s external_ids:gateway_ip=%s" > + % (network, network, subnet, gateway_ip)) > + except Exception as e: > + error = "create_network: lswitch-add %s" % (str(e)) > + return jsonify({'Err': error}) > + > + return jsonify({}) > + > + > +@app.route('/NetworkDriver.DeleteNetwork', methods=['POST']) > +def delete_network(): > + if not request.data: > + abort(400) > + > + data = json.loads(request.data) > + > + nid = data.get("NetworkID", "") > + if not nid: > + abort(400) > + > + try: > + ovn_nbctl("lswitch-del %s" % (nid)) > + except Exception as e: > + error = "delete_network: lswitch-del %s" % (str(e)) > + return jsonify({'Err': error}) > + > + return jsonify({}) > + > + > +@app.route('/NetworkDriver.CreateEndpoint', methods=['POST']) > +def create_endpoint(): > + if not request.data: > + abort(400) > + > + data = json.loads(request.data) > + > + nid = data.get("NetworkID", "") > + if not nid: > + abort(400) > + > + eid = data.get("EndpointID", "") > + if not eid: > + abort(400) > + > + interface = data.get("Interface", "") > + if not interface: > + error = "create_endpoint: no interfaces structure supplied by " \ > + "libnetwork" > + return jsonify({'Err': error}) > + > + ip_address_and_mask = interface.get("Address", "") > + if not ip_address_and_mask: > + error = "create_endpoint: ip address not provided by libnetwork" > + return jsonify({'Err': error}) > + > + ip_address = ip_address_and_mask.rsplit('/', 1)[0] > + mac_address_input = interface.get("MacAddress", "") > + mac_address_output = "" > + > + try: > + ovn_nbctl("lport-add %s %s" % (nid, eid)) > + except Exception as e: > + error = "create_endpoint: lport-add (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + if not mac_address_input: > + mac_address = "02:%02x:%02x:%02x:%02x:%02x" % (random.randint(0, > 255), > + random.randint(0, > 255), > + random.randint(0, > 255), > + random.randint(0, > 255), > + random.randint(0, > 255)) > + else: > + mac_address = mac_address_input > + > + try: > + ovn_nbctl("lport-set-addresses %s \"%s %s\"" > + % (eid, mac_address, ip_address)) > + except Exception as e: > + error = "create_endpoint: lport-set-addresses (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + # Only return a mac address if one did not come as request. > + mac_address_output = "" > + if not mac_address_input: > + mac_address_output = mac_address > + > + return jsonify({"Interface": { > + "Address": "", > + "AddressIPv6": "", > + "MacAddress": mac_address_output > + }}) > + > + > +def get_logical_port_addresses(eid): > + ret = ovn_nbctl("--if-exists get Logical_port %s addresses" % (eid)) > + if not ret: > + error = "endpoint not found in OVN database" > + return (None, None, error) > + addresses = ast.literal_eval(ret) > + if len(addresses) == 0: > + error = "unexpected return while fetching addresses" > + return (None, None, error) > + (mac_address, ip_address) = addresses[0].split() > + return (mac_address, ip_address, None) > + > + > +@app.route('/NetworkDriver.EndpointOperInfo', methods=['POST']) > +def show_endpoint(): > + if not request.data: > + abort(400) > + > + data = json.loads(request.data) > + > + nid = data.get("NetworkID", "") > + if not nid: > + abort(400) > + > + eid = data.get("EndpointID", "") > + if not eid: > + abort(400) > + > + try: > + (mac_address, ip_address, error) = get_logical_port_addresses(eid) > + if error: > + jsonify({'Err': error}) > + except Exception as e: > + error = "show_endpoint: get Logical_port addresses. (%s)" % > (str(e)) > + return jsonify({'Err': error}) > + > + veth_outside = eid[0:15] > + return jsonify({"Value": {"ip_address": ip_address, > + "mac_address": mac_address, > + "veth_outside": veth_outside > + }}) > + > + > +@app.route('/NetworkDriver.DeleteEndpoint', methods=['POST']) > +def delete_endpoint(): > + if not request.data: > + abort(400) > + > + data = json.loads(request.data) > + > + nid = data.get("NetworkID", "") > + if not nid: > + abort(400) > + > + eid = data.get("EndpointID", "") > + if not eid: > + abort(400) > + > + try: > + ovn_nbctl("lport-del %s" % eid) > + except Exception as e: > + error = "delete_endpoint: lport-del %s" % (str(e)) > + return jsonify({'Err': error}) > + > + return jsonify({}) > + > + > +@app.route('/NetworkDriver.Join', methods=['POST']) > +def network_join(): > + if not request.data: > + abort(400) > + > + data = json.loads(request.data) > + > + nid = data.get("NetworkID", "") > + if not nid: > + abort(400) > + > + eid = data.get("EndpointID", "") > + if not eid: > + abort(400) > + > + sboxkey = data.get("SandboxKey", "") > + if not sboxkey: > + abort(400) > + > + # sboxkey is of the form: /var/run/docker/netns/CONTAINER_ID > + vm_id = sboxkey.rsplit('/')[-1] > + > + try: > + (mac_address, ip_address, error) = get_logical_port_addresses(eid) > + if error: > + jsonify({'Err': error}) > + except Exception as e: > + error = "network_join: %s" % (str(e)) > + return jsonify({'Err': error}) > + > + veth_outside = eid[0:15] > + veth_inside = eid[0:13] + "_c" > + command = "ip link add %s type veth peer name %s" \ > + % (veth_inside, veth_outside) > + try: > + call_popen(shlex.split(command)) > + except Exception as e: > + error = "network_join: failed to create veth pair (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + command = "ip link set dev %s address %s" \ > + % (veth_inside, mac_address) > + > + try: > + call_popen(shlex.split(command)) > + except Exception as e: > + error = "network_join: failed to set veth mac address (%s)" % > (str(e)) > + return jsonify({'Err': error}) > + > + command = "ip link set %s up" % (veth_outside) > + > + try: > + call_popen(shlex.split(command)) > + except Exception as e: > + error = "network_join: failed to up the veth interface (%s)" % > (str(e)) > + return jsonify({'Err': error}) > + > + try: > + ovs_vsctl("add-port %s %s" % (OVN_BRIDGE, veth_outside)) > + ovs_vsctl("set interface %s external_ids:attached-mac=%s " > + "external_ids:iface-id=%s " > + "external_ids:vm-id=%s " > + "external_ids:iface-status=%s " > + % (veth_outside, mac_address, eid, vm_id, "active")) > + except Exception as e: > + error = "network_join: failed to create a port (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + return jsonify({"InterfaceName": { > + "SrcName": veth_inside, > + "DstPrefix": "eth" > + }, > + "Gateway": "", > + "GatewayIPv6": ""}) > + > + > +@app.route('/NetworkDriver.Leave', methods=['POST']) > +def network_leave(): > + if not request.data: > + abort(400) > + > + data = json.loads(request.data) > + > + nid = data.get("NetworkID", "") > + if not nid: > + abort(400) > + > + eid = data.get("EndpointID", "") > + if not eid: > + abort(400) > + > + veth_outside = eid[0:15] > + command = "ip link delete %s" % (veth_outside) > + try: > + call_popen(shlex.split(command)) > + except Exception as e: > + error = "network_leave: failed to delete veth pair (%s)" % > (str(e)) > + return jsonify({'Err': error}) > + > + try: > + ovs_vsctl("--if-exists del-port %s" % (veth_outside)) > + except Exception as e: > + error = "network_leave: failed to delete port (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + return jsonify({}) > + > +if __name__ == '__main__': > + prepare() > + app.run(host='0.0.0.0') > diff --git a/ovn/utilities/ovn-docker-underlay-driver > b/ovn/utilities/ovn-docker-underlay-driver > new file mode 100755 > index 0000000..46364da > --- /dev/null > +++ b/ovn/utilities/ovn-docker-underlay-driver > @@ -0,0 +1,675 @@ > +#! /usr/bin/python > +# Copyright (C) 2015 Nicira, Inc. > +# > +# Licensed under the Apache License, Version 2.0 (the "License"); > +# you may not use this file except in compliance with the License. > +# You may obtain a copy of the License at: > +# > +# http://www.apache.org/licenses/LICENSE-2.0 > +# > +# Unless required by applicable law or agreed to in writing, software > +# distributed under the License is distributed on an "AS IS" BASIS, > +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > +# See the License for the specific language governing permissions and > +# limitations under the License. > + > +import argparse > +import atexit > +import getpass > +import json > +import os > +import re > +import shlex > +import subprocess > +import sys > +import time > +import uuid > + > +import ovs.dirs > +import ovs.util > +import ovs.daemon > +import ovs.unixctl.server > +import ovs.vlog > + > +from neutronclient.v2_0 import client > +from flask import Flask, jsonify > +from flask import request, abort > + > +app = Flask(__name__) > +vlog = ovs.vlog.Vlog("ovn-docker-underlay-driver") > + > +AUTH_STRATEGY = "" > +AUTH_URL = "" > +ENDPOINT_URL = "" > +OVN_BRIDGE = "" > +PASSWORD = "" > +PLUGIN_DIR = "/etc/docker/plugins" > +PLUGIN_FILE = "/etc/docker/plugins/openvswitch.spec" > +TENANT_ID = "" > +USERNAME = "" > +VIF_ID = "" > + > + > +def call_popen(cmd): > + child = subprocess.Popen(cmd, stdout=subprocess.PIPE) > + output = child.communicate() > + if child.returncode: > + raise RuntimeError("Fatal error executing %s" % (cmd)) > + if len(output) == 0 or output[0] == None: > + output = "" > + else: > + output = output[0].strip() > + return output > + > + > +def call_prog(prog, args_list): > + cmd = [prog, "--timeout=5", "-vconsole:off"] + args_list > + return call_popen(cmd) > + > + > +def ovs_vsctl(args): > + return call_prog("ovs-vsctl", shlex.split(args)) > + > + > +def cleanup(): > + if os.path.isfile(PLUGIN_FILE): > + os.remove(PLUGIN_FILE) > + > + > +def ovn_init_underlay(args): > + global USERNAME, PASSWORD, TENANT_ID, AUTH_URL, AUTH_STRATEGY, VIF_ID > + global OVN_BRIDGE > + > + if not args.bridge: > + sys.exit("OVS bridge name not provided") > + OVN_BRIDGE = args.bridge > + > + VIF_ID = os.environ.get('OS_VIF_ID', '') > + if not VIF_ID: > + sys.exit("env OS_VIF_ID not set") > + USERNAME = os.environ.get('OS_USERNAME', '') > + if not USERNAME: > + sys.exit("env OS_USERNAME not set") > + TENANT_ID = os.environ.get('OS_TENANT_ID', '') > + if not TENANT_ID: > + sys.exit("env OS_TENANT_ID not set") > + AUTH_URL = os.environ.get('OS_AUTH_URL', '') > + if not AUTH_URL: > + sys.exit("env OS_AUTH_URL not set") > + AUTH_STRATEGY = "keystone" > + > + PASSWORD = os.environ.get('OS_PASSWORD', '') > + if not PASSWORD: > + PASSWORD = getpass.getpass() > + > + > +def prepare(): > + parser = argparse.ArgumentParser() > + parser.add_argument('--bridge', help="The Bridge to which containers " > + "interfaces connect to.") > + > + ovs.vlog.add_args(parser) > + ovs.daemon.add_args(parser) > + args = parser.parse_args() > + ovs.vlog.handle_args(args) > + ovs.daemon.handle_args(args) > + ovn_init_underlay(args) > + > + if not os.path.isdir(PLUGIN_DIR): > + os.makedirs(PLUGIN_DIR) > + > + ovs.daemon.daemonize() > + try: > + fo = open(PLUGIN_FILE, "w") > + fo.write("tcp://0.0.0.0:5000") > + fo.close() > + except Exception as e: > + ovs.util.ovs_fatal(0, "Failed to write to spec file (%s)" % > str(e), > + vlog) > + > + atexit.register(cleanup) > + > + > +@app.route('/Plugin.Activate', methods=['POST']) > +def plugin_activate(): > + return jsonify({"Implements": ["NetworkDriver"]}) > + > + > +@app.route('/NetworkDriver.GetCapabilities', methods=['POST']) > +def get_capability(): > + return jsonify({"Scope": "global"}) > + > + > +@app.route('/NetworkDriver.DiscoverNew', methods=['POST']) > +def new_discovery(): > + return jsonify({}) > + > + > +@app.route('/NetworkDriver.DiscoverDelete', methods=['POST']) > +def delete_discovery(): > + return jsonify({}) > + > + > +def neutron_login(): > + try: > + neutron = client.Client(username=USERNAME, > + password=PASSWORD, > + tenant_id=TENANT_ID, > + auth_url=AUTH_URL, > + endpoint_url=ENDPOINT_URL, > + auth_strategy=AUTH_STRATEGY) > + except Exception as e: > + raise RuntimeError("Failed to login into Neutron(%s)" % str(e)) > + return neutron > + > + > +def get_networkuuid_by_name(neutron, name): > + param = {'fields': 'id', 'name': name} > + ret = neutron.list_networks(**param) > + if len(ret['networks']) > 1: > + raise RuntimeError("More than one network for the given name") > + elif len(ret['networks']) == 0: > + network = None > + else: > + network = ret['networks'][0]['id'] > + return network > + > + > +def get_subnetuuid_by_name(neutron, name): > + param = {'fields': 'id', 'name': name} > + ret = neutron.list_subnets(**param) > + if len(ret['subnets']) > 1: > + raise RuntimeError("More than one subnet for the given name") > + elif len(ret['subnets']) == 0: > + subnet = None > + else: > + subnet = ret['subnets'][0]['id'] > + return subnet > + > + > +@app.route('/NetworkDriver.CreateNetwork', methods=['POST']) > +def create_network(): > + if not request.data: > + abort(400) > + > + data = json.loads(request.data) > + > + # NetworkID will have docker generated network uuid and it > + # becomes 'name' in a neutron network record. > + network = data.get("NetworkID", "") > + if not network: > + abort(400) > + > + # Limit subnet handling to ipv4 till ipv6 usecase is clear. > + ipv4_data = data.get("IPv4Data", "") > + if not ipv4_data: > + error = "create_network: No ipv4 subnet provided" > + return jsonify({'Err': error}) > + > + subnet = ipv4_data[0].get("Pool", "") > + if not subnet: > + error = "create_network: no subnet in ipv4 data from libnetwork" > + return jsonify({'Err': error}) > + > + gateway_ip = ipv4_data[0].get("Gateway", "").rsplit('/', 1)[0] > + if not gateway_ip: > + error = "create_network: no gateway in ipv4 data from libnetwork" > + return jsonify({'Err': error}) > + > + try: > + neutron = neutron_login() > + except Exception as e: > + error = "create_network: neutron login. (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + try: > + if get_networkuuid_by_name(neutron, network): > + error = "create_network: network has already been created" > + return jsonify({'Err': error}) > + except Exception as e: > + error = "create_network: neutron network uuid by name. (%s)" % > (str(e)) > + return jsonify({'Err': error}) > + > + try: > + body = {'network': {'name': network, 'admin_state_up': True}} > + ret = neutron.create_network(body) > + network_id = ret['network']['id'] > + except Exception as e: > + error = "create_network: neutron net-create call. (%s)" % str(e) > + return jsonify({'Err': error}) > + > + subnet_name = "docker-%s" % (network) > + > + try: > + body = {'subnet': {'network_id': network_id, > + 'ip_version': 4, > + 'cidr': subnet, > + 'gateway_ip': gateway_ip, > + 'name': subnet_name}} > + created_subnet = neutron.create_subnet(body) > + except Exception as e: > + error = "create_network: neutron subnet-create call. (%s)" % > str(e) > + return jsonify({'Err': error}) > + > + return jsonify({}) > + > + > +@app.route('/NetworkDriver.DeleteNetwork', methods=['POST']) > +def delete_network(): > + if not request.data: > + abort(400) > + > + data = json.loads(request.data) > + > + nid = data.get("NetworkID", "") > + if not nid: > + abort(400) > + > + try: > + neutron = neutron_login() > + except Exception as e: > + error = "delete_network: neutron login. (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + try: > + network = get_networkuuid_by_name(neutron, nid) > + if not network: > + error = "delete_network: failed in network by name. (%s)" % > (nid) > + return jsonify({'Err': error}) > + except Exception as e: > + error = "delete_network: network uuid by name. (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + try: > + neutron.delete_network(network) > + except Exception as e: > + error = "delete_network: neutron net-delete. (%s)" % str(e) > + return jsonify({'Err': error}) > + > + return jsonify({}) > + > + > +def reserve_vlan(): > + reserved_vlan = 0 > + vlans = ovs_vsctl("--if-exists get Open_vSwitch . " > + "external_ids:vlans").strip('"') > + if not vlans: > + reserved_vlan = 1 > + ovs_vsctl("set Open_vSwitch . external_ids:vlans=%s" % > reserved_vlan) > + return reserved_vlan > + > + vlan_set = str(vlans).split(',') > + > + for vlan in range(1, 4095): > + if str(vlan) not in vlan_set: > + vlan_set.append(str(vlan)) > + reserved_vlan = vlan > + vlans = re.sub(r'[ \[\]\']', '', str(vlan_set)) > + ovs_vsctl("set Open_vSwitch . external_ids:vlans=%s" % vlans) > + return reserved_vlan > + > + if not reserved_vlan: > + raise RuntimeError("No more vlans available on this host") > + > + > +def unreserve_vlan(reserved_vlan): > + vlans = ovs_vsctl("--if-exists get Open_vSwitch . " > + "external_ids:vlans").strip('"') > + if not vlans: > + return > + > + vlan_set = str(vlans).split(',') > + if str(reserved_vlan) not in vlan_set: > + return > + > + vlan_set.remove(str(reserved_vlan)) > + vlans = re.sub(r'[ \[\]\']', '', str(vlan_set)) > + if vlans: > + ovs_vsctl("set Open_vSwitch . external_ids:vlans=%s" % vlans) > + else: > + ovs_vsctl("remove Open_vSwitch . external_ids vlans") > + > + > +def create_port_underlay(neutron, network, eid, ip_address, mac_address): > + reserved_vlan = reserve_vlan() > + if mac_address: > + body = {'port': {'network_id': network, > + 'binding:profile': {'parent_name': VIF_ID, > + 'tag': int(reserved_vlan)}, > + 'mac_address': mac_address, > + 'fixed_ips': [{'ip_address': ip_address}], > + 'name': eid, > + 'admin_state_up': True}} > + else: > + body = {'port': {'network_id': network, > + 'binding:profile': {'parent_name': VIF_ID, > + 'tag': int(reserved_vlan)}, > + 'fixed_ips': [{'ip_address': ip_address}], > + 'name': eid, > + 'admin_state_up': True}} > + > + try: > + ret = neutron.create_port(body) > + mac_address = ret['port']['mac_address'] > + except Exception as e: > + unreserve_vlan(reserved_vlan) > + raise RuntimeError("Failed in creation of neutron port (%s)." % > str(e)) > + > + ovs_vsctl("set Open_vSwitch . external_ids:%s_vlan=%s" > + % (eid, reserved_vlan)) > + > + return mac_address > + > + > +def get_endpointuuid_by_name(neutron, name): > + param = {'fields': 'id', 'name': name} > + ret = neutron.list_ports(**param) > + if len(ret['ports']) > 1: > + raise RuntimeError("More than one endpoint for the given name") > + elif len(ret['ports']) == 0: > + endpoint = None > + else: > + endpoint = ret['ports'][0]['id'] > + return endpoint > + > + > +@app.route('/NetworkDriver.CreateEndpoint', methods=['POST']) > +def create_endpoint(): > + if not request.data: > + abort(400) > + > + data = json.loads(request.data) > + > + nid = data.get("NetworkID", "") > + if not nid: > + abort(400) > + > + eid = data.get("EndpointID", "") > + if not eid: > + abort(400) > + > + interface = data.get("Interface", "") > + if not interface: > + error = "create_endpoint: no interfaces supplied by libnetwork" > + return jsonify({'Err': error}) > + > + ip_address_and_mask = interface.get("Address", "") > + if not ip_address_and_mask: > + error = "create_endpoint: ip address not provided by libnetwork" > + return jsonify({'Err': error}) > + > + ip_address = ip_address_and_mask.rsplit('/', 1)[0] > + mac_address_input = interface.get("MacAddress", "") > + mac_address_output = "" > + > + try: > + neutron = neutron_login() > + except Exception as e: > + error = "create_endpoint: neutron login. (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + try: > + endpoint = get_endpointuuid_by_name(neutron, eid) > + if endpoint: > + error = "create_endpoint: Endpoint has already been created" > + return jsonify({'Err': error}) > + except Exception as e: > + error = "create_endpoint: endpoint uuid by name. (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + try: > + network = get_networkuuid_by_name(neutron, nid) > + if not network: > + error = "create_endpoint: neutron network by name. (%s)" % > (nid) > + return jsonify({'Err': error}) > + except Exception as e: > + error = "create_endpoint: network uuid by name. (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + try: > + mac_address = create_port_underlay(neutron, network, eid, > ip_address, > + mac_address_input) > + except Exception as e: > + error = "create_endpoint: neutron port-create (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + if not mac_address_input: > + mac_address_output = mac_address > + > + return jsonify({"Interface": { > + "Address": "", > + "AddressIPv6": "", > + "MacAddress": mac_address_output > + }}) > + > + > +@app.route('/NetworkDriver.EndpointOperInfo', methods=['POST']) > +def show_endpoint(): > + if not request.data: > + abort(400) > + > + data = json.loads(request.data) > + > + nid = data.get("NetworkID", "") > + if not nid: > + abort(400) > + > + eid = data.get("EndpointID", "") > + if not eid: > + abort(400) > + > + try: > + neutron = neutron_login() > + except Exception as e: > + error = "%s" % (str(e)) > + return jsonify({'Err': error}) > + > + try: > + endpoint = get_endpointuuid_by_name(neutron, eid) > + if not endpoint: > + error = "show_endpoint: Failed to get endpoint by name" > + return jsonify({'Err': error}) > + except Exception as e: > + error = "show_endpoint: get endpoint by name. (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + try: > + ret = neutron.show_port(endpoint) > + mac_address = ret['port']['mac_address'] > + ip_address = ret['port']['fixed_ips'][0]['ip_address'] > + except Exception as e: > + error = "show_endpoint: show port (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + veth_outside = eid[0:15] > + return jsonify({"Value": {"ip_address": ip_address, > + "mac_address": mac_address, > + "veth_outside": veth_outside > + }}) > + > + > +@app.route('/NetworkDriver.DeleteEndpoint', methods=['POST']) > +def delete_endpoint(): > + if not request.data: > + abort(400) > + > + data = json.loads(request.data) > + > + nid = data.get("NetworkID", "") > + if not nid: > + abort(400) > + > + eid = data.get("EndpointID", "") > + if not eid: > + abort(400) > + > + try: > + neutron = neutron_login() > + except Exception as e: > + error = "delete_endpoint: neutron login (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + endpoint = get_endpointuuid_by_name(neutron, eid) > + if not endpoint: > + return jsonify({}) > + > + reserved_vlan = ovs_vsctl("--if-exists get Open_vSwitch . " > + "external_ids:%s_vlan" % eid).strip('"') > + if reserved_vlan: > + unreserve_vlan(reserved_vlan) > + ovs_vsctl("remove Open_vSwitch . external_ids %s_vlan" % eid) > + > + try: > + neutron.delete_port(endpoint) > + except Exception as e: > + error = "delete_endpoint: neutron port-delete. (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + return jsonify({}) > + > + > +@app.route('/NetworkDriver.Join', methods=['POST']) > +def network_join(): > + if not request.data: > + abort(400) > + > + data = json.loads(request.data) > + > + nid = data.get("NetworkID", "") > + if not nid: > + abort(400) > + > + eid = data.get("EndpointID", "") > + if not eid: > + abort(400) > + > + sboxkey = data.get("SandboxKey", "") > + if not sboxkey: > + abort(400) > + > + # sboxkey is of the form: /var/run/docker/netns/CONTAINER_ID > + vm_id = sboxkey.rsplit('/')[-1] > + > + try: > + neutron = neutron_login() > + except Exception as e: > + error = "network_join: neutron login. (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + subnet_name = "docker-%s" % (nid) > + try: > + subnet = get_subnetuuid_by_name(neutron, subnet_name) > + if not subnet: > + error = "network_join: can't find subnet in neutron" > + return jsonify({'Err': error}) > + except Exception as e: > + error = "network_join: subnet uuid by name. (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + try: > + ret = neutron.show_subnet(subnet) > + gateway_ip = ret['subnet']['gateway_ip'] > + if not gateway_ip: > + error = "network_join: no gateway_ip for the subnet" > + return jsonify({'Err': error}) > + except Exception as e: > + error = "network_join: neutron show subnet. (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + try: > + endpoint = get_endpointuuid_by_name(neutron, eid) > + if not endpoint: > + error = "network_join: Failed to get endpoint by name" > + return jsonify({'Err': error}) > + except Exception as e: > + error = "network_join: neutron endpoint by name. (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + try: > + ret = neutron.show_port(endpoint) > + mac_address = ret['port']['mac_address'] > + except Exception as e: > + error = "network_join: neutron show port. (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + veth_outside = eid[0:15] > + veth_inside = eid[0:13] + "_c" > + command = "ip link add %s type veth peer name %s" \ > + % (veth_inside, veth_outside) > + try: > + call_popen(shlex.split(command)) > + except Exception as e: > + error = "network_join: failed to create veth pair. (%s)" % > (str(e)) > + return jsonify({'Err': error}) > + > + command = "ip link set dev %s address %s" \ > + % (veth_inside, mac_address) > + > + try: > + call_popen(shlex.split(command)) > + except Exception as e: > + error = "network_join: failed to set veth mac address. (%s)" % > (str(e)) > + return jsonify({'Err': error}) > + > + command = "ip link set %s up" % (veth_outside) > + > + try: > + call_popen(shlex.split(command)) > + except Exception as e: > + error = "network_join: failed to up the veth iface. (%s)" % > (str(e)) > + return jsonify({'Err': error}) > + > + try: > + reserved_vlan = ovs_vsctl("--if-exists get Open_vSwitch . " > + "external_ids:%s_vlan" % eid).strip('"') > + if not reserved_vlan: > + error = "network_join: no reserved vlan for this endpoint" > + return jsonify({'Err': error}) > + ovs_vsctl("add-port %s %s tag=%s" > + % (OVN_BRIDGE, veth_outside, reserved_vlan)) > + except Exception as e: > + error = "network_join: failed to create a OVS port. (%s)" % > (str(e)) > + return jsonify({'Err': error}) > + > + return jsonify({"InterfaceName": { > + "SrcName": veth_inside, > + "DstPrefix": "eth" > + }, > + "Gateway": gateway_ip, > + "GatewayIPv6": ""}) > + > + > +@app.route('/NetworkDriver.Leave', methods=['POST']) > +def network_leave(): > + if not request.data: > + abort(400) > + > + data = json.loads(request.data) > + > + nid = data.get("NetworkID", "") > + if not nid: > + abort(400) > + > + eid = data.get("EndpointID", "") > + if not eid: > + abort(400) > + > + veth_outside = eid[0:15] > + command = "ip link delete %s" % (veth_outside) > + try: > + call_popen(shlex.split(command)) > + except Exception as e: > + error = "network_leave: failed to delete veth pair. (%s)" % > (str(e)) > + return jsonify({'Err': error}) > + > + try: > + ovs_vsctl("--if-exists del-port %s" % (veth_outside)) > + except Exception as e: > + error = "network_leave: Failed to delete port (%s)" % (str(e)) > + return jsonify({'Err': error}) > + > + return jsonify({}) > + > +if __name__ == '__main__': > + prepare() > + app.run(host='0.0.0.0') > diff --git a/rhel/openvswitch-fedora.spec.in b/rhel/ > openvswitch-fedora.spec.in > index 066086c..cb76500 100644 > --- a/rhel/openvswitch-fedora.spec.in > +++ b/rhel/openvswitch-fedora.spec.in > @@ -346,6 +346,8 @@ rm -rf $RPM_BUILD_ROOT > %files ovn > %{_bindir}/ovn-controller > %{_bindir}/ovn-controller-vtep > +%{_bindir}/ovn-docker-overlay-driver > +%{_bindir}/ovn-docker-underlay-driver > %{_bindir}/ovn-nbctl > %{_bindir}/ovn-northd > %{_bindir}/ovn-sbctl > -- > 1.9.1 > > _______________________________________________ > dev mailing list > dev@openvswitch.org > http://openvswitch.org/mailman/listinfo/dev > _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev