conlink 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +5 -0
- package/Dockerfile +34 -0
- package/LICENSE +373 -0
- package/README.md +485 -0
- package/TODO +34 -0
- package/conlink +11 -0
- package/conlink-start.sh +172 -0
- package/examples/dot.js +36 -0
- package/examples/index.html +11 -0
- package/examples/net2dot.yaml +21 -0
- package/examples/test1-compose.yaml +60 -0
- package/examples/test2-compose.yaml +31 -0
- package/examples/test2-network.yaml +5 -0
- package/examples/test3-network.yaml +5 -0
- package/examples/test4-multiple/all-compose.yaml +5 -0
- package/examples/test4-multiple/base-compose.yaml +25 -0
- package/examples/test4-multiple/node1-compose.yaml +17 -0
- package/examples/test4-multiple/nodes2-compose.yaml +20 -0
- package/examples/test4-multiple/web-network.yaml +2 -0
- package/examples/test5-geneve-compose.yaml +31 -0
- package/examples/test6-cfn.yaml +184 -0
- package/examples/test7-compose.yaml +31 -0
- package/examples/test8-compose.yaml +35 -0
- package/host-build.yaml +1 -0
- package/inspect.json +210 -0
- package/link-add.sh +197 -0
- package/link-del.sh +60 -0
- package/net2dot +11 -0
- package/notes.txt +82 -0
- package/old/Dockerfile.bak +26 -0
- package/old/add-link.sh +82 -0
- package/old/conlink +12 -0
- package/old/conlink.cljs +131 -0
- package/old/dot_gitignore +1 -0
- package/old/examples/test2-compose.yaml +32 -0
- package/old/examples/test2-network.yaml +42 -0
- package/old/move-link.sh +108 -0
- package/old/net2dot.py +122 -0
- package/old/notes-old.txt +97 -0
- package/old/package.json +16 -0
- package/old/schema.yaml +138 -0
- package/old/schema.yaml.bak +76 -0
- package/old/test2b-compose.yaml +18 -0
- package/old/veth-link.sh +96 -0
- package/package.json +15 -0
- package/schema-ish.yaml +29 -0
- package/schema.yaml +71 -0
- package/shadow-cljs.edn +33 -0
- package/src/conlink/addrs.cljc +63 -0
- package/src/conlink/core.cljs +772 -0
- package/src/conlink/net2dot.cljs +158 -0
- package/src/conlink/util.cljs +140 -0
- package/tests/invalid-schema-1.yaml +6 -0
- package/tests/invalid-schema-2.yaml +6 -0
- package/tests/invalid-schema-3.yaml +17 -0
- package/tests/invalid-schema-4.yaml +14 -0
- package/tests/invalid-schema-5.yaml +12 -0
- package/tests/invalid-schema-6.yaml +12 -0
- package/tmp/conlink/.env +1 -0
package/old/conlink.cljs
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env nbb
|
|
2
|
+
|
|
3
|
+
(ns conlink
|
|
4
|
+
(:require [clojure.string :as S]
|
|
5
|
+
[promesa.core :as P]
|
|
6
|
+
[cljs-bean.core :refer [->clj]]
|
|
7
|
+
[conlink.util :refer [parse-opts Eprintln Epprint
|
|
8
|
+
fatal spawn exec read-file]]
|
|
9
|
+
["yaml$default" :as yaml]
|
|
10
|
+
["dockerode$default" :as Docker]))
|
|
11
|
+
|
|
12
|
+
(def usage "
|
|
13
|
+
conlink: advanced container linking (networking).
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
conlink [options]
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
-v, --verbose Show verbose output (stderr)
|
|
20
|
+
[env: VERBOSE]
|
|
21
|
+
--project PROJECT Docker compose project name
|
|
22
|
+
[env: COMPOSE_PROJECT_NAME]
|
|
23
|
+
--network-file NETWORK-FILE... Network configuration file
|
|
24
|
+
--compose-file COMPOSE-FILE... Docker compose file
|
|
25
|
+
")
|
|
26
|
+
|
|
27
|
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
28
|
+
|
|
29
|
+
(defn json-str [obj]
|
|
30
|
+
(js/JSON.stringify (clj->js obj)))
|
|
31
|
+
|
|
32
|
+
(defn mangle-network-config
|
|
33
|
+
"
|
|
34
|
+
- Rewrite network config to use actual container names instead of
|
|
35
|
+
the convenience aliases.
|
|
36
|
+
- Prune links, interfaces, and commands based on enabled service
|
|
37
|
+
profiles.
|
|
38
|
+
"
|
|
39
|
+
[net-cfg prefix]
|
|
40
|
+
(let [links (reduce
|
|
41
|
+
(fn [links {:keys [left right] :as link}]
|
|
42
|
+
(let [lname (str prefix (:container left))
|
|
43
|
+
rname (str prefix (:container right))]
|
|
44
|
+
(conj links
|
|
45
|
+
{:left (assoc left :container lname)
|
|
46
|
+
:right (assoc right :container rname)})))
|
|
47
|
+
[] (:links net-cfg))]
|
|
48
|
+
(assoc net-cfg :links links)))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
(defn init-containers-state [net-cfg]
|
|
52
|
+
(let [state (into {} (for [l (:links net-cfg)
|
|
53
|
+
c [(:left l) (:right l)]]
|
|
54
|
+
[(:container c) {:state :unconnected :links []}]))]
|
|
55
|
+
(reduce
|
|
56
|
+
(fn [cfg {:keys [left right] :as link}]
|
|
57
|
+
(let [lfn (fn [me you] {:self (dissoc (me link) :container)
|
|
58
|
+
:remote (you link)})]
|
|
59
|
+
(-> cfg
|
|
60
|
+
(update-in [(:container left) :links] conj (lfn :left :right))
|
|
61
|
+
(update-in [(:container right) :links] conj (lfn :right :left)))))
|
|
62
|
+
state
|
|
63
|
+
(:links net-cfg)))
|
|
64
|
+
|
|
65
|
+
(defn handle-container [{:keys [client project config state] :as ctx} cid]
|
|
66
|
+
(P/let [container (.getContainer client cid)
|
|
67
|
+
details (P/-> container .inspect ->clj)
|
|
68
|
+
{Name :Name {Labels :Labels} :Config} details]
|
|
69
|
+
(Eprintln :handle-container :cid cid)
|
|
70
|
+
#_(Epprint details)
|
|
71
|
+
(Epprint (get @state Name))))
|
|
72
|
+
|
|
73
|
+
(P/let
|
|
74
|
+
[cfg (parse-opts usage *command-line-args*)
|
|
75
|
+
_ (when (empty? cfg) (js/process.exit 2))
|
|
76
|
+
{:keys [verbose project network-file compose-file]} cfg
|
|
77
|
+
_ (when verbose (Eprintln "Settings:"))
|
|
78
|
+
_ (when verbose (Epprint cfg))
|
|
79
|
+
_ (when (not (or network-file compose-file))
|
|
80
|
+
(fatal 2 "either --network-file or --compose-file is required"))
|
|
81
|
+
|
|
82
|
+
raw-network-config (P/-> (first network-file)
|
|
83
|
+
(read-file "utf8")
|
|
84
|
+
yaml/parse
|
|
85
|
+
->clj)
|
|
86
|
+
docker (Docker. #js {:socketPath "/var/run/docker.sock"})
|
|
87
|
+
|
|
88
|
+
_ (Eprintln "raw-network-config:")
|
|
89
|
+
_ (Epprint raw-network-config)
|
|
90
|
+
prefix (if project (str "/" project "_") "/")
|
|
91
|
+
network-config (mangle-network-config raw-network-config prefix)
|
|
92
|
+
_ (Eprintln "network-config:")
|
|
93
|
+
_ (Epprint network-config)
|
|
94
|
+
state (init-containers-state network-config)
|
|
95
|
+
_ (Eprintln "state:")
|
|
96
|
+
_ (Epprint state)
|
|
97
|
+
|
|
98
|
+
ctx {:client docker
|
|
99
|
+
:project project
|
|
100
|
+
:config network-config
|
|
101
|
+
:state (atom state)}
|
|
102
|
+
label-filters (if project
|
|
103
|
+
{"label" [(str "com.docker.compose.project=" project)]}
|
|
104
|
+
{})
|
|
105
|
+
ev-filters (merge label-filters
|
|
106
|
+
{"event" ["start"]})
|
|
107
|
+
ev-stream (.getEvents docker #js {:filters (json-str ev-filters)})
|
|
108
|
+
_ (.on ev-stream "data" (fn [ev]
|
|
109
|
+
(let [event (-> ev js/JSON.parse ->clj)]
|
|
110
|
+
(prn :event event)
|
|
111
|
+
(handle-container ctx (:id event)))))
|
|
112
|
+
|
|
113
|
+
_ (Eprintln "Handling already running containers")
|
|
114
|
+
containers (P/-> docker
|
|
115
|
+
(.listContainers #js {:filters (json-str label-filters)
|
|
116
|
+
:sparse true})
|
|
117
|
+
->clj)
|
|
118
|
+
#_#__ (Epprint containers)
|
|
119
|
+
_ (doseq [c containers] (handle-container ctx (:Id c)))
|
|
120
|
+
|
|
121
|
+
#_#_res (P/all [(spawn "sleep 1")
|
|
122
|
+
(spawn "ip link show")
|
|
123
|
+
(spawn "ip addr show")
|
|
124
|
+
(spawn "ip route show")])
|
|
125
|
+
#_#__ (prn :res res)
|
|
126
|
+
]
|
|
127
|
+
(prn :here1)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.swp
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# A docker-compose file with an external network configuration file
|
|
2
|
+
# and two docker containers that are connected via a switch. In
|
|
3
|
+
# addition an "internet" host is setup in the network container that
|
|
4
|
+
# is also connected to the switch and is listening on 8.8.8.8.
|
|
5
|
+
|
|
6
|
+
version: "2.4"
|
|
7
|
+
|
|
8
|
+
services:
|
|
9
|
+
network:
|
|
10
|
+
build: {context: ../}
|
|
11
|
+
image: conlink.cljs
|
|
12
|
+
pid: host
|
|
13
|
+
network_mode: none
|
|
14
|
+
cap_add: [SYS_ADMIN, NET_ADMIN, SYS_NICE, NET_BROADCAST, IPC_LOCK]
|
|
15
|
+
security_opt: [ 'apparmor:unconfined' ] # needed on Ubuntu 18.04
|
|
16
|
+
volumes:
|
|
17
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
18
|
+
- /var/lib/docker:/var/lib/docker
|
|
19
|
+
- ./:/test
|
|
20
|
+
command: /sbin/conlink -v --project ${COMPOSE_PROJECT_NAME:?required} --compose-file /test/test2-compose.yaml --network-file /test/test2-network.yaml
|
|
21
|
+
|
|
22
|
+
node1:
|
|
23
|
+
image: alpine
|
|
24
|
+
cap_add: [NET_ADMIN]
|
|
25
|
+
network_mode: none
|
|
26
|
+
command: sleep 864000
|
|
27
|
+
|
|
28
|
+
node2:
|
|
29
|
+
image: alpine
|
|
30
|
+
cap_add: [NET_ADMIN]
|
|
31
|
+
network_mode: none
|
|
32
|
+
command: sh -c 'while ! ip link show eth0 up; do sleep 1; done; sleep 864000'
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# "Physical" network interface definitions.
|
|
2
|
+
# This defines container to container links.
|
|
3
|
+
links:
|
|
4
|
+
- left: {container: node1_1, intf: eth0, ip: 10.0.1.1/16}
|
|
5
|
+
right: {container: network_1, intf: node1-eth0}
|
|
6
|
+
- left: {container: node2_1, intf: eth0, ip: 10.0.1.2/16}
|
|
7
|
+
right: {container: network_1, intf: node2-eth0}
|
|
8
|
+
|
|
9
|
+
# Commands to run in containers after they are connected
|
|
10
|
+
commands:
|
|
11
|
+
- container: node1_1
|
|
12
|
+
command: ip route add default via 10.0.0.1
|
|
13
|
+
- container: node2_1
|
|
14
|
+
command: ip route add default via 10.0.0.1
|
|
15
|
+
|
|
16
|
+
# Network container mininet configuration
|
|
17
|
+
# This configuration is used by config_mininet.py inside the network
|
|
18
|
+
# container to define complex network elements (routers, switches,
|
|
19
|
+
# etc).
|
|
20
|
+
mininet-cfg:
|
|
21
|
+
switches:
|
|
22
|
+
- name: s1 # the switch between node1, node2, and internet
|
|
23
|
+
hosts:
|
|
24
|
+
- name: internet
|
|
25
|
+
opts: {ip: 10.0.0.1/16}
|
|
26
|
+
interfaces:
|
|
27
|
+
- name: node1-eth0
|
|
28
|
+
node: s1
|
|
29
|
+
- name: node2-eth0
|
|
30
|
+
node: s1
|
|
31
|
+
links:
|
|
32
|
+
- left: internet
|
|
33
|
+
right: s1
|
|
34
|
+
|
|
35
|
+
commands:
|
|
36
|
+
# In "internet" listen to 8.8.8.* addresses.
|
|
37
|
+
- node: internet
|
|
38
|
+
sync: true
|
|
39
|
+
command:
|
|
40
|
+
- ip link add ${DNS_INTF:-gdns} type dummy
|
|
41
|
+
- ip link set ${DNS_INTF:-gdns} up
|
|
42
|
+
- ip addr add 8.8.8.8/24 dev ${DNS_INTF:-gdns}
|
package/old/move-link.sh
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2023, Viasat, Inc
|
|
4
|
+
# Licensed under MPL 2.0
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
usage () {
|
|
9
|
+
echo >&2 "${0} [OPTIONS] TYPE INTF0 INTF1 PID0 PID1"
|
|
10
|
+
echo >&2 ""
|
|
11
|
+
echo >&2 " TYPE: one of the following values: move, vlan, macvlan,"
|
|
12
|
+
echo >&2 " macvtap, ipvlan, or ipvtap. If TYPE is 'move'"
|
|
13
|
+
echo >&2 " then INTF0 will be moved directly. For any other"
|
|
14
|
+
echo >&2 " TYPE a sub-interface of TYPE will be created on"
|
|
15
|
+
echo >&2 " INTF0 and the sub-interface virtual link will be"
|
|
16
|
+
echo >&2 " moved instead."
|
|
17
|
+
echo >&2 " INTF0: the interface in PID0 to move to PID1"
|
|
18
|
+
echo >&2 " INTF1: the interface in PID1 after moving"
|
|
19
|
+
echo >&2 " PID0: the process ID of the first namespace"
|
|
20
|
+
echo >&2 " PID1: the process ID of the second namespace"
|
|
21
|
+
echo >&2 ""
|
|
22
|
+
echo >&2 "OPTIONS:"
|
|
23
|
+
echo >&2 " --verbose - Verbose output (set -x)"
|
|
24
|
+
echo >&2 " --mode MODE - Mode setting for *vlan, *vtap TYPEs"
|
|
25
|
+
echo >&2 " --vlanid VLANID - VLAN ID for vlan TYPE"
|
|
26
|
+
echo >&2 " --ip IP - Add IP (CIDR) to the moved interface"
|
|
27
|
+
echo >&2 " --route 'ROUTE' - route to add to the moved interface"
|
|
28
|
+
echo >&2 " --nat TARGET - Stateless NAT traffic to/from TARGET"
|
|
29
|
+
exit 2
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
IPTABLES() {
|
|
33
|
+
local ns=${1}; shift
|
|
34
|
+
ip netns exec ${ns} iptables -D "${@}" 2>/dev/null || true
|
|
35
|
+
ip netns exec ${ns} iptables -I "${@}"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
VERBOSE=${VERBOSE:-}
|
|
39
|
+
MODE= VLANID= IP= ROUTE= TARGET=
|
|
40
|
+
|
|
41
|
+
info() { echo "move-link [${PID0}/${IF0} ->> ${PID1}/${IF1}] ${*}"; }
|
|
42
|
+
warn() { >&2 echo "move-link [${PID0}/${IF0} ->> ${PID1}/${IF1}] ${*}"; }
|
|
43
|
+
die() { warn "ERROR: ${*}"; exit 1; }
|
|
44
|
+
|
|
45
|
+
# Parse arguments
|
|
46
|
+
positional=
|
|
47
|
+
while [ "${*}" ]; do
|
|
48
|
+
param=$1; OPTARG=$2
|
|
49
|
+
case ${param} in
|
|
50
|
+
--verbose) VERBOSE=1 ;;
|
|
51
|
+
--mode) MODE="${OPTARG}"; shift ;;
|
|
52
|
+
--vlanid) VLANID="${OPTARG}"; shift ;;
|
|
53
|
+
--ip) IP="${OPTARG}"; shift ;;
|
|
54
|
+
--route) ROUTE="${OPTARG}"; shift ;;
|
|
55
|
+
--nat) TARGET="${OPTARG}"; shift ;;
|
|
56
|
+
-h|--help) usage ;;
|
|
57
|
+
*) positional="${positional} $1" ;;
|
|
58
|
+
esac
|
|
59
|
+
shift
|
|
60
|
+
done
|
|
61
|
+
set -- ${positional}
|
|
62
|
+
|
|
63
|
+
TYPE=$1 IF0=$2 IF1=$3 PID0=$4 PID1=$5 NS0=ns${PID0} NS1=ns${PID1}
|
|
64
|
+
|
|
65
|
+
[ "${VERBOSE}" ] && set -x || true
|
|
66
|
+
|
|
67
|
+
# Check arguments
|
|
68
|
+
[ "${$#}" -lt 5 ] && usage
|
|
69
|
+
[ "${TARGET}" -a -z "${IP}" ] && die "--nat requires --ip"
|
|
70
|
+
|
|
71
|
+
# Sanity checks
|
|
72
|
+
[ ! -d /proc/$PID0 ] && die "PID0 $PID0 is no longer running!"
|
|
73
|
+
[ ! -d /proc/$PID1 ] && die "PID1 $PID1 is no longer running!"
|
|
74
|
+
|
|
75
|
+
export PATH=$PATH:/usr/sbin
|
|
76
|
+
mkdir -p /var/run/netns
|
|
77
|
+
ln -sf /proc/${PID0}/ns/net /var/run/netns/${NS0}
|
|
78
|
+
ln -sf /proc/${PID1}/ns/net /var/run/netns/${NS1}
|
|
79
|
+
|
|
80
|
+
info "Moving ${TYPE} link (${IP})"
|
|
81
|
+
|
|
82
|
+
case "${TYPE}" in
|
|
83
|
+
macvlan|macvtap|ipvlan|ipvtap|vlan)
|
|
84
|
+
ip -netns ${NS0} link add link ${IF0} name tmp$$ type ${TYPE} \
|
|
85
|
+
${MODE:+mode ${MODE}} ${VLANID:+id ${VLANID}}
|
|
86
|
+
ip -netns ${NS0} link set tmp$$ netns ${NS1} name ${IF1}
|
|
87
|
+
;;
|
|
88
|
+
move)
|
|
89
|
+
ip -netns ${NS0} link set ${IF0} netns ${NS1} name ${IF1}
|
|
90
|
+
;;
|
|
91
|
+
esac
|
|
92
|
+
|
|
93
|
+
info "Setting IP, ROUTE, and up state"
|
|
94
|
+
ip -netns ${NS1} --force -b - <<EOF
|
|
95
|
+
${IP:+addr add ${IP} dev ${IF1}}
|
|
96
|
+
link set ${IF1} up
|
|
97
|
+
${ROUTE:+route add ${ROUTE} dev ${IF1}}
|
|
98
|
+
EOF
|
|
99
|
+
|
|
100
|
+
if [ "${TARGET}" ]; then
|
|
101
|
+
info "Adding NAT rule to ${TARGET}"
|
|
102
|
+
IPTABLES ${NS1} PREROUTING -t nat -i ${IF1} -j DNAT --to-destination ${TARGET}
|
|
103
|
+
IPTABLES ${NS1} POSTROUTING -t nat -o ${IF1} -j SNAT --to-source ${IP%/*}
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
info "Moved ${TYPE} link (${IP})"
|
|
107
|
+
|
|
108
|
+
# /test/move-link.sh --verbose ipvlan enp6s0 host 1 2500144 --ip 192.168.88.32/24 --nat 10.0.1.2
|
package/old/net2dot.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env -S python3 -u
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2021, Viasat, Inc
|
|
4
|
+
# Licensed under MPL 2.0
|
|
5
|
+
|
|
6
|
+
import re, sys, yaml
|
|
7
|
+
|
|
8
|
+
NODE_PROPS = 'shape=box style=filled penwidth=1'
|
|
9
|
+
CONTAINER_PROPS = 'fontsize = 12 style = filled fillcolor = "#e1d5e7" color = "#9673a6"'
|
|
10
|
+
NETWORK_PROPS = '%s penwidth = 2' % CONTAINER_PROPS
|
|
11
|
+
INTF_PROPS = 'width=0.1 height=0.1 fontsize=10 fillcolor="#ffbb9e" color="#d7db00"'
|
|
12
|
+
SWITCH_PROPS = 'fontsize=12 style="rounded,filled" fillcolor="#dae8fc" color="#6c8ebf"'
|
|
13
|
+
HOST_PROPS = 'fontsize=12 fillcolor="#f5f5f5" color="#666666"'
|
|
14
|
+
|
|
15
|
+
def id(n):
|
|
16
|
+
return re.sub(r"-", "_", n)
|
|
17
|
+
|
|
18
|
+
def intfText(cluster, intf):
|
|
19
|
+
name = intf.get('intf', intf.get('origName', intf.get('name')))
|
|
20
|
+
if name.startswith('DUMMY_'):
|
|
21
|
+
return '%s__%s [shape=point style=invis]' % (
|
|
22
|
+
id(cluster), id(name))
|
|
23
|
+
else:
|
|
24
|
+
ip = intf.get('ip', intf.get('opts', {}).get('ip', ''))
|
|
25
|
+
if ip: ip = "\\n%s" % ip
|
|
26
|
+
return '%s__%s [label="%s%s" %s]' % (
|
|
27
|
+
id(cluster), id(name), name, ip, INTF_PROPS)
|
|
28
|
+
|
|
29
|
+
if __name__ == '__main__':
|
|
30
|
+
|
|
31
|
+
networkFile = sys.argv[1]
|
|
32
|
+
networkName = sys.argv[2]
|
|
33
|
+
|
|
34
|
+
netCfg = yaml.full_load(open(networkFile))
|
|
35
|
+
if 'services' in netCfg:
|
|
36
|
+
netService = re.sub(r'_1$', '', networkName)
|
|
37
|
+
netCfg = netCfg['services'][netService]['x-network']
|
|
38
|
+
mnCfg = netCfg['mininet-cfg']
|
|
39
|
+
|
|
40
|
+
links = []
|
|
41
|
+
containers = {}
|
|
42
|
+
containers[networkName] = {'nodes': []}
|
|
43
|
+
namespaces = {}
|
|
44
|
+
|
|
45
|
+
for link in netCfg.get('links', []):
|
|
46
|
+
l, r = link['left'], link['right']
|
|
47
|
+
clname, crname = l['container'], r['container']
|
|
48
|
+
ilname, irname = l['intf'], r['intf']
|
|
49
|
+
links.append([(clname, ilname), (crname, irname)])
|
|
50
|
+
for cname, intf in [(clname, l), (crname, r)]:
|
|
51
|
+
if cname not in containers:
|
|
52
|
+
containers[cname] = {'nodes': []}
|
|
53
|
+
containers[cname]['nodes'].append(intf)
|
|
54
|
+
|
|
55
|
+
for lk in mnCfg.get('links', []):
|
|
56
|
+
l, r = lk['left'], lk['right']
|
|
57
|
+
links.append([(networkName, l), (networkName, r)])
|
|
58
|
+
|
|
59
|
+
for s in mnCfg.get('switches', []):
|
|
60
|
+
namespaces[s['name']] = {'interfaces': [], 'type': 'switch', **s}
|
|
61
|
+
for h in mnCfg.get('hosts', []):
|
|
62
|
+
namespaces[h['name']] = {'interfaces': [], 'type': 'host', **h}
|
|
63
|
+
|
|
64
|
+
for i in mnCfg.get('interfaces', []):
|
|
65
|
+
nm, nd = i.get('origName', i['name']), i['node']
|
|
66
|
+
if nd in namespaces:
|
|
67
|
+
namespaces[nd]['interfaces'].append(i)
|
|
68
|
+
else:
|
|
69
|
+
links.append([(networkName, nm), (networkName, nd)])
|
|
70
|
+
|
|
71
|
+
# Make sure all namespaces have at least one interface so that
|
|
72
|
+
# they can be connected by edges (lhead, ltail)
|
|
73
|
+
for nsname, nsdata in namespaces.items():
|
|
74
|
+
if not nsdata['interfaces']:
|
|
75
|
+
nsdata['interfaces'].append({'name': 'DUMMY_%s' % nsname})
|
|
76
|
+
|
|
77
|
+
###
|
|
78
|
+
|
|
79
|
+
print('digraph D {')
|
|
80
|
+
print(' splines = true;')
|
|
81
|
+
print(' compound = true;')
|
|
82
|
+
print(' node [%s];' % NODE_PROPS)
|
|
83
|
+
|
|
84
|
+
for cname, cdata in containers.items():
|
|
85
|
+
print(' subgraph cluster_%s {' % id(cname))
|
|
86
|
+
print(' label = "%s";' % cname)
|
|
87
|
+
if cname == networkName:
|
|
88
|
+
print(' %s;' % NETWORK_PROPS)
|
|
89
|
+
else:
|
|
90
|
+
print(' %s;' % CONTAINER_PROPS)
|
|
91
|
+
for node in cdata['nodes']:
|
|
92
|
+
print(' %s;' % intfText(node['container'], node))
|
|
93
|
+
# mininet/network interface is processed after container links
|
|
94
|
+
# so that the mininet/network definitions take precedence
|
|
95
|
+
if cname == networkName:
|
|
96
|
+
for nsname, nsdata in namespaces.items():
|
|
97
|
+
print(' subgraph cluster_%s__%s {' % (
|
|
98
|
+
id(cname), id(nsname)))
|
|
99
|
+
print(' label = "%s";' % nsname)
|
|
100
|
+
if nsdata['type'] == 'switch':
|
|
101
|
+
print(' %s;' % SWITCH_PROPS)
|
|
102
|
+
else:
|
|
103
|
+
print(' %s;' % HOST_PROPS)
|
|
104
|
+
for intf in nsdata['interfaces']:
|
|
105
|
+
print(' %s;' % intfText(cname, intf))
|
|
106
|
+
print(' }')
|
|
107
|
+
print(" }")
|
|
108
|
+
|
|
109
|
+
for ((lc, ln), (rc, rn)) in links:
|
|
110
|
+
extra=''
|
|
111
|
+
if lc == networkName and ln in namespaces:
|
|
112
|
+
iname = namespaces[ln]['interfaces'][0]['name']
|
|
113
|
+
extra += ' ltail=cluster_%s__%s' % (id(networkName), id(ln))
|
|
114
|
+
ln = id(iname)
|
|
115
|
+
if rc == networkName and rn in namespaces:
|
|
116
|
+
iname = namespaces[rn]['interfaces'][0]['name']
|
|
117
|
+
extra += ' lhead=cluster_%s__%s' % (id(networkName), id(rn))
|
|
118
|
+
rn = id(iname)
|
|
119
|
+
print(' %s__%s -> %s__%s [dir=none%s];' % (
|
|
120
|
+
id(lc), id(ln), id(rc), id(rn), extra))
|
|
121
|
+
|
|
122
|
+
print("}")
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
- MVP for ViaBox:
|
|
2
|
+
- [x] compose/x-network file loading
|
|
3
|
+
- [x] multiple config sources and merging
|
|
4
|
+
- [x] link route config
|
|
5
|
+
- [x] filtering on project and workdir
|
|
6
|
+
- [x] interface and MAC iteration
|
|
7
|
+
- [x] variable templating
|
|
8
|
+
- [ ] *vlan type interfaces
|
|
9
|
+
|
|
10
|
+
- Near term:
|
|
11
|
+
- [ ] dummy interfaces
|
|
12
|
+
- [ ] schema validation
|
|
13
|
+
- [ ] tc/qdisc settings
|
|
14
|
+
- [ ] arbitrary container commands
|
|
15
|
+
|
|
16
|
+
- Further term:
|
|
17
|
+
- [ ] CNI networking support
|
|
18
|
+
- [ ] tunnel interfaces
|
|
19
|
+
- [ ] multiple routes
|
|
20
|
+
- [ ] ovs flow config
|
|
21
|
+
- [ ] Multiple bridge-modes
|
|
22
|
+
- bridge-mode as part of the domain definition so that the
|
|
23
|
+
same conlink instances can support multiple bridge modes
|
|
24
|
+
simultaneously (with a default for links that don't
|
|
25
|
+
specify).
|
|
26
|
+
- [ ] CNI model:
|
|
27
|
+
- conlink runs in container listening for events on a UDS
|
|
28
|
+
(intead of docker events)
|
|
29
|
+
- an outer conlink command is the CNI client that formats
|
|
30
|
+
events to send over the UDS to the inner conlink
|
|
31
|
+
|
|
32
|
+
- Also see schema-ish.yaml
|
|
33
|
+
links:
|
|
34
|
+
- type: TYPE # Default: domain.
|
|
35
|
+
# Others: dummy, tunnel, host, *vlan, etc
|
|
36
|
+
|
|
37
|
+
domain: DOMAIN # ovs switch name
|
|
38
|
+
|
|
39
|
+
container: FOO # full container name
|
|
40
|
+
# OR
|
|
41
|
+
service: FOO # compose service name
|
|
42
|
+
|
|
43
|
+
interface: INTF # internal container interface name
|
|
44
|
+
|
|
45
|
+
# --- optional general ---
|
|
46
|
+
|
|
47
|
+
ip(s): IP # starting address, can include net slash to limit max
|
|
48
|
+
mac: MAC
|
|
49
|
+
mtu: MTU
|
|
50
|
+
route(s): ROUTE # `ip route add ROUTE`, maybe add "dev INTF" automatically
|
|
51
|
+
tc(s): TC # tc/qdisc commands/settings
|
|
52
|
+
flow(s): FLOW # `ovs-ofctl add-flow DOMAIN FLOW`. With var templating.
|
|
53
|
+
command(s): CMD # arbitrary shell cmd. After all links setup for this container
|
|
54
|
+
|
|
55
|
+
# --- optional for 'type:' links ---
|
|
56
|
+
|
|
57
|
+
host-intf: INTF # host interface to *vlan or move in
|
|
58
|
+
mode: MODE # bridge, etc
|
|
59
|
+
vlanid: VLANID # VLAN #
|
|
60
|
+
nat: NAT # nat target
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
- type maps to 'ip link' type with default of 'veth'
|
|
64
|
+
-
|
|
65
|
+
- when type is 'veth', then base default is 'conlink'
|
|
66
|
+
- when type is 'veth' and base is 'conlink', then bridge is required.
|
|
67
|
+
|
|
68
|
+
- conlink veth link: {type: veth [DEFAULT], base: conlink [DEFAULT], bridge: BRIDGE, dev: DEV}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
Dependencies in python version:
|
|
73
|
+
- argparse
|
|
74
|
+
- shlex (parsing commands)
|
|
75
|
+
- compose_interpolation import TemplateWithDefaults
|
|
76
|
+
- cerberus import Validator
|
|
77
|
+
- options: joi, ajv, json-schema, and z-schema.
|
|
78
|
+
- docker
|
|
79
|
+
- psutil (pid_exists)
|
|
80
|
+
- json
|
|
81
|
+
- yaml
|
|
82
|
+
- mininet
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
- [deprecated idea] conlink sub-commands:
|
|
86
|
+
conlink spit
|
|
87
|
+
- output override docker-compose file with conlink service
|
|
88
|
+
- figure out volume mounts to get to other compose file(s)
|
|
89
|
+
conlink dc up ...
|
|
90
|
+
- generate override docker-compose file with conlink service
|
|
91
|
+
- run the compose command with override file
|
|
92
|
+
conlink start
|
|
93
|
+
- start inside compose
|
|
94
|
+
conlink run
|
|
95
|
+
- start outside compose
|
|
96
|
+
|
|
97
|
+
|
package/old/package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "conlink",
|
|
3
|
+
"dependencies": {
|
|
4
|
+
"@exodus/schemasafe": "^1.3.0",
|
|
5
|
+
"ajv": "^8.12.0",
|
|
6
|
+
"dockerode": "^3.3.4",
|
|
7
|
+
"joi": "^17.10.2",
|
|
8
|
+
"nbb": "^1.2.178",
|
|
9
|
+
"neodoc": "^2.0.2",
|
|
10
|
+
"yaml": "^2.2.1"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"shadow-cljs": "^2.25.7",
|
|
14
|
+
"source-map-support": "^0.5.21"
|
|
15
|
+
}
|
|
16
|
+
}
|
package/old/schema.yaml
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
links:
|
|
2
|
+
type: list
|
|
3
|
+
schema:
|
|
4
|
+
type: dict
|
|
5
|
+
schema:
|
|
6
|
+
left:
|
|
7
|
+
type: dict
|
|
8
|
+
required: true
|
|
9
|
+
schema:
|
|
10
|
+
container: {type: string, required: true}
|
|
11
|
+
intf: {type: string, required: true}
|
|
12
|
+
ip: {type: string, required: false,
|
|
13
|
+
regex: '([0-9]+\.){3}[0-9]+\/[0-9]+'}
|
|
14
|
+
mac: {type: string, required: false,
|
|
15
|
+
regex: '([0-9A-Fa-f][0-9A-Fa-f]:){5}[0-9A-Fa-f][0-9A-Fa-f]'}
|
|
16
|
+
right:
|
|
17
|
+
type: dict
|
|
18
|
+
required: true
|
|
19
|
+
schema:
|
|
20
|
+
container: {type: string, required: true}
|
|
21
|
+
intf: {type: string, required: true}
|
|
22
|
+
ip: {type: string, required: false,
|
|
23
|
+
regex: '([0-9]+\.){3}[0-9]+\/[0-9]+'}
|
|
24
|
+
mac: {type: string, required: false,
|
|
25
|
+
regex: '([0-9A-Fa-f][0-9A-Fa-f]:){5}[0-9A-Fa-f][0-9A-Fa-f]'}
|
|
26
|
+
|
|
27
|
+
tunnels:
|
|
28
|
+
type: list
|
|
29
|
+
schema:
|
|
30
|
+
type: dict
|
|
31
|
+
schema:
|
|
32
|
+
type: {type: string, required: true}
|
|
33
|
+
intf: {type: string, required: true}
|
|
34
|
+
vni: {type: number, required: true}
|
|
35
|
+
remote: {type: string, required: true,
|
|
36
|
+
regex: '([0-9]+\.){3}[0-9]+'}
|
|
37
|
+
ip: {type: string, required: false,
|
|
38
|
+
regex: '([0-9]+\.){3}[0-9]+\/[0-9]+'}
|
|
39
|
+
mac: {type: string, required: false,
|
|
40
|
+
regex: '([0-9A-Fa-f][0-9A-Fa-f]:){5}[0-9A-Fa-f][0-9A-Fa-f]'}
|
|
41
|
+
link_args: {type: string, required: false}
|
|
42
|
+
|
|
43
|
+
interfaces:
|
|
44
|
+
type: list
|
|
45
|
+
schema:
|
|
46
|
+
type: dict
|
|
47
|
+
required: true
|
|
48
|
+
schema:
|
|
49
|
+
type: {type: string, required: true,
|
|
50
|
+
allowed: [link, vlan, macvlan, macvtap, ipvlan, ipvtap]}
|
|
51
|
+
container: {type: string, required: true}
|
|
52
|
+
host-intf: {type: string, required: true}
|
|
53
|
+
intf: {type: string, required: true}
|
|
54
|
+
mode: {type: string, required: false,
|
|
55
|
+
dependencies: {type: [macvlan, macvtap, ipvlan, ipvtap]}}
|
|
56
|
+
vlanid: {type: number, required: false,
|
|
57
|
+
dependencies: {type: [vlan]}}
|
|
58
|
+
ip: {type: string, required: false,
|
|
59
|
+
regex: '([0-9]+\.){3}[0-9]+\/[0-9]+'}
|
|
60
|
+
nat: {type: string, required: false,
|
|
61
|
+
regex: '([0-9]+\.){3}[0-9]+',
|
|
62
|
+
dependencies: ip}
|
|
63
|
+
|
|
64
|
+
network-settings: {type: string}
|
|
65
|
+
|
|
66
|
+
commands:
|
|
67
|
+
type: list
|
|
68
|
+
schema:
|
|
69
|
+
type: dict
|
|
70
|
+
schema:
|
|
71
|
+
container: {type: string, required: true}
|
|
72
|
+
command:
|
|
73
|
+
required: true
|
|
74
|
+
oneof: [ {type: string}, {type: list, schema: {type: string}} ]
|
|
75
|
+
|
|
76
|
+
mininet-cfg:
|
|
77
|
+
type: dict
|
|
78
|
+
schema:
|
|
79
|
+
|
|
80
|
+
switches:
|
|
81
|
+
type: list
|
|
82
|
+
required: false
|
|
83
|
+
schema:
|
|
84
|
+
type: dict
|
|
85
|
+
schema:
|
|
86
|
+
name: {type: string, required: true}
|
|
87
|
+
opts: {type: dict, required: false}
|
|
88
|
+
|
|
89
|
+
hosts:
|
|
90
|
+
type: list
|
|
91
|
+
required: false
|
|
92
|
+
schema:
|
|
93
|
+
type: dict
|
|
94
|
+
schema:
|
|
95
|
+
name: {type: string, required: true}
|
|
96
|
+
opts: {type: dict, required: false}
|
|
97
|
+
|
|
98
|
+
containers:
|
|
99
|
+
type: list
|
|
100
|
+
required: false
|
|
101
|
+
schema:
|
|
102
|
+
type: dict
|
|
103
|
+
schema:
|
|
104
|
+
name: {type: string, required: true}
|
|
105
|
+
opts: {type: dict, required: false}
|
|
106
|
+
|
|
107
|
+
links:
|
|
108
|
+
type: list
|
|
109
|
+
required: false
|
|
110
|
+
schema:
|
|
111
|
+
type: dict
|
|
112
|
+
schema:
|
|
113
|
+
left: {type: string, required: true}
|
|
114
|
+
right: {type: string, required: true}
|
|
115
|
+
opts: {type: dict, required: false}
|
|
116
|
+
|
|
117
|
+
interfaces:
|
|
118
|
+
type: list
|
|
119
|
+
required: false
|
|
120
|
+
schema:
|
|
121
|
+
type: dict
|
|
122
|
+
schema:
|
|
123
|
+
node: {type: string, required: true}
|
|
124
|
+
name: {type: string, required: true}
|
|
125
|
+
origName: {type: string, required: false}
|
|
126
|
+
opts: {type: dict, required: false}
|
|
127
|
+
|
|
128
|
+
commands:
|
|
129
|
+
type: list
|
|
130
|
+
required: false
|
|
131
|
+
schema:
|
|
132
|
+
type: dict
|
|
133
|
+
schema:
|
|
134
|
+
node: {type: string, required: true}
|
|
135
|
+
sync: {type: boolean, required: false}
|
|
136
|
+
command:
|
|
137
|
+
required: true
|
|
138
|
+
oneof: [ {type: string}, {type: list, schema: {type: string}} ]
|