Browse Source

Intial Framework for networking-lagopus

This is an initial framework for networking-lagopus
contains ML2 Drivers and Agent for Lagopus switch.

Change-Id: Id7079f8e28fef01bdfca6928bf339eaaf4d00cd9
Hirofumi Ichihara 1 year ago
parent
commit
12f143cce2

+ 13
- 0
devstack/README.rst View File

@@ -0,0 +1,13 @@
1
+======================
2
+ Enabling in Devstack
3
+======================
4
+
5
+1. Download DevStack
6
+
7
+2. Copy the sample local.conf over::
8
+
9
+    cp devstack/local.conf.example local.conf
10
+
11
+3. Copy the lagopus_agent over::
12
+
13
+    cp devstack/lagopus_agent %{DEVSTACK_HOME}/lib/neutron_plugins/

+ 9
- 0
devstack/files/debs/networking-lagopus View File

@@ -0,0 +1,9 @@
1
+build-essential
2
+libexpat-dev
3
+libgmp-dev
4
+libssl-dev
5
+libpcap-dev
6
+byacc
7
+flex
8
+python-dev
9
+python-twisted

+ 42
- 0
devstack/generate_dsl.py View File

@@ -0,0 +1,42 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+import sys
14
+
15
+
16
+LAGOPUS_DSL_TEMPLATE = "lagopus_template.dsl"
17
+
18
+
19
+def main():
20
+    if len(sys.argv) < 3:
21
+        print("usage: generate_dsl dpdk_port_mappings dsl_path")
22
+        return 1
23
+    dpdk_port_mappings = sys.argv[1]
24
+    dsl_path = sys.argv[2]
25
+
26
+    phys_nets = []
27
+    for map in dpdk_port_mappings.split(','):
28
+        _, phys = map.split('#')
29
+        phys_nets.append(phys)
30
+
31
+    with open(LAGOPUS_DSL_TEMPLATE, "r") as dsl:
32
+        bridge_conf = dsl.read()
33
+
34
+    with open(dsl_path, "w") as f:
35
+        num = 1
36
+        for phys in phys_nets:
37
+            f.write(bridge_conf % {'num': num, 'port': num - 1})
38
+            num += 1
39
+
40
+
41
+if __name__ == "__main__":
42
+    sys.exit(main())

+ 130
- 0
devstack/lagopus View File

@@ -0,0 +1,130 @@
1
+#!/bin/bash
2
+
3
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+#    not use this file except in compliance with the License. You may obtain
5
+#    a copy of the License at
6
+#
7
+#         http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+#    Unless required by applicable law or agreed to in writing, software
10
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+#    License for the specific language governing permissions and limitations
13
+#    under the License.
14
+
15
+# Save trace setting
16
+_XTRACE_NEUTRON_LAGOPUS=$(set +o | grep xtrace)
17
+set +o xtrace
18
+
19
+function install_lagopus {
20
+    git_clone $LAGOPUS_REPO $LAGOPUS_DIR $LAGOPUS_BRANCH
21
+    cd $LAGOPUS_DIR
22
+    ./configure --enable-hybrid
23
+    make
24
+    sudo make install
25
+}
26
+
27
+function generate_dsl {
28
+    local dir=${GITDIR['networking-lagopus']}
29
+    sudo mkdir -p $LAGOPUS_CONF_DIR
30
+    sudo python $dir/devstack/generate_dsl.py \
31
+        $DPDK_PORT_MAPPINGS $LAGOPUS_CONF_DIR/$LAGOPUS_CONF
32
+}
33
+
34
+function run_lagopus {
35
+    echo "run_lagopus"
36
+    generate_dsl
37
+    sudo lagopus -C $LAGOPUS_CONF_DIR/$LAGOPUS_CONF -l $LAGOPUS_LOG \
38
+        -- -c $LAGOPUS_CORE_MASK -n1 --socket-mem $LAGOPUS_SOCKET_MEM \
39
+        --huge-dir $LAGOPUS_HUGEPAGE_MOUNT -- --core-assign balance
40
+}
41
+
42
+function stop_lagopus {
43
+    echo "stop_lagopus"
44
+    sudo pkill -15 -f "lagopus -C $LAGOPUS_CONF_DIR/$LAGOPUS_CONF"
45
+    sudo rm -f /tmp/sock*
46
+}
47
+
48
+function free_hugepages(){
49
+    HUGEPAGE_SIZE=$(grep Hugepagesize /proc/meminfo | awk '{ print $2 }')
50
+
51
+    sudo rm -rf ${LAGOPUS_HUGEPAGE_MOUNT}/rtemap*
52
+    sudo umount ${LAGOPUS_HUGEPAGE_MOUNT}
53
+
54
+    if [ $LAGOPUS_ALLOCATE_HUGEPAGES == 'True' ]; then
55
+       for d in /sys/devices/system/node/node? ; do
56
+          echo 0 | sudo tee $d/hugepages/hugepages-${HUGEPAGE_SIZE}kB/nr_hugepages
57
+       done
58
+    fi
59
+
60
+    #TODO: restart libvirtd ?
61
+}
62
+
63
+function alloc_hugepages(){
64
+    HUGEPAGE_SIZE=$(grep Hugepagesize /proc/meminfo | awk '{ print $2 }')
65
+
66
+    if [ $LAGOPUS_NUM_HUGEPAGES -eq 0 ]; then
67
+        die 6 $LINENO "LAGOPUS_NUM_HUGEPAGES not set"
68
+    fi
69
+
70
+    if grep -ws $LAGOPUS_HUGEPAGE_MOUNT /proc/mounts > /dev/null; then
71
+        free_hugepages
72
+    fi
73
+
74
+    if [ $LAGOPUS_ALLOCATE_HUGEPAGES == 'True' ]; then
75
+        for d in /sys/devices/system/node/node? ; do
76
+            echo $LAGOPUS_NUM_HUGEPAGES | sudo tee $d/hugepages/hugepages-${HUGEPAGE_SIZE}kB/nr_hugepages
77
+        done
78
+    fi
79
+
80
+    sudo mkdir -p $LAGOPUS_HUGEPAGE_MOUNT
81
+    sudo mount -t hugetlbfs nodev $LAGOPUS_HUGEPAGE_MOUNT
82
+
83
+    #TODO: restart libvirtd ?
84
+}
85
+
86
+function bind_nics() {
87
+    if [ -n "$DPDK_PORT_MAPPINGS" ]; then
88
+        sudo modprobe uio
89
+        if ! lsmod | grep -ws igb_uio > /dev/null; then
90
+            sudo insmod $DPDK_DIR/build/kmod/igb_uio.ko
91
+        fi
92
+
93
+        MAPPINGS=${DPDK_PORT_MAPPINGS//,/ }
94
+        ARRAY=( $MAPPINGS )
95
+        NICS=""
96
+        for pair in "${ARRAY[@]}"; do
97
+            addr=`echo $pair | cut -f 1 -d "#"`
98
+            NICS="$NICS $addr"
99
+        done
100
+        sudo $DPDK_DIR/usertools/dpdk-devbind.py -b igb_uio $NICS
101
+    fi
102
+}
103
+
104
+function unbind_nics() {
105
+    if [ -n "$DPDK_PORT_MAPPINGS" ]; then
106
+        MAPPINGS=${DPDK_PORT_MAPPINGS//,/ }
107
+        ARRAY=( $MAPPINGS )
108
+        NICS=""
109
+        for pair in "${ARRAY[@]}"; do
110
+            addr=`echo $pair | cut -f 1 -d "#"`
111
+            NICS="$NICS $addr"
112
+        done
113
+        sudo $DPDK_DIR/usertools/dpdk-devbind.py -b e1000 $NICS
114
+    fi
115
+}
116
+
117
+function prepare_lagopus(){
118
+    alloc_hugepages
119
+    bind_nics
120
+}
121
+
122
+function cleanup_lagopus(){
123
+    unbind_nics
124
+    if grep -ws $LAGOPUS_HUGEPAGE_MOUNT /proc/mounts > /dev/null; then
125
+        free_hugepages
126
+    fi
127
+}
128
+
129
+# Restore xtrace
130
+$_XTRACE_NEUTRON_LAGOPUS

+ 91
- 0
devstack/lagopus_agent View File

@@ -0,0 +1,91 @@
1
+#!/bin/bash
2
+
3
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+#    not use this file except in compliance with the License. You may obtain
5
+#    a copy of the License at
6
+#
7
+#         http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+#    Unless required by applicable law or agreed to in writing, software
10
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+#    License for the specific language governing permissions and limitations
13
+#    under the License.
14
+
15
+# Save trace setting
16
+_XTRACE_NEUTRON_LAGOPUS=$(set +o | grep xtrace)
17
+set +o xtrace
18
+
19
+function neutron_plugin_create_nova_conf {
20
+    :
21
+}
22
+
23
+function neutron_plugin_install_agent_packages {
24
+    :
25
+}
26
+
27
+function is_neutron_ovs_base_plugin {
28
+    return 1
29
+}
30
+
31
+function neutron_plugin_configure_debug_command {
32
+    :
33
+}
34
+
35
+function neutron_plugin_configure_dhcp_agent {
36
+    :
37
+}
38
+
39
+function neutron_plugin_configure_l3_agent {
40
+    :
41
+}
42
+
43
+function neutron_plugin_configure_plugin_agent {
44
+    AGENT_BINARY="$NEUTRON_BIN_DIR/neutron-lagopus-agent"
45
+
46
+    # Install networking-lagopus
47
+    dir=${GITDIR['networking-lagopus']}
48
+    setup_package $dir
49
+
50
+    if [ -n "$DPDK_PORT_MAPPINGS" ]; then
51
+        MAPPINGS=${DPDK_PORT_MAPPINGS//,/ }
52
+        ARRAY=( $MAPPINGS )
53
+        BRIDGE_MAPPINGS=""
54
+        num=1
55
+        for map in "${ARRAY[@]}"; do
56
+            phys=`echo $map | cut -f 2 -d "#"`
57
+            pair="$phys:bridge0$num"
58
+            if [ $num != "1" ]; then
59
+                pair=",$pair"
60
+            fi
61
+            BRIDGE_MAPPINGS="$BRIDGE_MAPPINGS$pair"
62
+            num=$(($num+1))
63
+        done
64
+        iniset /$Q_PLUGIN_CONF_FILE lagopus bridge_mappings $BRIDGE_MAPPINGS
65
+    fi
66
+    iniset /$Q_PLUGIN_CONF_FILE securitygroup firewall_driver neutron.agent.firewall.NoopFirewallDriver
67
+}
68
+
69
+function neutron_plugin_setup_interface_driver {
70
+    local conf_file=$1
71
+    # TODO: Adds lagopus interface_driver
72
+    iniset $conf_file DEFAULT interface_driver lagopus
73
+}
74
+
75
+
76
+#function has_neutron_plugin_security_group {
77
+#    # 1 means False here
78
+#    return 0
79
+#}
80
+
81
+function neutron_plugin_check_adv_test_requirements {
82
+    is_service_enabled q-agt && is_service_enabled q-dhcp && return 0
83
+}
84
+
85
+
86
+function has_neutron_plugin_security_group {
87
+    return 1
88
+}
89
+
90
+# Restore xtrace
91
+$_XTRACE_NEUTRON_LAGOPUS

+ 6
- 0
devstack/lagopus_template.dsl View File

@@ -0,0 +1,6 @@
1
+channel channel0%(num)d create -dst-addr 127.0.0.1 -protocol tcp
2
+controller controller0%(num)d create -channel channel0%(num)d -role equal -connection-type main
3
+interface interface0%(num)d create -type ethernet-dpdk-phy -port-number %(port)d
4
+port port0%(num)d create -interface interface0%(num)d
5
+bridge bridge0%(num)d create -controller controller0%(num)d -port port0%(num)d 1 -dpid %(num)d -l2-bridge True -mactable-ageing-time 300 -mactable-max-entries 8192
6
+bridge bridge0%(num)d enable

+ 45
- 0
devstack/plugin.sh View File

@@ -0,0 +1,45 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+# Save trace setting
14
+_XTRACE_NEUTRON_LAGOPUS=$(set +o | grep xtrace)
15
+set +o xtrace
16
+
17
+dir=${GITDIR['networking-lagopus']}
18
+
19
+source $dir/devstack/settings
20
+source $dir/devstack/lagopus_agent
21
+source $dir/devstack/lagopus
22
+
23
+if is_service_enabled q-agt; then
24
+    if [[ "$1" == "stack" && "$2" == "install" ]]; then
25
+	if [[ "$LAGOPUS_INSTALL" == "True" ]]; then
26
+	    install_lagopus
27
+	fi
28
+	if [[ "$LAGOPUS_RUN" == "True" ]]; then
29
+	    prepare_lagopus
30
+	fi
31
+    elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then
32
+	if [[ "$LAGOPUS_RUN" == "True" ]]; then
33
+	    run_lagopus
34
+	fi
35
+	_neutron_deploy_rootwrap_filters $dir
36
+    elif [[ "$1" == "unstack" ]]; then
37
+	if [[ "$LAGOPUS_RUN" == "True" ]]; then
38
+	    stop_lagopus
39
+            cleanup_lagopus
40
+	fi
41
+    fi
42
+fi
43
+
44
+# Restore xtrace
45
+$_XTRACE_NEUTRON_LAGOPUS

+ 32
- 0
devstack/settings View File

@@ -0,0 +1,32 @@
1
+#!/bin/bash
2
+
3
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4
+#    not use this file except in compliance with the License. You may obtain
5
+#    a copy of the License at
6
+#
7
+#         http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+#    Unless required by applicable law or agreed to in writing, software
10
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
+#    License for the specific language governing permissions and limitations
13
+#    under the License.
14
+
15
+LAGOPUS_VHOST_MODE=${LAGOPUS_VHOST_MODE:-True}
16
+LAGOPUS_CONF_DIR=${LAGOPUS_CONF_DIR:-/usr/local/etc/lagopus}
17
+LAGOPUS_CONF=${LAGOPUS_CONF:-lagopus.dsl}
18
+LAGOPUS_LOG=${LAGOPUS_LOG:-/tmp/lagopus.log}
19
+LAGOPUS_REPO=${LAGOPUS_REPO:-https://github.com/lagopus/lagopus.git}
20
+LAGOPUS_BRANCH=${LAGOPUS_BRANCH:-master}
21
+LAGOPUS_DIR=${LAGOPUS_DIR:-$DEST/lagopus}
22
+LAGOPUS_INSTALL=${LAGOPUS_INSTALL:-False}
23
+LAGOPUS_RUN=${LAGOPUS_RUN:-False}
24
+
25
+LAGOPUS_NUM_HUGEPAGES=${LAGOPUS_NUM_HUGEPAGES:-1024}
26
+LAGOPUS_ALLOCATE_HUGEPAGES=${LAGOPUS_ALLOCATE_HUGEPAGES:-True}
27
+LAGOPUS_HUGEPAGE_MOUNT=${LAGOPUS_HUGEPAGE_MOUNT:-/mnt/huge}
28
+LAGOPUS_CORE_MASK=${LAGOPUS_CORE_MASK:-3}
29
+LAGOPUS_SOCKET_MEM=${LAGOPUS_SOCKET_MEM:-512}
30
+
31
+DPDK_PORT_MAPPINGS=${DPDK_PORT_MAPPINGS:-""}
32
+DPDK_DIR=${DPDK_DIR:-$LAGOPUS_DIR/src/dpdk}

+ 18
- 0
devstack/tools/make_ns.sh View File

@@ -0,0 +1,18 @@
1
+#!/bin/bash
2
+
3
+if [ $# -ne 4 ]; then
4
+    echo "Argument Error: ./make_ns.sh veth0 veth1 ns1 172.21.0.1/24 " 1>&2
5
+    exit 1
6
+fi
7
+
8
+VETH_01=$1
9
+VETH_02=$2
10
+NAMESPACE=$3
11
+ADDRESS=$4
12
+
13
+sudo ip link add $VETH_01 type veth peer name $VETH_02
14
+sudo ip netns add $NAMESPACE
15
+sudo ip link set $VETH_02 netns $NAMESPACE
16
+sudo ip netns exec $NAMESPACE ip addr add $ADDRESS dev $VETH_02
17
+sudo ip link set $VETH_01 up
18
+sudo ip netns exec $NAMESPACE ip link set $VETH_02 up

+ 15
- 0
etc/neutron/rootwrap.d/lagopus.filters View File

@@ -0,0 +1,15 @@
1
+# neutron-rootwrap command filters for nodes on which neutron is
2
+# expected to control network
3
+#
4
+# This file should be owned by (and only-writeable by) the root user
5
+
6
+# format seems to be
7
+# cmd-name: filter-name, raw-command, user, args
8
+
9
+[Filters]
10
+
11
+# ip_lib
12
+ip: IpFilter, ip, root
13
+ip_exec: IpNetnsExecFilter, ip, root
14
+
15
+ethtool: CommandFilter, ethtool, root

+ 0
- 2
networking_lagopus/__init__.py View File

@@ -1,5 +1,3 @@
1
-# -*- coding: utf-8 -*-
2
-
3 1
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 2
 # not use this file except in compliance with the License. You may obtain
5 3
 # a copy of the License at

+ 40
- 0
networking_lagopus/_i18n.py View File

@@ -0,0 +1,40 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+import oslo_i18n
14
+
15
+DOMAIN = "networking_lagopus"
16
+
17
+_translators = oslo_i18n.TranslatorFactory(domain=DOMAIN)
18
+
19
+# The primary translation function using the well-known name "_"
20
+_ = _translators.primary
21
+
22
+# The contextual translation function using the name "_C"
23
+_C = _translators.contextual_form
24
+
25
+# The plural translation function using the name "_P"
26
+_P = _translators.plural_form
27
+
28
+# Translators for log levels.
29
+#
30
+# The abbreviated names are meant to reflect the usual use of a short
31
+# name like '_'. The "L" is for "log" and the other letter comes from
32
+# the level.
33
+_LI = _translators.log_info
34
+_LW = _translators.log_warning
35
+_LE = _translators.log_error
36
+_LC = _translators.log_critical
37
+
38
+
39
+def get_available_languages():
40
+    return oslo_i18n.get_available_languages(DOMAIN)

+ 0
- 0
networking_lagopus/agent/__init__.py View File


+ 93
- 0
networking_lagopus/agent/interface.py View File

@@ -0,0 +1,93 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+from neutron_lib import constants as n_const
14
+from neutron_lib import context
15
+from oslo_log import log as logging
16
+
17
+from neutron.agent.linux import interface as n_interface
18
+from neutron.agent.linux import ip_lib
19
+from neutron.agent import rpc as agent_rpc
20
+from neutron.common import topics
21
+
22
+from networking_lagopus.agent import rpc as lagopus_rpc
23
+
24
+LOG = logging.getLogger(__name__)
25
+
26
+
27
+class LagopusInterfaceDriver(n_interface.LinuxInterfaceDriver):
28
+
29
+    DEV_NAME_PREFIX = 'ns-'
30
+
31
+    def __init__(self, conf):
32
+        super(LagopusInterfaceDriver, self).__init__(conf)
33
+        self.context = context.get_admin_context_without_session()
34
+        self.host = self.conf.host
35
+        self.agent_id = 'lagopus-agent-%s' % self.host
36
+        self.plugin_api = agent_rpc.PluginApi(topic=topics.PLUGIN)
37
+        self.lagopus_api = lagopus_rpc.LagopusAgentApi()
38
+
39
+    def _get_network_segment(self, port_id):
40
+        details = self.plugin_api.get_device_details(self.context,
41
+                                                     port_id,
42
+                                                     self.agent_id,
43
+                                                     self.host)
44
+        if details.get('physical_network'):
45
+            return {'physical_network': details['physical_network'],
46
+                    'network_type': details['network_type'],
47
+                    'segmentation_id': details['segmentation_id']}
48
+
49
+    def _disable_tcp_offload(self, namespace, device_name):
50
+        ip_wrapper = ip_lib.IPWrapper(namespace)
51
+        cmd = ['ethtool', '-K', device_name, 'tx', 'off', 'tso', 'off']
52
+        ip_wrapper.netns.execute(cmd)
53
+
54
+    def plug_new(self, network_id, port_id, device_name, mac_address,
55
+                 bridge=None, namespace=None, prefix=None, mtu=None):
56
+        """Plugin the interface."""
57
+        ip = ip_lib.IPWrapper()
58
+        tap_name = device_name.replace(prefix or self.DEV_NAME_PREFIX,
59
+                                       n_const.TAP_DEVICE_PREFIX)
60
+        root_veth, ns_veth = ip.add_veth(tap_name, device_name,
61
+                                         namespace2=namespace)
62
+        root_veth.disable_ipv6()
63
+        ns_veth.link.set_address(mac_address)
64
+
65
+        if mtu:
66
+            root_veth.link.set_mtu(mtu)
67
+            ns_veth.link.set_mtu(mtu)
68
+        else:
69
+            LOG.warning("No MTU configured for port %s", port_id)
70
+
71
+        root_veth.link.set_up()
72
+        ns_veth.link.set_up()
73
+        self._disable_tcp_offload(namespace, device_name)
74
+
75
+        # do first and error check
76
+        segment = self._get_network_segment(port_id)
77
+
78
+        self.lagopus_api.plug_rawsock(self.context, tap_name, segment)
79
+        self.plugin_api.update_device_up(self.context, port_id,
80
+                                         self.agent_id, self.host)
81
+
82
+    def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
83
+        """Unplug the interface."""
84
+        device = ip_lib.IPDevice(device_name, namespace=namespace)
85
+        tap_name = device_name.replace(prefix or self.DEV_NAME_PREFIX,
86
+                                       n_const.TAP_DEVICE_PREFIX)
87
+        try:
88
+            self.lagopus_api.unplug_rawsock(self.context, tap_name)
89
+            device.link.delete()
90
+            LOG.debug("Unplugged interface '%s'", device_name)
91
+        except RuntimeError:
92
+            LOG.error("Failed unplugging interface '%s'",
93
+                      device_name)

+ 124
- 0
networking_lagopus/agent/lagopus_lib.py View File

@@ -0,0 +1,124 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+import socket
14
+
15
+from neutron_lib import constants as n_const
16
+from oslo_log import helpers as log_helpers
17
+from oslo_log import log as logging
18
+
19
+from networking_lagopus.agent import lagosh
20
+
21
+LOG = logging.getLogger(__name__)
22
+SOCKET_ISSUE = "Socket connection refused.  Lagopus is not running?\n"
23
+
24
+
25
+class LagopusCommand(object):
26
+
27
+    def _lagosh(self, cmd=None):
28
+        if not cmd:
29
+            return
30
+        lagosh_client = lagosh.ds_client()
31
+        try:
32
+            return lagosh_client.call(cmd)
33
+        except socket.error:
34
+            LOG.debug("_lagosh socket error")
35
+        except lagosh.DSLError as e:
36
+            LOG.debug("_lagosh DSLError cmd: %s, error: %s", cmd, e)
37
+
38
+    def show_interfaces(self):
39
+        cmd = "interface\n"
40
+        return self._lagosh(cmd)
41
+
42
+    def show_ports(self):
43
+        cmd = "port\n"
44
+        return self._lagosh(cmd)
45
+
46
+    def show_bridges(self):
47
+        cmd = "bridge\n"
48
+        return self._lagosh(cmd)
49
+
50
+    @log_helpers.log_method_call
51
+    def create_channel(self, name):
52
+        cmd = "channel %s create -dst-addr 127.0.0.1 -protocol tcp\n" % name
53
+        self._lagosh(cmd)
54
+
55
+    @log_helpers.log_method_call
56
+    def create_controller(self, name, channel):
57
+        cmd = ("controller %s create -channel %s -role equal "
58
+               "-connection-type main\n") % (name, channel)
59
+        self._lagosh(cmd)
60
+
61
+    @log_helpers.log_method_call
62
+    def create_bridge(self, name, controller, dpid):
63
+        cmd = ("bridge %s create -controller %s -dpid %d "
64
+               "-l2-bridge True -mactable-ageing-time 300 "
65
+               "-mactable-max-entries 8192\n") % (name, controller, dpid)
66
+        self._lagosh(cmd)
67
+        cmd = "bridge %s enable\n" % name
68
+        self._lagosh(cmd)
69
+
70
+    # TODO(hichihara): unify create_*_interface
71
+    @log_helpers.log_method_call
72
+    def create_vhost_interface(self, name, device):
73
+        cmd = ("interface %s create -type ethernet-dpdk-phy "
74
+               "-device %s\n") % (name, device)
75
+        self._lagosh(cmd)
76
+
77
+    def create_pipe_interface(self, name, device):
78
+        self.create_vhost_interface(name, device)
79
+
80
+    @log_helpers.log_method_call
81
+    def create_rawsock_interface(self, name, device):
82
+        cmd = ("interface %s create -type ethernet-rawsock "
83
+               "-device %s\n") % (name, device)
84
+        self._lagosh(cmd)
85
+
86
+    @log_helpers.log_method_call
87
+    def create_port(self, port, interface):
88
+        cmd = "port %s create -interface %s\n" % (port, interface)
89
+        self._lagosh(cmd)
90
+
91
+    @log_helpers.log_method_call
92
+    def destroy_port(self, port):
93
+        cmd = "port %s destroy\n" % port
94
+        self._lagosh(cmd)
95
+
96
+    @log_helpers.log_method_call
97
+    def destroy_interface(self, interface):
98
+        cmd = "interface %s destroy\n" % interface
99
+        self._lagosh(cmd)
100
+
101
+    @log_helpers.log_method_call
102
+    def bridge_add_port(self, bridge_name, port_name, ofport):
103
+        cmd = ("bridge %s config -port %s %s\n" %
104
+               (bridge_name, port_name, ofport))
105
+        self._lagosh(cmd)
106
+
107
+    @log_helpers.log_method_call
108
+    def bridge_del_port(self, bridge_name, port_name):
109
+        cmd = "bridge %s config -port -%s\n" % (bridge_name, port_name)
110
+        self._lagosh(cmd)
111
+
112
+    def find_bridge_port(self, port_id, bridge_name=None):
113
+        if port_id.startswith(n_const.TAP_DEVICE_PREFIX):
114
+            port_id = port_id[len(n_const.TAP_DEVICE_PREFIX):]
115
+        bridges = self.show_bridges()
116
+        for bridge in bridges:
117
+            if bridge_name and bridge["name"] != bridge_name:
118
+                continue
119
+            ports = bridge["ports"]
120
+            for port in ports:
121
+                port_name = port[1:]
122
+                if port_name.startswith(port_id):
123
+                    return bridge["name"], port_name
124
+        return None, None

+ 789
- 0
networking_lagopus/agent/lagosh.py View File

@@ -0,0 +1,789 @@
1
+#!/usr/bin/env python
2
+
3
+# Copyright (C) 2014-2015 Nippon Telegraph and Telephone Corporation.
4
+#
5
+# Licensed under the Apache License, Version 2.0 (the "License");
6
+# you may not use this file except in compliance with the License.
7
+# You may obtain a copy of the License at
8
+#
9
+#    http://www.apache.org/licenses/LICENSE-2.0
10
+#
11
+# Unless required by applicable law or agreed to in writing, software
12
+# distributed under the License is distributed on an "AS IS" BASIS,
13
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14
+# implied.
15
+# See the License for the specific language governing permissions and
16
+# limitations under the License.
17
+
18
+#
19
+# lagosh.py - interactive shell for Lagopus vswitch.
20
+# from https://github.com/hibitomo/lago-dsl
21
+#
22
+
23
+import cmd
24
+import getopt
25
+import inspect
26
+import json
27
+import os
28
+import pydoc
29
+import re
30
+import select
31
+import socket
32
+import sys
33
+
34
+
35
+class DSLError(Exception):
36
+
37
+    def __init__(self, value):
38
+        self.value = value
39
+
40
+    def __str__(self):
41
+        return repr(self.value)
42
+
43
+
44
+class dsl(object):
45
+
46
+    def decode_file(self, args):
47
+        if args == []:
48
+            lines = sys.stdin.read().splitlines()
49
+        else:
50
+            lines = open(args[0], 'r').read().splitlines()
51
+        return self.decode(lines)
52
+
53
+    def decode(self, lines):
54
+        result = ''
55
+        topcmd = ''
56
+        for line in lines:
57
+            line = re.sub(r'#.*$', r'', line)
58
+            line = re.sub(r'\n', r'', line)
59
+            line = re.sub(r'^.*(enable|disable)\s*$', r'', line)
60
+            words = line.split()
61
+            if words == []:
62
+                continue
63
+            # top level keywords
64
+            if words[0] != topcmd:
65
+                if topcmd != '':
66
+                    result += '}\n'
67
+                topcmd = words[0]
68
+                result += topcmd + ' {\n'
69
+            words.pop(0)
70
+            # identifier
71
+            if (words[0])[0] != '-':
72
+                result += '\t'
73
+                result += re.sub(r'^:([^:])', r'\1', words.pop(0)) + ' {\n'
74
+                tab = '\t'
75
+            else:
76
+                tab = ''
77
+            # params
78
+            fmt = ''
79
+            while words != []:
80
+                word = re.sub(r'^:([^:])', r'\1', words.pop(0))
81
+                if word == 'create':
82
+                    continue
83
+                if word[0] == '-':
84
+                    word = word.replace('-', '', 1)
85
+                    if fmt != '':
86
+                        result += tab + '\t' + fmt + ';\n'
87
+                    fmt = word
88
+                else:
89
+                    fmt = fmt + ' ' + word
90
+            if fmt != '':
91
+                result += tab + '\t' + fmt + ';\n'
92
+            if tab != '':
93
+                result += tab + '}\n'
94
+        if topcmd != '':
95
+            result += '}'
96
+        return result
97
+
98
+    def encode_file(self, args):
99
+        if args == []:
100
+            lines = sys.stdin.read().splitlines()
101
+        else:
102
+            lines = open(args[0], 'r').read().splitlines()
103
+        return self.encode(lines)
104
+
105
+    def encode(self, lines):
106
+        result = ''
107
+        wordlist = []
108
+        for line in lines:
109
+            line = re.sub(r'#.*$', r'', line)
110
+            line = re.sub(r'\n', r'', line)
111
+            wordlist.append(line.split())
112
+
113
+        prewords = []
114
+        childwords = []
115
+        bridge = []
116
+        encodedpre = 0
117
+        linecount = 0
118
+        for words in wordlist:
119
+            linecount += 1
120
+            for word in words:
121
+                # add to childwords if normal word
122
+                if word != '{' and not re.search(r';$', word) and word != '}':
123
+                    childwords.append(word)
124
+                    continue
125
+                # set (add) prewords(repeated words) if block begin.
126
+                if word == '{':
127
+                    prewords.append(childwords)
128
+                    if len(prewords) == 2:
129
+                        if prewords[0][0] == 'bridge':
130
+                            bridge.append(prewords[1][0])
131
+                        prewords.append(['create'])
132
+                    for decwords in prewords[encodedpre:]:
133
+                        result += self.encode_id(decwords)
134
+                    encodedpre = len(decwords)
135
+                    childwords = []
136
+                    continue
137
+                # unset (remove) prewords if block end.
138
+                if word == '}':
139
+                    result += '\n'
140
+                    try:
141
+                        if len(prewords) == 3:
142
+                            prewords.pop()
143
+                        prewords.pop()
144
+                    except Exception:
145
+                        raise DSLError('line ' + str(linecount) +
146
+                                       ': Unbalanced parenthesis')
147
+                    childwords = []
148
+                    encodedpre = 0
149
+                    continue
150
+                # ;
151
+                childwords.append(word)
152
+                result += self.encode_op(childwords)
153
+                childwords = []
154
+        if prewords != []:
155
+            raise DSLError('line ' + str(linecount) +
156
+                           ': End of input, unbalanced parenthesis')
157
+        for word in bridge:
158
+            result += 'bridge ' + word + ' enable\n'
159
+        return result
160
+
161
+    def encode_id(self, words):
162
+        result = ''
163
+        for word in words:
164
+            result += word + ' '
165
+        return result
166
+
167
+    def encode_op(self, words):
168
+        op = words.pop(0)
169
+        result = '-' + op
170
+        for word in words:
171
+            result += ' ' + word
172
+        return re.sub(r';$', ' ', result)
173
+
174
+
175
+class ds_client(object):
176
+
177
+    port = 12345
178
+
179
+    def remove_namespace(self, arg):
180
+        if isinstance(arg, dict):
181
+            for a in arg.iterkeys():
182
+                if isinstance(arg[a], unicode):
183
+                    arg[a] = re.sub(r'^:([^:])', r'\1', arg[a])
184
+                elif isinstance(arg[a], dict):
185
+                    self.remove_namespace(arg[a])
186
+                elif isinstance(arg[a], list):
187
+                    for l in arg[a]:
188
+                        self.remove_namespace(l)
189
+
190
+    def open(self):
191
+        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
192
+        self.sock.connect(('127.0.0.1', self.port))
193
+
194
+    def close(self):
195
+        del self.sock
196
+
197
+    def write(self, arg):
198
+        self.sock.sendall(arg)
199
+
200
+    def is_readable(self):
201
+        return select.select([self.sock], [], [], 0) == ([self.sock], [], [])
202
+
203
+    def read(self):
204
+        data = ''
205
+        while True:
206
+            res = self.sock.recv(8192)
207
+            data += res
208
+            if not self.is_readable():
209
+                try:
210
+                    jdata = json.loads(data)
211
+                    self.remove_namespace(jdata)
212
+                    break
213
+                except ValueError:
214
+                    continue
215
+        return jdata
216
+
217
+    def request(self, arg):
218
+        self.write(arg)
219
+        jdata = self.read()
220
+        if jdata['ret'] != 'OK':
221
+            if 'file' in jdata and 'line' in jdata:
222
+                raise DSLError(jdata['file'] + ':' + str(jdata['line']) +
223
+                               ': ' + jdata['ret'] + ': ' + jdata['data'])
224
+            else:
225
+                raise DSLError(jdata['ret'] + ': ' + jdata['data'])
226
+        return jdata['data']
227
+
228
+    def lock(self):
229
+        self.request('lock\n')
230
+
231
+    def unlock(self):
232
+        self.request('unlock\n')
233
+
234
+    def call(self, arg, response=True):
235
+        try:
236
+            self.open()
237
+        except Exception:
238
+            raise
239
+        self.write(arg)
240
+        if response:
241
+            jdata = self.read()
242
+            self.close()
243
+        else:
244
+            self.close()
245
+            return
246
+        if jdata['ret'] != 'OK':
247
+            if 'file' in jdata and 'line' in jdata:
248
+                raise DSLError(jdata['file'] + ':' + str(jdata['line']) +
249
+                               ': ' + jdata['ret'] + ': ' + jdata['data'])
250
+            else:
251
+                raise DSLError(jdata['ret'] + ': ' + jdata['data'])
252
+        try:
253
+            return jdata['data']
254
+        except Exception:
255
+            return
256
+
257
+
258
+class Cmd(cmd.Cmd):
259
+
260
+    pager = False
261
+
262
+    def __init__(self, *args, **kwargs):
263
+        cmd.Cmd.__init__(self, *args, **kwargs)
264
+
265
+    def onecmd(self, line):
266
+        try:
267
+            return cmd.Cmd.onecmd(self, line)
268
+        except socket.error:
269
+            print('Socket connection refused.  Lagopus is not running?')
270
+
271
+    def completenames(self, text, *ignored):
272
+        dotext = 'do_' + text
273
+        return [a[3:] for a in self.get_names()
274
+                if a.startswith(dotext) and a != 'do_shell']
275
+
276
+    def precmd(self, line):
277
+        args = line.split()
278
+        line = ''
279
+        cmd = ''
280
+        for a in args:
281
+            begidx = len(line)
282
+            endidx = begidx + len(a) + 1
283
+            if begidx == 0:
284
+                matches = self.completenames(a, line, begidx, endidx)
285
+                if len(matches) == 1:
286
+                    cmd = matches[0]
287
+                else:
288
+                    cmd = a
289
+                line = cmd
290
+            else:
291
+                if cmd == '':
292
+                    compfunc = self.completedefault
293
+                else:
294
+                    try:
295
+                        compfunc = getattr(self, 'complete_' + cmd)
296
+                    except AttributeError:
297
+                        compfunc = self.completedefault
298
+                matches = compfunc(a, line, begidx, endidx)
299
+                if len(matches) == 1:
300
+                    line += ' ' + matches[0]
301
+                else:
302
+                    line += ' ' + a
303
+        return line
304
+
305
+    def emptyline(self):
306
+        return
307
+
308
+    def output(self, line):
309
+        if self.pager:
310
+            pydoc.pager(line)
311
+        else:
312
+            print(line)
313
+
314
+    def cmdloop(self, showpager=False):
315
+        self.pager = showpager
316
+        cmd.Cmd.cmdloop(self)
317
+
318
+    def complete_pager(self, text, line, bigidx, endidx):
319
+        return ['on', 'off']
320
+
321
+    def do_pager(self, line):
322
+        if line == 'on':
323
+            self.pager = True
324
+        elif line == 'off':
325
+            self.pager = False
326
+        elif line == '':
327
+            if self.pager:
328
+                print('pager is on.')
329
+            else:
330
+                print('pager is off.')
331
+        else:
332
+            print('Argument error.')
333
+
334
+    def do_shell(self, line):
335
+        os.system(line)
336
+
337
+    def complete_EOF(self, _text, _line, _begidx, _endidx):
338
+        return []
339
+
340
+    def do_EOF(self, _line):
341
+        return True
342
+
343
+    def do_exit(self, _line):
344
+        return True
345
+
346
+    def do_quit(self, _line):
347
+        return True
348
+
349
+
350
+class Configure(Cmd):
351
+
352
+    def __init__(self, *args, **kwargs):
353
+        self._in_onecmd = False
354
+        self.prompt = 'Configure# '
355
+        Cmd.__init__(self, *args, **kwargs)
356
+
357
+    def do_set(self, line):
358
+        """Add or modify candidate configuration line."""
359
+        print('not implemented yet')
360
+
361
+    def do_unset(self, line):
362
+        """Remove candidate configuration line."""
363
+        print('not implemented yet')
364
+
365
+    def complete_show(self, text, line, bigidx, endidx):
366
+        confdir = os.environ.get('HOME') + '/.lagopus.conf.d'
367
+        params = os.listdir(confdir + '/')
368
+        params.remove('.git')
369
+        return [name for name in params if name.startswith(text)]
370
+
371
+    def do_show(self, line):
372
+        """Show the configuration.
373
+
374
+        usage:
375
+                show
376
+                show file
377
+        """
378
+        confdir = os.environ.get('HOME') + '/.lagopus.conf.d'
379
+        if not os.path.isdir(confdir):
380
+            os.mkdir(confdir)
381
+        args = line.split()
382
+        try:
383
+            conffile = confdir + '/' + args[0]
384
+        except Exception:
385
+            lines = ds_client().call('save\n').splitlines()
386
+            self.output(dsl().decode(lines))
387
+            return
388
+        try:
389
+            self.output(open(conffile).read())
390
+        except Exception:
391
+            print("failed to read " + conffile + "\n")
392
+
393
+    def complete_commit(self, text, line, begidx, endidx):
394
+        confdir = os.environ.get('HOME') + '/.lagopus.conf.d'
395
+        params = os.listdir(confdir + '/')
396
+        params.remove('.git')
397
+        return [name for name in params if name.startswith(text)]
398
+
399
+    def do_commit(self, line):
400
+        """Commit configuration."""
401
+        confdir = os.environ.get('HOME') + '/.lagopus.conf.d'
402
+        args = line.split()
403
+        try:
404
+            if '/' in args[0]:
405
+                conffile = args[0]
406
+            else:
407
+                conffile = confdir + '/' + args[0]
408
+        except Exception:
409
+            conffile = confdir + '/' + 'lagopus.conf'
410
+
411
+        if not os.path.isfile(conffile):
412
+            print('Do not exist configuration')
413
+            return
414
+        # Load candidate config and convert to DSL
415
+        dslfile = conffile + '.dsl'
416
+        f = open(dslfile, 'w')
417
+        try:
418
+            f.write(dsl().encode_file([conffile]))
419
+        except DSLError as e:
420
+            print(e.value)
421
+            f.close()
422
+            return
423
+        f.close()
424
+        # Load DSL.
425
+        ds_client().call('destroy-all-obj\n')
426
+        try:
427
+            ds_client().call('load ' + dslfile + '\n')
428
+        except DSLError as e:
429
+            print(e.value)
430
+
431
+    def do_load(self, line):
432
+        """Load DSL
433
+
434
+        Load configuration file and commit immediately.
435
+        So far, use raw DSL output.
436
+        """
437
+        conffile = '@PREFIX@/etc/lagopus/lagopus.dsl'
438
+        # Load candidate config and convert to DSL
439
+        # Load DSL.
440
+        ds_client().call('destroy-all-obj\n')
441
+        try:
442
+            ds_client().call('load ' + conffile + '\n')
443
+        except DSLError as e:
444
+            print(e.value)
445
+
446
+    def do_save(self, line):
447
+        """Save DSL
448
+
449
+        Load configuration file and commit immediately.
450
+        So far, use raw DSL output.
451
+        """
452
+        conffile = '@PREFIX@/etc/lagopus/lagopus.dsl'
453
+        # Load candidate config and convert to DSL
454
+        # Load DSL.
455
+        ds_client().call('save ' + conffile + '\n')
456
+
457
+    def complete_edit(self, text, line, bigidx, endidx):
458
+        if inspect.currentframe().f_back.f_code.co_name != 'precmd':
459
+            confdir = os.environ.get('HOME') + '/.lagopus.conf.d'
460
+            params = os.listdir(confdir + '/')
461
+            params.remove('.git')
462
+            return [name for name in params if name.startswith(text)]
463
+        else:
464
+            return []
465
+
466
+    def do_edit(self, line):
467
+        """Edit candidate configuration.  Launch text editor."""
468
+        confdir = os.environ.get('HOME') + '/.lagopus.conf.d'
469
+        if not os.path.isdir(confdir):
470
+            os.mkdir(confdir)
471
+        if not os.path.isdir(confdir + '/.git'):
472
+            os.system('cd ' + confdir + '; git init')
473
+        args = line.split()
474
+        try:
475
+            conffile = confdir + '/' + args[0]
476
+        except Exception:
477
+            conffile = confdir + '/' + 'lagopus.conf'
478
+            if not os.path.isfile(conffile):
479
+                # save running DSL if candidate file is not exist.
480
+                lines = ds_client().call('save\n').splitlines()
481
+                f = open(conffile, 'w')
482
+                f.write(dsl().decode(lines))
483
+                f.close()
484
+                # first git commit
485
+                os.system('cd ' + confdir + '; git add ' + conffile)
486
+                os.system('cd ' + confdir +
487
+                          '; git commit --allow-empty-message -m ""')
488
+        # Edit
489
+        editor = os.environ.get('EDITOR', 'vi')
490
+        try:
491
+            st = os.system('cd ' + confdir + ';' + editor + ' ' + conffile)
492
+            if st != 0:
493
+                print('Aborted.')
494
+                return
495
+        except Exception:
496
+            print('Execution failed.')
497
+            return
498
+        # git commit automatically
499
+        os.system('cd ' + confdir + '; git add ' + conffile)
500
+        os.system('cd ' + confdir + '; git commit --allow-empty-message -m ""')
501
+
502
+    def do_ls(self, line):
503
+        """Show configuration file contents."""
504
+        confdir = os.environ.get('HOME') + '/.lagopus.conf.d'
505
+        if not os.path.isdir(confdir):
506
+            os.mkdir(confdir)
507
+        os.system('cd ' + confdir + '; /bin/ls ' + line)
508
+
509
+    def complete_diff(self, text, line, bigidx, endidx):
510
+        confdir = os.environ.get('HOME') + '/.lagopus.conf.d'
511
+        params = os.listdir(confdir + '/')
512
+        params.remove('.git')
513
+        return [name for name in params if name.startswith(text)]
514
+
515
+    def do_diff(self, line):
516
+        """Show differences from previous configuration."""
517
+        confdir = os.environ.get('HOME') + '/.lagopus.conf.d'
518
+        if not os.path.isdir(confdir):
519
+            return
520
+        if not os.path.isdir(confdir + '/.git'):
521
+            return
522
+        args = line.split()
523
+        try:
524
+            conffile = args[-1]
525
+            if len(args) > 0 and os.path.isfile(confdir + '/' + conffile):
526
+                args.pop(-1)
527
+            else:
528
+                raise
529
+        except Exception:
530
+            conffile = 'lagopus.conf'
531
+            if not os.path.isfile(confdir + '/' + conffile):
532
+                return
533
+        if len(args) < 2:
534
+            os.system('cd ' + confdir + '; git diff HEAD^ ' + conffile)
535
+        else:
536
+            os.system('cd ' + confdir + '; git diff ' +
537
+                      ' '.join(map(str, args)) + ' ' + conffile)
538
+
539
+    def do_history(self, line):
540
+        """Show differences from previous configuration."""
541
+        confdir = os.environ.get('HOME') + '/.lagopus.conf.d'
542
+        if not os.path.isdir(confdir):
543
+            return
544
+        if not os.path.isdir(confdir + '/.git'):
545
+            return
546
+        args = line.split()
547
+        try:
548
+            conffile = args[0]
549
+        except Exception:
550
+            conffile = 'lagopus.conf'
551
+            if not os.path.isfile(confdir + '/' + conffile):
552
+                return
553
+        os.system('cd ' + confdir + '; git log ' + conffile)
554
+
555
+
556
+class Topcmd(Cmd):
557
+
558
+    def __init__(self, *args, **kwargs):
559
+        self._in_onecmd = False
560
+        self.prompt = 'Lagosh> '
561
+        Cmd.__init__(self, *args, **kwargs)
562
+
563
+    def complete_configure(self, text, line, begidx, endidx):
564
+        dotext = 'do_' + text
565
+        return [a[3:] for a in Configure().get_names()
566
+                if a.startswith(dotext) and a != 'do_shell']
567
+
568
+    def do_configure(self, line):
569
+        """Manipulate software configuration information."""
570
+        if line == '':
571
+            Configure().cmdloop(showpager=self.pager)
572
+        else:
573
+            Configure().onecmd(line)
574
+
575
+    def complete_show(self, text, line, begidx, endidx):
576
+        params = ['bridge', 'channel', 'controller', 'flow', 'group',
577
+                  'mactable', 'interface', 'meter', 'port', 'route', 'version']
578
+        return [name for name in params if name.startswith(text)]
579
+
580
+    def subcmd_id_merge(self, subcmd, subcmd_id, res, resc):
581
+        gstats = res[subcmd + 's']
582
+        gconfig = resc[subcmd + 's']
583
+        for gs in gstats:
584
+            for gc in gconfig:
585
+                if gs[subcmd + '-id'] == gc[subcmd + '-id']:
586
+                    stats = gs[subcmd_id + '-stats']
587
+                    config = gc[subcmd_id + 's']
588
+                    for b in stats:
589
+                        for c in config:
590
+                            if c[subcmd_id + '-id'] == b[subcmd_id + '-id']:
591
+                                b.update(c)
592
+
593
+    def subcmd_show(self, line, key='bridge', stats='stats', subcmd_id=None):
594
+
595
+        if len(line) == 0:
596
+            print('Argument error')
597
+            return
598
+
599
+        args = line.split()
600
+        subcmd = args[0]
601
+
602
+        if len(args) >= 2:
603
+            req = subcmd + ' ' + args[1] + ' ' + stats + '\n'
604
+            try:
605
+                res = ds_client().call(req)[0]
606
+            except Exception:
607
+                print('invalid keyword: ' + args[1])
608
+                return
609
+            if subcmd_id is not None:
610
+                resc = ds_client().call(subcmd + ' ' + args[1] + '\n')[0]
611
+                self.subcmd_id_merge(subcmd, subcmd_id, res, resc)
612
+            if len(args) == 3:
613
+                try:
614
+                    res = res[args[2]]
615
+                except Exception:
616
+                    print('invalid keyword: ' + args[2])
617
+                    return
618
+            self.output(json.dumps(res, indent=4) + '\n')
619
+        else:
620
+            try:
621
+                odata = []
622
+                data = ds_client().call(key + '\n')
623
+                for ifdata in data:
624
+                    name = ifdata['name']
625
+                    req = subcmd + ' ' + name.encode() + ' ' + stats + '\n'
626
+                    res = ds_client().call(req)[0]
627
+                    if subcmd_id is not None:
628
+                        resc = ds_client().call((subcmd + ' '
629
+                                                 + name.encode() + '\n'))[0]
630
+                        self.subcmd_id_merge(subcmd, subcmd_id, res, resc)
631
+                    try:
632
+                        res['name'] = name
633
+                        res['is-enabled'] = ifdata['is-enabled']
634
+                    except Exception:
635
+                        pass
636
+                    odata.append(res)
637
+                self.output(json.dumps(odata, indent=4) + '\n')
638
+            except TypeError:
639
+                return
640
+
641
+    def do_show(self, line):
642
+        """Show statistics.
643
+
644
+        Usage
645
+                show bridge
646
+                show flow
647
+                show mactable
648
+                show interface
649
+                show table
650
+        """
651
+        if len(line) == 0:
652
+            print('Argument error')
653
+            return
654
+
655
+        args = line.split()
656
+        subcmd = args[0]
657
+
658
+        if subcmd == 'bridge':
659
+            self.subcmd_show(line)
660
+
661
+        elif subcmd == 'group':
662
+            self.subcmd_show(line, 'bridge', 'stats', 'bucket')
663
+
664
+        elif subcmd == 'meter':
665
+            self.subcmd_show(line, 'bridge', 'stats', 'band')
666
+
667
+        elif subcmd == 'flow':
668
+            self.subcmd_show(line, 'bridge', '-with-stats')
669
+
670
+        elif subcmd in ['interface', 'port']:
671
+            self.subcmd_show(line, subcmd)
672
+
673
+        elif subcmd == 'channel':
674
+            data = ds_client().call('channel\n')
675
+            self.output(json.dumps(data, indent=4))
676
+
677
+        elif subcmd == 'controller':
678
+            data = ds_client().call('controller\n')
679
+            self.output(json.dumps(data, indent=4))
680
+
681
+        elif subcmd == 'mactable':
682
+            odata = []
683
+            data = ds_client().call('bridge\n')
684
+            try:
685
+                for ifdata in data:
686
+                    req = 'mactable ' + ifdata['name'].encode() + '\n'
687
+                    res = ds_client().call(req)[0]
688
+                    odata.append(res)
689
+                self.output(json.dumps(odata, indent=4) + '\n')
690
+            except Exception:
691
+                return
692
+
693
+        elif subcmd == 'route':
694
+            data = ds_client().call('route\n')
695
+            self.output(json.dumps(data, indent=4))
696
+
697
+        elif subcmd == 'version':
698
+            data = ds_client().call('version\n')
699
+            self.output(json.dumps(data, indent=4))
700
+
701
+        else:
702
+            print('Argument error')
703
+
704
+    def do_stop(self, line):
705
+        """Stop lagopus process."""
706
+        ds_client().call('shutdown\n', False)
707
+
708
+    def do_log(self, line):
709
+        """Show log settings."""
710
+        data = ds_client().call('log\n')
711
+        self.output(json.dumps(data, indent=4))
712
+
713
+    def do_telnet(self, line):
714
+        """Telnet to another host."""
715
+        if len(line) == 0:
716
+            print('Argument error')
717
+            return
718
+
719
+        args = line.split()
720
+        host = args[0]
721
+        os.system('telnet ' + host)
722
+
723
+    def do_ssh(self, line):
724
+        """Connect to another host with secure shell."""
725
+        if len(line) == 0:
726
+            print('Argument error')
727
+            return
728
+
729
+        args = line.split()
730
+        host = args[0]
731
+        os.system('ssh ' + host)
732
+
733
+    def do_ping(self, line):
734
+        """Ping a remote target."""
735
+        if len(line) == 0:
736
+            print('Argument error')
737
+            return
738
+
739
+        args = line.split()
740
+        host = args[0]
741
+        os.system('ping ' + host)
742
+
743
+    def do_traceroute(self, line):
744
+        """Trace the route to a remote host."""
745
+
746
+        if len(line) == 0:
747
+            print('Argument error')
748
+            return
749
+        args = line.split()
750
+        host = args[0]
751
+        os.system('traceroute ' + host)
752
+
753
+
754
+def usage():
755
+    print("Usage: " + sys.argv[0] + "[--dsl-decode [file]] "
756
+          "[--dsl-encode [file]]")
757
+
758
+
759
+def main():
760
+    Topcmd().cmdloop()
761
+
762
+
763
+if __name__ == "__main__":
764
+    try:
765
+        opts, args = getopt.getopt(sys.argv[1:],
766
+                                   'p:c',
767
+                                   ['dsl-decode=', 'dsl-encode='])
768
+    except getopt.GetoptError:
769
+        usage()
770
+        sys.exit(2)
771
+    for opt, arg in opts:
772
+        if opt == '--dsl-decode':
773
+            print(dsl().decode_file([arg]))
774
+            sys.exit(0)
775
+        elif opt == '--dsl-encode':
776
+            try:
777
+                print(dsl().encode_file([arg]))
778
+            except DSLError as e:
779
+                print(e.value)
780
+            sys.exit(0)
781
+        elif opt == '-p':
782
+            ds_client.port = int(arg)
783
+        elif opt == '-c':
784
+            str = ''
785
+            for word in args:
786
+                str += word + ' '
787
+            Topcmd().onecmd(str)
788
+            sys.exit(0)
789
+    main()

+ 58
- 0
networking_lagopus/agent/rpc.py View File

@@ -0,0 +1,58 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+from oslo_config import cfg
14
+from oslo_log import log as logging
15
+import oslo_messaging
16
+
17
+from neutron.common import rpc as n_rpc
18
+
19
+
20
+LOG = logging.getLogger(__name__)
21
+
22
+
23
+class LagopusAgentApi(object):
24
+    '''Lagopus agent RPC API
25
+
26
+    API version history:
27
+        1.0 - Initial version.
28
+    '''
29
+
30
+    def __init__(self):
31
+        target = oslo_messaging.Target(version='1.0')
32
+        self.client = n_rpc.get_client(target)
33
+
34
+    def _get_context(self, host):
35
+        if host is None:
36
+            host = cfg.CONF.host
37
+        topic = "q-lagopus.%s" % host
38
+        return self.client.prepare(topic=topic)
39
+
40
+    def plug_rawsock(self, context, device, segment, host=None):
41
+        cctxt = self._get_context(host)
42
+        return cctxt.call(context, 'plug_rawsock',
43
+                          device=device, segment=segment)
44
+
45
+    def unplug_rawsock(self, context, device, host=None):
46
+        cctxt = self._get_context(host)
47
+        return cctxt.call(context, 'unplug_rawsock',
48
+                          device=device)
49
+
50
+    def plug_vhost(self, context, port_id, segment, host=None):
51
+        cctxt = self._get_context(host)
52
+        return cctxt.call(context, 'plug_vhost',
53
+                          port_id=port_id, segment=segment)
54
+
55
+    def unplug_vhost(self, context, port_id, host=None):
56
+        cctxt = self._get_context(host)
57
+        return cctxt.call(context, 'unplug_vhost',
58
+                          port_id=port_id)

+ 0
- 0
networking_lagopus/cmd/__init__.py View File


+ 15
- 0
networking_lagopus/cmd/eventlet/__init__.py View File

@@ -0,0 +1,15 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+from neutron.common import eventlet_utils
14
+
15
+eventlet_utils.monkey_patch()

+ 17
- 0
networking_lagopus/cmd/eventlet/lagopus_agent.py View File

@@ -0,0 +1,17 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+from networking_lagopus.ml2.agent import main as lagopus_main
14
+
15
+
16
+def main():
17
+    lagopus_main.main()

+ 0
- 0
networking_lagopus/common/__init__.py View File


+ 41
- 0
networking_lagopus/common/config.py View File

@@ -0,0 +1,41 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+from oslo_config import cfg
14
+
15
+from networking_lagopus._i18n import _
16
+
17
+DEFAULT_BRIDGE_MAPPINGS = []
18
+
19
+lagopus_opts = [
20
+    cfg.BoolOpt('vhost_mode', default=True,
21
+                help=_("Boot virtual machines with vhost_mode ")),
22
+    cfg.ListOpt('bridge_mappings',
23
+                default=DEFAULT_BRIDGE_MAPPINGS,
24
+                help=_("Comma-separated list of "
25
+                       "<physical_network>:<brige> tuples "
26
+                       "mapping physical network names to the agent's "
27
+                       "node-specific physical network interfaces to be used "
28
+                       "for flat and VLAN networks. All physical networks "
29
+                       "listed in network_vlan_ranges on the server should "
30
+                       "have mappings to appropriate interfaces on each "
31
+                       "agent.")),
32
+    cfg.IPOpt('of_listen_address', default='127.0.0.1',
33
+              help=_("Address to listen on for OpenFlow connections. "
34
+                     "Used only for 'native' driver.")),
35
+    cfg.PortOpt('of_listen_port', default=6633,
36
+                help=_("Port to listen on for OpenFlow connections. "
37
+                       "Used only for 'native' driver.")),
38
+]
39
+
40
+
41
+cfg.CONF.register_opts(lagopus_opts, "lagopus")

+ 0
- 0
networking_lagopus/ml2/__init__.py View File


+ 0
- 0
networking_lagopus/ml2/agent/__init__.py View File


+ 476
- 0
networking_lagopus/ml2/agent/lagopus_agent.py View File

@@ -0,0 +1,476 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+import eventlet
14
+import os
15
+import sys
16
+
17
+from neutron_lib import context
18
+from neutron_lib.utils import helpers
19
+from oslo_config import cfg
20
+from oslo_log import helpers as log_helpers
21
+from oslo_log import log as logging
22
+from oslo_service import loopingcall
23
+from oslo_service import service
24
+from osprofiler import profiler
25
+from ryu.app.ofctl import api as ofctl_api
26
+
27
+from neutron.agent import rpc as agent_rpc
28
+from neutron.api.rpc.callbacks import resources
29
+from neutron.common import config as common_config
30
+from neutron.common import rpc as n_rpc
31
+from neutron.common import topics
32
+from neutron.plugins.common import constants as p_constants
33
+
34
+from networking_lagopus.agent import lagopus_lib
35
+from networking_lagopus.common import config  # noqa
36
+
37
+
38
+LOG = logging.getLogger(__name__)
39
+
40
+LAGOPUS_AGENT_BINARY = 'neutron-lagopus-agent'
41
+AGENT_TYPE_LAGOPUS = 'Lagopus agent'
42
+EXTENSION_DRIVER_TYPE = 'lagopus'
43
+LAGOPUS_FS = "/sys/class/net/"
44
+RESOURCE_ID_LENGTH = 11
45
+
46
+OFPP_MAX = 0xffffff00
47
+
48
+
49
+class LagopusBridge(object):
50
+
51
+    def __init__(self, ryu_app, name, dpid, port_data, wait_connection=True):
52
+        LOG.debug("LagopusBridge: %s %s", name, dpid)
53
+        self.ryu_app = ryu_app
54
+        self.name = name
55
+        self.lagopus_client = lagopus_lib.LagopusCommand()
56
+
57
+        self.used_ofport = []
58
+        self.max_ofport = 0
59
+        self.port_mappings = {}
60
+
61
+        if port_data:
62
+            for port, ofport in port_data.items():
63
+                # remove ':'
64
+                port_name = port[1:]
65
+                self.port_mappings[port_name] = ofport
66
+                self.used_ofport.append(ofport)
67
+            if self.used_ofport:
68
+                self.max_ofport = max(self.used_ofport)
69
+
70
+        LOG.debug("used_ofport: %s, max_ofport: %d",
71
+                  self.used_ofport, self.max_ofport)
72
+
73
+        self.dpid = dpid
74
+        self.datapath = self._get_datapath()
75
+        self.install_normal()
76
+        # just for debug
77
+        self.dump_flows()
78
+        return
79
+
80
+    def _get_datapath(self):
81
+        # TODO(hichihara): set timeout
82
+        # NOTE: basically it is OK because lagopus is running
83
+        # and dpid exists at this point. so the call shoud be
84
+        # success.
85
+        while True:
86
+            dp = ofctl_api.get_datapath(self.ryu_app, self.dpid)
87
+            if dp is not None:
88
+                return dp
89
+            # lagopus switch dose not establish connection yet.
90
+            # wait a while
91
+            eventlet.sleep(1)
92
+
93
+    def install_normal(self):
94
+        ofp = self.datapath.ofproto
95
+        ofpp = self.datapath.ofproto_parser
96
+
97
+        actions = [ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0)]
98
+        instructions = [ofpp.OFPInstructionActions(
99
+                        ofp.OFPIT_APPLY_ACTIONS, actions)]
100
+        msg = ofpp.OFPFlowMod(self.datapath,
101
+                              table_id=0,
102
+                              priority=0,
103
+                              instructions=instructions)
104
+        # TODO(hichihara): error handling
105
+        ofctl_api.send_msg(self.ryu_app, msg)
106
+
107
+    def install_vlan(self, vlan_id, port_name):
108
+        ofport = self.port_mappings[port_name]
109
+        ofp = self.datapath.ofproto
110
+        ofpp = self.datapath.ofproto_parser
111
+
112
+        # pipe port -> phys port: push vlan, output:1
113
+        match = ofpp.OFPMatch(in_port=ofport)
114
+        vlan_vid = vlan_id | ofp.OFPVID_PRESENT
115
+        actions = [ofpp.OFPActionPushVlan(),
116
+                   ofpp.OFPActionSetField(vlan_vid=vlan_vid),
117
+                   ofpp.OFPActionOutput(1, 0)]
118
+        instructions = [ofpp.OFPInstructionActions(
119
+                        ofp.OFPIT_APPLY_ACTIONS, actions)]
120
+        msg = ofpp.OFPFlowMod(self.datapath,
121
+                              table_id=0,
122
+                              priority=2,
123
+                              match=match,
124
+                              instructions=instructions)
125
+        # TODO(hichihara): error handling
126
+        ofctl_api.send_msg(self.ryu_app, msg)
127
+
128
+        # phys port -> pipe port: pop vlan, output:<ofport>
129
+        vlan_vid = vlan_id | ofp.OFPVID_PRESENT
130
+        match = ofpp.OFPMatch(in_port=1, vlan_vid=vlan_vid)
131
+        actions = [ofpp.OFPActionPopVlan(),
132
+                   ofpp.OFPActionOutput(ofport, 0)]
133
+        instructions = [ofpp.OFPInstructionActions(
134
+                        ofp.OFPIT_APPLY_ACTIONS, actions)]
135
+        msg = ofpp.OFPFlowMod(self.datapath,
136
+                              table_id=0,
137
+                              priority=2,
138
+                              match=match,
139
+                              instructions=instructions)
140
+        # TODO(hichihara): error handling
141
+        ofctl_api.send_msg(self.ryu_app, msg)
142
+
143
+    def dump_flows(self):
144
+        ofpp = self.datapath.ofproto_parser
145
+        msg = ofpp.OFPFlowStatsRequest(self.datapath)
146
+        reply_cls = ofpp.OFPFlowStatsReply
147
+        # TODO(hichihara): error handling
148
+        result = ofctl_api.send_msg(self.ryu_app, msg, reply_cls=reply_cls,
149
+                                    reply_multi=True)
150
+        LOG.debug("%s flows: %s", self.name, result)
151
+
152
+    def get_ofport(self):
153
+        if self.max_ofport < OFPP_MAX:
154
+            self.max_ofport += 1
155
+            self.used_ofport.append(self.max_ofport)
156
+            return self.max_ofport
157
+        for num in range(1, OFPP_MAX + 1):
158
+            if num not in self.used_ofport:
159
+                self.used_ofport.append(num)
160
+                return num
161
+
162
+    def free_ofport(self, ofport):
163
+        if ofport in self.used_ofport:
164
+            self.used_ofport.remove(ofport)
165
+
166
+    def add_port(self, port_name):
167
+        b, p = self.lagopus_client.find_bridge_port(port_name, self.name)
168
+        if b is not None:
169
+            LOG.debug("port %s is already pluged.", port_name)
170
+            return
171
+
172
+        ofport = self.get_ofport()
173
+        self.port_mappings[port_name] = ofport
174
+        self.lagopus_client.bridge_add_port(self.name, port_name, ofport)
175
+
176
+
177
+@profiler.trace_cls("rpc")
178
+class LagopusManager(object):
179
+
180
+    def __init__(self, ryu_app, bridge_mappings):
181
+        self.lagopus_client = lagopus_lib.LagopusCommand()
182
+        self.bridge_mappings = bridge_mappings
183
+        self.ryu_app = ryu_app
184
+
185
+        raw_bridges = self.lagopus_client.show_bridges()
186
+        if not raw_bridges:
187
+            LOG.error("Lagopus isn't running")
188
+            sys.exit(1)
189
+        LOG.debug("bridges: %s", raw_bridges)
190
+
191
+        self.bridges = {}
192
+        name_to_dpid = {}
193
+        bridge_names = bridge_mappings.values()
194
+        for raw_bridge in raw_bridges:
195
+            name = raw_bridge["name"]
196
+            dpid = raw_bridge["dpid"]
197
+            ports = raw_bridge["ports"]
198
+            self.bridges[dpid] = LagopusBridge(ryu_app, name, dpid, ports)
199
+            if name in bridge_names:
200
+                name_to_dpid[name] = dpid
201
+
202
+        self.phys_to_dpid = {}
203
+        for phys_net, name in bridge_mappings.items():
204
+            if name not in name_to_dpid:
205
+                LOG.error("Bridge %s not found.", name)
206
+                sys.exit(1)
207
+            self.phys_to_dpid[phys_net] = name_to_dpid[name]
208
+        LOG.debug("phys_to_dpid: %s", self.phys_to_dpid)
209
+
210
+        interfaces = self.lagopus_client.show_interfaces()
211
+        ports = self.lagopus_client.show_ports()
212
+        LOG.debug("interfaces: %s", interfaces)
213
+        LOG.debug("ports: %s", ports)
214
+
215
+        # init vhost
216
+        vhost_interfaces = [inter for inter in interfaces
217
+                            if inter["device"].startswith("eth_vhost")]
218
+        self.num_vhost = len(vhost_interfaces)
219
+        used_interfaces = [p["interface"] for p in ports]
220
+        self.used_vhost_id = []
221
+        for inter in vhost_interfaces:
222
+            if inter["name"] in used_interfaces:
223
+                vhost_dev = inter['device'].split(',')[0]
224
+                vhost_id = int(vhost_dev[len("eth_vhost"):])
225
+                self.used_vhost_id.append(vhost_id)
226
+        LOG.debug("num_vhost: %d, used_vhost_id: %s", self.num_vhost,
227
+                  self.used_vhost_id)
228
+
229
+        # init pipe
230
+        pipe_interfaces = [inter for inter in interfaces
231
+                           if inter["device"].startswith("eth_pipe")]
232
+        self.num_pipe = len(pipe_interfaces)
233
+        # TODO(hichihara) pipe interface does not remove now.
234
+
235
+    def get_vhost_interface(self):
236
+        if self.num_vhost == len(self.used_vhost_id):
237
+            # create new vhost interface
238
+            vhost_id = self.num_vhost
239
+            sock_path = "/tmp/sock%d" % vhost_id
240
+            device = "eth_vhost%d,iface=%s" % (vhost_id, sock_path)
241
+            name = "vhost_%d" % vhost_id
242
+            self.lagopus_client.create_vhost_interface(name, device)
243
+            self.num_vhost += 1
244
+            LOG.debug("vhost %d added.", vhost_id)
245
+            os.system("sudo chmod 777 %s" % sock_path)
246
+        else:
247
+            for vhost_id in range(self.num_vhost):
248
+                if vhost_id not in self.used_vhost_id:
249
+                    sock_path = "/tmp/sock%d" % vhost_id
250
+                    name = "vhost_%d" % vhost_id
251
+                    break
252
+        self.used_vhost_id.append(vhost_id)
253
+        return name, sock_path
254
+
255
+    def free_vhost_id(self, vhost_id):
256
+        if vhost_id in self.used_vhost_id:
257
+            self.used_vhost_id.remove(vhost_id)
258
+
259
+    def port_to_vhost_id(self, port_id):
260
+        ports = self.lagopus_client.show_ports()
261
+        for port in ports:
262
+            if port["name"] == port_id:
263
+                interface = port["interface"]
264
+                if interface.startswith("vhost_"):
265
+                    vhost_id = int(interface[len("vhost_"):])
266
+                    return vhost_id
267
+                return
268
+
269
+    def get_pipe(self):
270
+        name0 = "pipe-%d" % self.num_pipe
271
+        name1 = "pipe-%d" % (self.num_pipe + 1)
272
+        device0 = "eth_pipe%d" % self.num_pipe
273
+        device1 = "eth_pipe%d,attach=%s" % (self.num_pipe + 1, device0)
274
+        self.num_pipe += 2
275
+
276
+        self.lagopus_client.create_pipe_interface(name0, device0)
277
+        self.lagopus_client.create_pipe_interface(name1, device1)
278
+
279
+        return name0, name1
280
+
281
+    def get_all_devices(self):
282
+        devices = set()
283
+        ports = self.lagopus_client.show_ports()
284
+        for port in ports:
285
+            devices.add(port["name"])
286
+        LOG.debug("get_all_devices: %s", devices)
287
+        return devices
288
+
289
+    def get_bridge(self, segment):
290
+        vlan_id = (segment['segmentation_id']
291
+                   if segment['network_type'] == p_constants.TYPE_VLAN
292
+                   else 0)
293
+        phys_net = segment['physical_network']
294
+        if phys_net not in self.phys_to_dpid:
295
+            # Error
296
+            return
297
+        dpid = (vlan_id << 48) | self.phys_to_dpid[phys_net]
298
+        LOG.debug("vlan_id %d phys dpid %d", vlan_id,
299
+                  self.phys_to_dpid[phys_net])
300
+        LOG.debug("dpid %d 0x%x", dpid, dpid)
301
+        if dpid in self.bridges:
302
+            return self.bridges[dpid]
303
+
304
+        # bridge for vlan physical_network does not exist.
305
+        # so create the bridge.
306
+        bridge_id = len(self.bridges) + 1
307
+        channel = "channel%d" % bridge_id
308
+        self.lagopus_client.create_channel(channel)
309
+        controller = "controller%d" % bridge_id
310
+        self.lagopus_client.create_controller(controller, channel)
311
+        name = "%s_%d" % (phys_net, vlan_id)
312
+        self.lagopus_client.create_bridge(name, controller, dpid)
313
+
314
+        bridge = LagopusBridge(self.ryu_app, name, dpid, None, False)
315
+        self.bridges[dpid] = bridge
316
+
317
+        pipe1, pipe2 = self.get_pipe()
318
+        port1 = "p-%s" % pipe1
319
+        port2 = "p-%s" % pipe2
320
+        self.lagopus_client.create_port(port1, pipe1)
321
+        self.lagopus_client.create_port(port2, pipe2)
322
+
323
+        phys_bridge = self.bridges[self.phys_to_dpid[phys_net]]
324
+        bridge.add_port(port1)
325
+        phys_bridge.add_port(port2)
326
+
327
+        phys_bridge.install_vlan(vlan_id, port2)
328
+
329
+        return bridge
330
+
331
+    @log_helpers.log_method_call
332
+    def plug_vhost(self, context, **kwargs):
333
+        port_id = kwargs['port_id']
334
+        segment = kwargs['segment']
335
+
336
+        bridge = self.get_bridge(segment)
337
+        if not bridge:
338
+            # raise
339
+            return
340
+
341
+        interface_name, sock_path = self.get_vhost_interface()
342
+        self.lagopus_client.create_port(port_id, interface_name)
343
+        bridge.add_port(port_id)
344
+        return sock_path
345
+
346
+    @log_helpers.log_method_call
347
+    def unplug_vhost(self, context, **kwargs):
348
+        port_id = kwargs['port_id']
349
+        bridge_name, _ = self.lagopus_client.find_bridge_port(port_id)
350
+        if not bridge_name:
351
+            LOG.debug("port %s is already unpluged.", port_id)
352
+            return
353
+        self.lagopus_client.bridge_del_port(bridge_name, port_id)
354
+        vhost_id = self.port_to_vhost_id(port_id)
355
+        self.lagopus_client.destroy_port(port_id)
356
+        if vhost_id:
357
+            self.free_vhost_id(vhost_id)
358
+
359
+    @log_helpers.log_method_call
360
+    def plug_rawsock(self, context, **kwargs):
361
+        device = kwargs['device']
362
+        segment = kwargs['segment']
363
+
364
+        if segment is None:
365
+            LOG.debug("no segment. port may not exist.")
366
+            return
367
+
368
+        bridge = self.get_bridge(segment)
369
+        if not bridge:
370
+            return
371
+
372
+        interface_name = 'i' + device
373
+        port_name = 'p' + device
374
+        self.lagopus_client.create_rawsock_interface(interface_name, device)
375
+        self.lagopus_client.create_port(port_name, interface_name)
376
+        bridge.add_port(port_name)
377
+
378
+        return True
379
+
380
+    @log_helpers.log_method_call
381
+    def unplug_rawsock(self, context, **kwargs):
382
+        device = kwargs['device']
383
+        interface_name = 'i' + device
384
+        port_name = 'p' + device
385
+
386
+        bridge_name, _ = self.lagopus_client.find_bridge_port(port_name)
387
+        if not bridge_name:
388
+            LOG.debug("device %s is already unpluged.", device)
389
+            return
390
+
391
+        self.lagopus_client.bridge_del_port(bridge_name, port_name)
392
+        self.lagopus_client.destroy_port(port_name)
393
+        self.lagopus_client.destroy_interface(interface_name)
394
+
395
+
396
+class LagopusAgent(service.Service):
397
+
398
+    def __init__(self, ryu_app, bridge_mappings, report_interval,
399
+                 quitting_rpc_timeout, agent_type, agent_binary):
400
+        super(LagopusAgent, self).__init__()
401
+        self.ryu_app = ryu_app
402
+        self.bridge_mappings = bridge_mappings
403
+        self.report_interval = report_interval
404
+        self.quitting_rpc_timeout = quitting_rpc_timeout
405
+        self.agent_type = agent_type
406
+        self.agent_binary = agent_binary
407
+        self.state_rpc = agent_rpc.PluginReportStateAPI(topics.REPORTS)
408
+
409
+    def start(self):
410
+        self.context = context.get_admin_context_without_session()
411
+        self.manager = LagopusManager(self.ryu_app, self.bridge_mappings)
412
+        self.connection = n_rpc.create_connection()
413
+        self.connection.create_consumer("q-lagopus", [self.manager])
414
+
415
+        configurations = {'bridge_mappings': self.bridge_mappings}
416
+
417
+        self.agent_state = {
418
+            'binary': self.agent_binary,
419
+            'host': cfg.CONF.host,
420
+            'topic': "q-lagopus",
421
+            'configurations': configurations,
422
+            'agent_type': self.agent_type,
423
+            'resource_versions': resources.LOCAL_RESOURCE_VERSIONS,
424
+            'start_flag': True}
425
+
426
+        if self.report_interval:
427
+            heartbeat = loopingcall.FixedIntervalLoopingCall(
428
+                self._report_state)
429
+            heartbeat.start(interval=self.report_interval)
430
+
431
+        LOG.info("Agent initialized successfully, now running... ")
432
+
433
+        self.connection.consume_in_threads()
434
+
435
+    def _report_state(self):
436
+        try:
437
+            devices = len(self.manager.get_all_devices())
438
+            self.agent_state.get('configurations')['devices'] = devices
439
+            # we only want to update resource versions on startup
440
+            self.agent_state.pop('resource_versions', None)
441
+            self.agent_state.pop('start_flag', None)
442
+        except Exception:
443
+            LOG.exception("Failed reporting state!")
444
+
445
+    def stop(self, graceful=True):
446
+        LOG.info("Stopping %s agent.", self.agent_type)
447
+        if graceful and self.quitting_rpc_timeout:
448
+            self.state_rpc.client.timeout = self.quitting_rpc_timeout
449
+        super(LagopusAgent, self).stop(graceful)
450
+
451
+    def reset(self):
452
+        common_config.setup_logging()
453
+
454
+
455
+def parse_bridge_mappings():
456
+    try:
457
+        bridge_mappings = helpers.parse_mappings(
458
+            cfg.CONF.lagopus.bridge_mappings)
459
+        LOG.info("Bridge mappings: %s", bridge_mappings)
460
+        return bridge_mappings
461
+    except ValueError as e:
462
+        LOG.error("Parsing bridge_mappings failed: %s. "
463
+                  "Agent terminated!", e)
464
+        sys.exit(1)
465
+
466
+
467
+def main(ryu_app):
468
+    bridge_mappings = parse_bridge_mappings()
469
+    report_interval = cfg.CONF.AGENT.report_interval
470
+    quitting_rpc_timeout = cfg.CONF.AGENT.quitting_rpc_timeout
471
+    agent = LagopusAgent(ryu_app, bridge_mappings, report_interval,
472
+                         quitting_rpc_timeout,
473
+                         AGENT_TYPE_LAGOPUS,
474
+                         LAGOPUS_AGENT_BINARY)
475
+    launcher = service.launch(cfg.CONF, agent)
476
+    launcher.wait()

+ 47
- 0
networking_lagopus/ml2/agent/lagopus_ryuapp.py View File

@@ -0,0 +1,47 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+from oslo_log import log as logging
14
+from oslo_utils import excutils
15
+import ryu.app.ofctl.api  # noqa
16
+from ryu.base import app_manager
17
+from ryu.lib import hub
18
+from ryu.ofproto import ofproto_v1_3
19
+
20
+from networking_lagopus.ml2.agent import lagopus_agent
21
+
22
+
23
+LOG = logging.getLogger(__name__)
24
+
25
+
26
+def agent_main_wrapper(ryu_app):
27
+    try:
28
+        lagopus_agent.main(ryu_app)
29
+    except Exception:
30
+        with excutils.save_and_reraise_exception():
31
+            LOG.exception("Agent main thread died of an exception")
32
+    finally:
33
+        # The following call terminates Ryu's AppManager.run_apps(),
34
+        # which is needed for clean shutdown of an agent process.
35
+        # The close() call must be called in another thread, otherwise
36
+        # it suicides and ends prematurely.
37
+        hub.spawn(app_manager.AppManager.get_instance().close)
38
+
39
+
40
+class LagopusAgentRyuApp(app_manager.RyuApp):
41
+    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
42
+
43
+    def start(self):
44
+        # Start Ryu event loop thread
45
+        super(LagopusAgentRyuApp, self).start()
46
+        self.threads.append(hub.spawn(agent_main_wrapper, self,
47
+                                      raise_error=True))

+ 48
- 0
networking_lagopus/ml2/agent/main.py View File

@@ -0,0 +1,48 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+import logging
14
+import sys
15
+
16
+from oslo_config import cfg
17
+from ryu.base import app_manager
18
+from ryu import cfg as ryu_cfg
19
+
20
+from neutron.common import config as common_config
21
+from neutron.common import profiler
22
+
23
+from networking_lagopus.common import config  # noqa
24
+
25
+
26
+LAGOPUS_AGENT_BINARY = 'neutron-lagopus-agent'
27
+
28
+
29
+def init_ryu_config():
30
+    ryu_cfg.CONF(project='ryu', args=[])
31
+    ryu_cfg.CONF.ofp_listen_host = cfg.CONF.lagopus.of_listen_address
32
+    ryu_cfg.CONF.ofp_tcp_listen_port = cfg.CONF.lagopus.of_listen_port
33
+
34
+    # enable Debug log for ryu modules
35
+    # why it is necessary see neutron/common/config.py
36
+    for mod in ('ryu.base.app_manager', 'ryu.controller.controller'):
37
+        logger = logging.getLogger(mod)
38
+        logger.setLevel(logging.DEBUG)
39
+
40
+
41
+def main():
42
+    common_config.init(sys.argv[1:])
43
+    common_config.setup_logging()
44
+    init_ryu_config()
45
+    profiler.setup(LAGOPUS_AGENT_BINARY, cfg.CONF.host)
46
+    app_manager.AppManager.run_apps([
47
+        'networking_lagopus.ml2.agent.lagopus_ryuapp'
48
+    ])

+ 0
- 0
networking_lagopus/ml2/mech_driver/__init__.py View File


+ 83
- 0
networking_lagopus/ml2/mech_driver/mech_lagopus.py View File

@@ -0,0 +1,83 @@
1
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
2
+#    not use this file except in compliance with the License. You may obtain
3
+#    a copy of the License at
4
+#
5
+#         http://www.apache.org/licenses/LICENSE-2.0
6
+#
7
+#    Unless required by applicable law or agreed to in writing, software
8
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+#    License for the specific language governing permissions and limitations
11
+#    under the License.
12
+
13
+from neutron_lib.api.definitions import portbindings
14
+from neutron_lib import constants
15
+from neutron_lib import context as n_context
16
+from oslo_log import helpers as log_helpers
17
+from oslo_log import log as logging
18
+
19
+from neutron.plugins.common import constants as p_constants
20
+from neutron.plugins.ml2 import driver_api as api
21
+from neutron.plugins.ml2.drivers import mech_agent
22
+
23
+from networking_lagopus.agent import rpc
24
+
25
+
26
+LOG = logging.getLogger(__name__)
27
+AGENT_TYPE_LAGOPUS = 'Lagopus agent'
28
+
29
+
30
+class LagopusMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
31
+
32
+    @log_helpers.log_method_call
33
+    def __init__(self):
34
+        super(LagopusMechanismDriver, self).__init__(
35
+            AGENT_TYPE_LAGOPUS, portbindings.VIF_TYPE_BRIDGE,
36
+            {portbindings.CAP_PORT_FILTER: False})
37
+        self.context = n_context.get_admin_context_without_session()
38
+        self.lagopus_api = rpc.LagopusAgentApi()
39
+
40
+    def get_allowed_network_types(self, agent=None):
41
+        return [p_constants.TYPE_FLAT, p_constants.TYPE_VLAN]
42
+
43
+    def get_mappings(self, agent):
44
+        return agent['configurations'].get('bridge_mappings', {})
45
+
46
+    def try_to_bind_segment_for_agent(self, context, segment, agent):
47
+        if self.check_segment_for_agent(segment, agent):
48
+            vif_type = self.vif_type
49
+            vif_details = dict(self.vif_details)
50
+
51
+            if (context.current['device_owner'].
52
+                    startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX)):
53
+                # use vhostuser for VM
54
+                vif_type = portbindings.VIF_TYPE_VHOST_USER
55
+                vif_details[portbindings.VHOST_USER_MODE] = (
56
+                    portbindings.VHOST_USER_MODE_CLIENT)
57
+
58
+                sock_path = self.lagopus_api.plug_vhost(
59
+                    self.context, context.current['id'],
60
+                    segment, context._binding.host)
61
+                vif_details[portbindings.VHOST_USER_SOCKET] = sock_path
62
+
63
+            context.set_binding(segment[api.ID], vif_type, vif_details,
64
+                                status=constants.PORT_STATUS_ACTIVE)
65
+            return True
66
+        else:
67
+            return False
68
+
69
+    @log_helpers.log_method_call
70
+    def update_port_postcommit(self, context):
71
+        if (context.original_host
72
+                and context.original_vif_type == 'vhostuser'
73
+                and not context.host and context.vif_type == 'unbound'):
74
+            self.lagopus_api.unplug_vhost(self.context,
75
+                                          context.current['id'],
76
+                                          context.original_host)
77
+
78
+    @log_helpers.log_method_call
79
+    def delete_port_postcommit(self, context):
80
+        if context.host and context.vif_type == 'vhostuser':
81
+            self.lagopus_api.unplug_vhost(self.context,
82
+                                          context.current['id'],
83
+                                          context.host)

+ 11
- 0
setup.cfg View File

@@ -22,6 +22,17 @@ classifier =
22 22
 [files]
23 23
 packages =
24 24
     networking_lagopus
25
+data_files =
26
+    etc/neutron/rootwrap.d =
27
+        etc/neutron/rootwrap.d/lagopus.filters
28
+
29
+[entry_points]
30
+console_scripts =
31
+    neutron-lagopus-agent = networking_lagopus.cmd.eventlet.lagopus_agent:main
32
+neutron.ml2.mechanism_drivers =
33
+    lagopus = networking_lagopus.ml2.mech_driver.mech_lagopus:LagopusMechanismDriver
34
+neutron.interface_drivers =
35
+    lagopus = networking_lagopus.agent.interface:LagopusInterfaceDriver
25 36
 
26 37
 [build_sphinx]
27 38
 all-files = 1

Loading…
Cancel
Save