Docker committed experimental support for multi-host networking yesterday. This commit adds a driver that works with that experimental support. Since Docker code is not part of any official Docker releases yet, this patch is sent as a RFC.
Signed-off-by: Gurucharan Shetty <gshe...@nicira.com> --- INSTALL.Docker.md | 206 ++++++++---- ovn/utilities/automake.mk | 3 +- ovn/utilities/ovn-docker-overlay-driver | 538 ++++++++++++++++++++++++++++++++ rhel/openvswitch-fedora.spec.in | 1 + 4 files changed, 678 insertions(+), 70 deletions(-) create mode 100755 ovn/utilities/ovn-docker-overlay-driver diff --git a/INSTALL.Docker.md b/INSTALL.Docker.md index 9e14043..dbc9699 100644 --- a/INSTALL.Docker.md +++ b/INSTALL.Docker.md @@ -1,109 +1,177 @@ 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.7.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.7.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.: +===== + +OVN provides network virtualization to containers. OVN can create +logical networks amongst containers running on multiple hosts. To better +explain OVN's integration with Docker, this document explains the +end to end workflow with an example. + +* Start a IPAM server. + +For multi-host networking, you will need an entity that provides consistent +IP and MAC addresses to your container interfaces. One way to achieve this +is to use a IPAM server that integrates with OVN's Northbound database. +OpenStack Neutron already has an integration with OVN's Northbound database +via a OVN plugin and this document uses it as an example. + +Installing OpenStack Neutron with OVN plugin from scratch on a server is out +of scope of this documentation. Instead this documentation uses a +Docker image that comes pre-packaged with OpenStack Neutron and OVN's daemons +as an example. + +Start your IPAM server on any host. + +``` +docker run -d --net=host --name ipam openvswitch/ipam:v2.4.90 /sbin/ipam +``` + +Once you start your container, you can do a 'docker logs -f ipam' to see +whether the ipam container has started properly. You should see a log message +of the following form to indicate a successful start. + +``` +oslo_messaging._drivers.impl_rabbit [-] Connecting to AMQP server on localhost:5672 +neutron.wsgi [-] (670) wsgi starting up on http://0.0.0.0:9696/ +INFO oslo_messaging._drivers.impl_rabbit [-] Connected to AMQP server on 127.0.0.1:5672 +``` + +Note down the IP address of the host. This document refers to this IP address +in the remainder of the document as $IPAM_IP. + +* One time setup. + +On each host, where you plan to spawn your containers, you will need to +create an Open vSwitch integration bridge. + +``` +ovn-integrate create-integration-bridge +``` + +You will also need to set the IPAM server's IP address. + +``` +ovn-integrate set-ipam $IPAM_IP +``` + +You will also need to provide the local IP address via which other hosts +can reach this host. This IP address is referred as the local tunnel endpoint. + +``` +ovn-integrate set-tep $LOCAL_IP +``` + +By default, OVN uses Geneve tunnels for overlay networks. If you prefer to use +STT tunnels (which are known for high throughput capabilities when TSO is +turned on in your NICs), you can run the following command. (For STT +tunnels to work, you will need a STT kernel module loaded. STT kernel +module does not come as part of the upstream Linux kernel.) ``` -% docker run -d ubuntu:14.04 /bin/sh -c \ -"while true; do echo hello world; sleep 1; done" +ovn-integrate set-encap-type stt ``` -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.: +And finally, start the OVN controller. ``` -% docker run -d --net=none ubuntu:14.04 /bin/sh -c \ -"while true; do echo hello world; sleep 1; done" +ovn-controller --pidfile --detach -vconsole:off --log-file ``` -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. +* Start the Open vSwitch network driver. -* Add a new network interface to the container and attach it to an Open vSwitch - bridge. e.g.: +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. -`% ovs-docker add-port br-int eth1 $CONTAINER_ID` +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: -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'. +``` +easy_install -U pip +pip install python-neutronclient +pip install Flask +``` -The script also lets one to add IP address, MAC address, Gateway address and -MTU for the interface. e.g.: +Start the Open vSwitch driver on every host where you plan to create your +containers. ``` -% 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 +ovn-docker-overlay-driver ``` -* A previously added network interface can be deleted. e.g.: +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. -`% ovs-docker del-port br-int eth1 $CONTAINER_ID` +* Create your logical switch. -All the previously added Open vSwitch interfaces inside a container can be -deleted. e.g.: +To create a logical switch with name 'foo', run: -`% ovs-docker del-ports br-int $CONTAINER_ID` +``` +docker network create -d openvswitch foo +``` -It is important that the same $CONTAINER_ID be passed to both add-port -and del-port[s] commands. +* List your logical switches. -* More network control. +``` +docker network ls +``` -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.: +* Create your logical port. + +To create a logical port with name 'db' in the network 'foo', run: ``` -% docker run -d --net=container:$MICROCONTAINER_ID ubuntu:14.04 /bin/sh -c \ -"while true; do echo hello world; sleep 1; done" +docker service publish db.foo ``` -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. +* List all your logical ports. + +``` +docker service ls +``` -Docker networking is quite flexible and can be used in multiple ways. For more -information, please read: -https://docs.docker.com/articles/networking +* Attach your logical port to a container. -Bug Reporting -------------- +``` +docker service attach CONTAINER_ID db.foo +``` -Please report problems to b...@openvswitch.org. +* Detach your logical port from a container. + +``` +docker service detach CONTAINER_ID db.foo +``` + +Delete your logical port. + +``` +docker service unpublish db.foo +``` + +* Running commands directly on the IPAM server (bypassing Docker) + +Since the above examples shows integration with a OpenStack Neutron +IPAM server, one can directlty invoke 'neutron' commands to fetch +information about logical switches and ports. e.g: + +``` +export OS_URL="http://$IPAM_IP:9696/" +export OS_AUTH_STRATEGY="noauth" +neutron net-list +``` [INSTALL.md]:INSTALL.md -[IntegrationGuide.md]:IntegrationGuide.md diff --git a/ovn/utilities/automake.mk b/ovn/utilities/automake.mk index 1a1a159..618e051 100644 --- a/ovn/utilities/automake.mk +++ b/ovn/utilities/automake.mk @@ -5,7 +5,8 @@ man_MANS += \ ovn/utilities/ovn-ctl.8 bin_SCRIPTS += \ - ovn/utilities/ovn-integrate + ovn/utilities/ovn-integrate \ + ovn/utilities/ovn-docker-overlay-driver EXTRA_DIST += \ ovn/utilities/ovn-ctl \ diff --git a/ovn/utilities/ovn-docker-overlay-driver b/ovn/utilities/ovn-docker-overlay-driver new file mode 100755 index 0000000..5076083 --- /dev/null +++ b/ovn/utilities/ovn-docker-overlay-driver @@ -0,0 +1,538 @@ +#! /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 atexit +import json +import netaddr +import os +import shlex +import subprocess +import sys +import uuid +from neutronclient.v2_0 import client +from flask import Flask, jsonify +from flask import request, abort + +app = Flask(__name__) + +AUTH_STRATEGY = "" +ENDPOINT_URL = "" +OVN_BRIDGE = "br-int" +PLUGIN_DIR = "/usr/share/docker/plugins" +PLUGIN_FILE = "/usr/share/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, "-vconsole:off"] + args_list + return call_popen(cmd) + + +def ovs_vsctl(args): + return call_prog("ovs-vsctl", shlex.split(args)) + + +def sanity_check(): + br_list = ovs_vsctl("list-br").split() + if OVN_BRIDGE not in br_list: + raise RuntimeError("OVN bridge is not seen") + + global AUTH_STRATEGY, ENDPOINT_URL + + AUTH_STRATEGY = "noauth" + endpoint_ip = ovs_vsctl("get Open_vSwitch . " + "external_ids:ipam").strip('"') + if not endpoint_ip: + raise RuntimeError("ipam server's ip address not set") + ENDPOINT_URL = "http://%s:9696/" % (endpoint_ip) + os.environ['OS_URL'] = ENDPOINT_URL + os.environ['OS_AUTH_STRATEGY'] = "noauth" + + +def cleanup(): + if os.path.isfile(PLUGIN_FILE): + os.remove(PLUGIN_FILE) + + +def init(): + br_list = ovs_vsctl("list-br").split() + if OVN_BRIDGE not in br_list: + sys.exit("Bridge %s does not exists" % (OVN_BRIDGE)) + + encap_type = ovs_vsctl("--if-exists get Open_vSwitch . " + "external_ids:ovn-encap-type").strip('"') + if not encap_type: + ovs_vsctl("set open_vswitch . external_ids:ovn-bridge=%s " + "external_ids:ovn-encap-type=geneve" % OVN_BRIDGE) + + if not os.path.isdir(PLUGIN_DIR): + sys.exit("No docker plugin directory configured") + + try: + fo = open(PLUGIN_FILE, "w") + fo.write("tcp://0.0.0.0:5000") + fo.close() + except Exception as e: + sys.exit("Failed to write to spec file (%s)" % (str(e))) + + atexit.register(cleanup) + + +# curl -i -H 'Content-Type: application/json' -X POST +# http://localhost:5000/Plugin.Activate +@app.route('/Plugin.Activate', methods=['POST']) +def plugin_activate(): + return jsonify({"Implements": ["NetworkDriver"]}) + + +def neutron_login(): + try: + sanity_check() + neutron = client.Client(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 + + +# curl -i -H 'Content-Type: application/json' -X POST -d +# '{"NetworkID":"dummy-network","Options":{"subnet":"192.168.1.0/24"}}' +# http://localhost:5000/NetworkDriver.CreateNetwork +@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) + + # Docker currently does not let you specify additional arguments. But + # plans to in the future. Till then every network is 192.168.0.0/16 + subnet = "192.168.0.0/16" + if not subnet: + abort(400) + + try: + neutron = neutron_login() + except Exception as e: + error = "%s" % (str(e)) + return jsonify({'Err': error}) + + # XXX: Currently, a create-network request from a user on one host ends + # up being a create network request in every host. This is a huge + # performance penalty as now we will need to check for the existance + # of such a network in every call. So in a 1000 node system, a create + # network request from user will have 1000 requests to OVN's IPAM. + try: + if get_networkuuid_by_name(neutron, network): + return jsonify({}) + except Exception as e: + error = "%s" % (str(e)) + return jsonify({'Err': error}) + + try: + body = {'network': {'name': network, + 'tenant_id': "admin", + 'admin_state_up': True}} + ret = neutron.create_network(body) + network_id = ret['network']['id'] + except Exception as e: + error = "Failed in neutron api call (%s)" % str(e) + return jsonify({'Err': error}) + + try: + netaddr.IPNetwork(subnet) + except Exception as e: + neutron.delete_network(network_id) + error = "Invalid subnet specified." + return jsonify({'Err': error}) + + try: + body = {'subnet': {'network_id': network_id, + 'tenant_id': "admin", + 'ip_version': 4, + 'cidr': subnet, + 'name': network}} + ret = neutron.create_subnet(body) + except Exception as e: + neutron.delete_network(network_id) + error = "Failed in neutron api call (%s)" % str(e) + return jsonify({'Err': error}) + + return jsonify({}) + + +# curl -i -H 'Content-Type: application/json' -X POST -d +# {"NetworkID":"dummy-network"} +# http://localhost:5000/NetworkDriver.DeleteNetwork +@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 = "%s" % (str(e)) + return jsonify({'Err': error}) + + network = get_networkuuid_by_name(neutron, nid) + if not network: + return jsonify({}) + + try: + neutron.delete_network(network) + except Exception as e: + error = "Failed in neutron api call (%s)" % str(e) + return jsonify({'Err': error}) + + return jsonify({}) + + +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 + + +# curl -i -H 'Content-Type: application/json' -X POST -d +# '{"NetworkID":"dummy-network","EndpointID":"dummy-endpoint","Interfaces":[],"Options":{}}' +# http://localhost:5000/NetworkDriver.CreateEndpoint +@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) + + interfaces = data.get("Interfaces", "") + if interfaces: + # If interfaces has record, the endpoint has already + # been created. + return jsonify({}) + + try: + neutron = neutron_login() + except Exception as e: + error = "%s" % (str(e)) + return jsonify({'Err': error}) + + network = get_networkuuid_by_name(neutron, nid) + if not network: + error = "Failed to get neutron network record for (%s)" % (nid) + return jsonify({'Err': error}) + + try: + ret = neutron.show_network(network) + subnet = ret['network']['subnets'][0] + if not subnet: + raise RuntimeError("No subnet defined for the network.") + except Exception as e: + error = "Could not obtain network information.\n(%s)" % (str(e)) + return jsonify({'Err': error}) + + try: + ret = neutron.show_subnet(subnet) + gateway_ip = ret['subnet']['gateway_ip'] + cidr = ret['subnet']['cidr'] + netmask = cidr.rsplit('/', 1)[1] + if not netmask: + raise RuntimeError("No cidr netmask found for subnet") + except Exception as e: + error = "Could not obtain subnet information (%s)" % (str(e)) + return jsonify({'Err': error}) + + try: + body = {'port': {'network_id': network, + 'tenant_id': "admin", + 'name': eid, + 'admin_state_up': True}} + + ret = neutron.create_port(body) + mac_address = ret['port']['mac_address'] + ip_address = "%s/%s" \ + % (ret['port']['fixed_ips'][0]['ip_address'], netmask) + + except Exception as e: + error = "Failed in neutron port creation call (%s)" % (str(e)) + return jsonify({'Err': error}) + + return jsonify({"Interfaces": [{ + "ID": 0, + "Address": ip_address, + "AddressIPv6": None, + "MacAddress": mac_address + }]}) + + +# curl -i -H 'Content-Type: application/json' -X POST -d +# '{"NetworkID":"dummy-network","EndpointID":"dummy-endpoint"}' +# http://localhost:5000/NetworkDriver.EndpointOperInfo +@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}) + + endpoint = get_endpointuuid_by_name(neutron, eid) + if not endpoint: + error = "Failed to get endpoint by name" + 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 = "Failed to get endpoint information (%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 + }}) + + +# curl -i -H 'Content-Type: application/json' -X POST -d +# '{"NetworkID":"dummy-network","EndpointID":"dummy-endpoint"}' +# http://localhost:5000/NetworkDriver.DeleteEndpoint +@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 = "%s" % (str(e)) + return jsonify({'Err': error}) + + endpoint = get_endpointuuid_by_name(neutron, eid) + if not endpoint: + return jsonify({}) + + try: + neutron.delete_port(endpoint) + except Exception as e: + error = "Failed to delete endpoint. (%s)" % (str(e)) + return jsonify({'Err': error}) + + return jsonify({}) + + +# curl -i -H 'Content-Type: application/json' -X POST -d +# '{u'NetworkID': u'dummy-network', u'SandboxKey': u'sandbox-key', \ +# u'Options': {u'foo': u'fooValue'}, u'EndpointID': u'dummy-endpoint'}' +# http://localhost:5000/NetworkDriver.Join +@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 = "%s" % (str(e)) + return jsonify({'Err': error}) + + endpoint = get_endpointuuid_by_name(neutron, eid) + if not endpoint: + error = "Failed to get endpoint by name" + return jsonify({'Err': error}) + + try: + ret = neutron.show_port(endpoint) + mac_address = ret['port']['mac_address'] + except Exception as e: + error = "Failed to get endpoint information (%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 = "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 = "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 = "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, + endpoint, vm_id, "active")) + except Exception as e: + error = "Failed to create a port (%s)" % (str(e)) + return jsonify({'Err': error}) + + return jsonify({"InterfaceNames": [{ + "SrcName": veth_inside, + "DstPrefix": "eth" + }], + "Gateway": "", + "GatewayIPv6": "", + "HostsPath": "", + "ResolvConfPath": ""}) + + +# curl -i -H 'Content-Type: application/json' -X POST -d +# '{"NetworkID":"dummy-network","EndpointID":"dummy-endpoint"}' +# http://localhost:5000/NetworkDriver.Leave +@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 = "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 = "Failed to delete port (%s)" % (str(e)) + return jsonify({'Err': error}) + + return jsonify({}) + +if __name__ == '__main__': + init() + app.run(host='0.0.0.0') diff --git a/rhel/openvswitch-fedora.spec.in b/rhel/openvswitch-fedora.spec.in index dfdcdca..2cfe6ce 100644 --- a/rhel/openvswitch-fedora.spec.in +++ b/rhel/openvswitch-fedora.spec.in @@ -318,6 +318,7 @@ rm -rf $RPM_BUILD_ROOT %files ovn %{_bindir}/ovn-controller +%{_bindir}/ovn-docker-overlay-driver %{_bindir}/ovn-integrate %{_bindir}/ovn-nbctl %{_bindir}/ovn-northd -- 1.9.1 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev