Skip to content

Commit bc70b9d

Browse files
Brian Rakbrakthehack
authored andcommitted
Add route rules for additional workload networks
Previously, HAProxy would not route to additional workload networks without user customization because we did not give them the option to provide workload networks interfaces via the UI. This change implements the ability for the user to provide workload networks the user wishes to route to. Since we expect workload networks to be routable to each other, we can program route rules to user-provided CIDR ranges in which routes will exit via the workload default gateway. These routes are configurable via a new file located at /etc/vmware/workload-networks.cfg. This file is written once just before cloud-init performs the bootstrapping. This change also fixes a few bugs in route table configuration. Closes #15, #11, #10
1 parent a7a4b86 commit bc70b9d

File tree

4 files changed

+98
-46
lines changed

4 files changed

+98
-46
lines changed

ansible/roles/cloudinit/files/var/lib/vmware/ovf-to-cloud-init.sh

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ management_gw_key="network.management_gateway"
3838
workload_gw_key="network.workload_gateway"
3939
frontend_gw_key="network.frontend_gateway"
4040

41-
workload_routes_key="network.workload.routes"
41+
workload_networks_key="network.additional_workload_networks"
4242

4343
# These are the display names for the nics
4444
management_net_name="management"
@@ -86,8 +86,13 @@ escapeString () {
8686
echo "$escaped"
8787
}
8888

89-
getWorkloadRoutes() {
90-
routes=$(ovf-rpctool get.ovf "${workload_routes_key}")
89+
# Extract the additional workload networks and store them in the appropriate file.
90+
# These CIDRs will be picked up by the route-tables service and the appropriate routes will be created.
91+
writeWorkloadNetworks() {
92+
networks=$(ovf-rpctool get.ovf "${workload_networks_key}")
93+
if [ -n "${networks}" ]; then
94+
echo "${networks//,/$'\n'}" > /etc/vmware/workload-networks.cfg
95+
fi
9196
}
9297

9398
# Persist a string to a file
@@ -364,6 +369,7 @@ if [ ! -f "$first_boot_path" ]; then
364369
setDataPlaneAPIPort
365370
writeCAfiles
366371
writeAnyipConfig
372+
writeWorkloadNetworks
367373
writeNetPostConfig
368374
else
369375
ensureMetadata

ansible/roles/vmware/files/var/lib/vmware/routetablectl.sh

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ CONFIG_FILE="${CONFIG_FILE:-/etc/vmware/route-tables.cfg}"
6262
# The path to the file with the route table identifiers.
6363
RT_TABLES_FILE="${RT_TABLES_FILE:-/etc/iproute2/rt_tables}"
6464

65+
# Path to the file for additional workload networks.
66+
WORKLOAD_NETWORKS_FILE="${WORKLOAD_NETWORKS_FILE:-/etc/vmware/workload-networks.cfg}"
67+
68+
# Name of the route table for workload networks.
69+
WORKLOAD_RT="${WORKLOAD_RT:-${RT_TABLE_NAME_PREFIX}workload}"
6570

6671
################################################################################
6772
## funcs
@@ -74,12 +79,19 @@ function error() {
7479
echo "${@}" 1>&2
7580
return "${exit_code}"
7681
}
82+
7783
function fatal() {
7884
error "${@}"
7985
exit 1
8086
}
87+
8188
function echo2() {
82-
echo "${@}" 1>&2
89+
echo "${@}" 2>&1
90+
}
91+
92+
function call() {
93+
echo2 "${@}"
94+
eval "${@}"
8395
}
8496

8597
# Returns the name of the device that has the provided MAC address.
@@ -100,43 +112,60 @@ function down_routes() {
100112
route_table_name="$(echo "${line}" | awk -F' ' '{print $2}')"
101113
echo2 "discovered route table ${route_table_name}"
102114

103-
# Remove the rule for this route table. If the route table file does not match
104-
# the actual route tables present, ignore the failure so that tables can be re-added gracefully.
105-
route_rule="$(ip rule | grep -F "${route_table_name}" | awk -F':[[:space:]]' '{print $2}')" || \
106-
echo2 "ignoring failed attempt to find existing table"
107-
echo2 "removing ip rule: ${route_rule}"
108-
# shellcheck disable=SC2086
109-
ip rule del ${route_rule} || echo2 "ignoring failed attempt to delete route rule \"${route_rule}\""
110-
111-
# Remove the default route for this route table. If the route file does not match
112-
# the actual route tables present, ignore the failure so that route can be re-added gracefully.
113-
echo2 "removing default route for ${route_table_name}"
114-
ip route del table "${route_table_name}" default || \
115-
echo "ignoring failed attempt to delete route table \"${route_table_name}\""
115+
# Remove the rules for this route table.
116+
while ip call "rule del from 0/0 to 0/0 table ${route_table_name} 2>/dev/null"; do true; done
117+
118+
# Remove any existing routes from our route tables.
119+
while IFS= read -r route; do
120+
call "ip route del table ${route_table_name} ${route}"
121+
done < <(ip route show table "${route_table_name}")
116122
fi
117-
done <"${RT_TABLES_FILE}"
123+
done < "${RT_TABLES_FILE}"
118124
mv -f "${RT_TABLES_FILE}.tmp" "${RT_TABLES_FILE}"
119125
}
120126

121127
# Adds route tables to the route tables file. Prevents duplicates from being added.
122-
add_route_tables() {
123-
tables=$(grep -E '^\w' "${CONFIG_FILE}" | cut -d, -f1,2 | uniq)
128+
function add_route_tables() {
129+
tables=$(grep -E '^\w' "${CONFIG_FILE}" || echo "" | cut -d, -f1,2 | uniq)
124130
for table in ${tables}; do
125131
IFS=, read -ra line <<< "${table}"
126-
cfg_table_id="${line[0]}"
127-
cfg_table_name="${line[1]}"
128-
echo2 "create new route table id=${cfg_table_id} name=${route_table_name}"
129-
printf '%d\t%s\n' "${cfg_table_id}" "${route_table_name}" >>"${RT_TABLES_FILE}"
132+
route_table_id="${line[0]}"
133+
route_table_name="${RT_TABLE_NAME_PREFIX}${line[1]}"
134+
echo2 "create new route table id=${route_table_id} name=${route_table_name}"
135+
printf '%d\t%s\n' "${route_table_id}" "${route_table_name}" >>"${RT_TABLES_FILE}"
130136
done
131137
}
132138

139+
# Adds lookup rules for workload routes. The net result is that additional workloads can be reached
140+
# via the default gateway of the workload network route table.
141+
function add_workload_network_rules() {
142+
if [ ! -f "${WORKLOAD_NETWORKS_FILE}" ]; then
143+
echo2 "no additional workload networks detected"
144+
return
145+
fi
146+
147+
while IFS= read -r cfg_cidr; do
148+
# Skip empty and commented lines.
149+
if [ -z "${cfg_cidr}" ] || [ "${cfg_cidr::1}" == "#" ]; then
150+
continue
151+
fi
152+
call "ip rule add to ${cfg_cidr} lookup ${WORKLOAD_RT}"
153+
done < "${WORKLOAD_NETWORKS_FILE}"
154+
}
155+
133156
# Enables the custom route tables.
134157
function up_routes() {
135158
# Enabling the custom route tables first requires removing any custom route
136159
# tables.
137160
down_routes
138161

162+
if [ ! -f "${CONFIG_FILE}" ]; then
163+
echo2 "missing config file ${CONFIG_FILE}"
164+
return 0
165+
fi
166+
139167
add_route_tables
168+
add_workload_network_rules
140169

141170
while IFS= read -r line; do
142171
# Skip empty and commented lines.
@@ -148,7 +177,6 @@ function up_routes() {
148177
IFS=, read -ra line_parts <<<"${line}"
149178

150179
# Store route table configuration's parts.
151-
cfg_table_id="${line_parts[0]}"
152180
cfg_table_name="${line_parts[1]}"
153181
cfg_mac_addr="${line_parts[2]}"
154182
cfg_cidr="${line_parts[3]}"
@@ -164,18 +192,14 @@ function up_routes() {
164192
if [[ "${cfg_gateway}" == "" ]]; then
165193
cfg_destination=$(python3 -c "import sys; import ipaddress; print(ipaddress.ip_network(sys.argv[1], strict=False))" "${cfg_cidr}")
166194
host="$(echo "${cfg_cidr}" | cut -d/ -f 1)"
167-
cmd="ip route add table ${route_table_name} ${cfg_destination} dev ${cfg_dev} proto kernel scope link src ${host}"
168-
echo2 "create route with cmd: ${cmd}"
169-
eval "${cmd}"
195+
call "ip route add table ${route_table_name} ${cfg_destination} dev ${cfg_dev} proto kernel scope link src ${host}"
170196
else
171197
# Create default route for new route table.
172-
echo2 "create default route for ${route_table_name}"
173-
ip route add table "${route_table_name}" default via "${cfg_gateway}" dev "${cfg_dev}" proto static
198+
call "ip route add table ${route_table_name} default via ${cfg_gateway} dev ${cfg_dev} proto static"
199+
# Create IP rule for new route table.
200+
call "ip rule add from ${cfg_cidr} lookup ${route_table_name}"
174201
fi
175202

176-
# Create IP rule for new route table.
177-
echo2 "create IP rule for ${route_table_name}"
178-
ip rule add from "${cfg_cidr}" lookup "${route_table_name}"
179203
done <"${CONFIG_FILE}"
180204
}
181205

hack/image-build-ova.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -611,12 +611,16 @@ def stream_optimize_vmdk_files(inlist):
611611
<Label>2.6. Workload Gateway</Label>
612612
<Description>The gateway address for the workload network</Description>
613613
</Property>
614+
<Property ovf:key="additional_workload_networks" ovf:type="string" ovf:userConfigurable="true">
615+
<Label>2.7. Additional Workload Networks</Label>
616+
<Description>(Optional) A comma-separated list of networks in CIDR notation (e.g. 192.168.0.1/24) to the workload networks. These networks must be routable via the Workload Gateway. This list must not include the primary workload network.</Description>
617+
</Property>
614618
<Property ovf:key="frontend_ip" ovf:type="string" ovf:userConfigurable="true" ovf:configuration="frontend">
615-
<Label>2.7. Frontend IP</Label>
619+
<Label>2.8. Frontend IP</Label>
616620
<Description>(Optional) The static IP address for the appliance on the Frontend Port Group in CIDR format (Eg. ip/subnet mask bits). This IP must be outside of the Load Balancer IP Range</Description>
617621
</Property>
618622
<Property ovf:key="frontend_gateway" ovf:type="string" ovf:userConfigurable="true" ovf:configuration="frontend">
619-
<Label>2.8. Frontend Gateway</Label>
623+
<Label>2.9. Frontend Gateway</Label>
620624
<Description>(Optional) The gateway address for the frontend network</Description>
621625
</Property>
622626
</ProductSection>

hack/test-route-programs.sh

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,9 @@ function test_anyiproutectl() {
235235
set -o errexit
236236
set -o nounset
237237
set -o pipefail
238+
set -v
239+
240+
echo "starting test" >&2
238241
239242
# Ping each of the IP addresses and expect an error for each one.
240243
! ping -c2 -W1 "${ANYIP_IP_SLASH_32}"
@@ -331,6 +334,7 @@ EOF
331334
set -o errexit
332335
set -o nounset
333336
set -o pipefail
337+
set -v
334338
335339
# Run the program with an empty config file and expect no errors.
336340
/var/lib/vmware/routetablectl.sh up
@@ -344,6 +348,11 @@ cat <<EOD >/etc/vmware/route-tables.cfg
344348
3,workload,${DOCKER_NET_3_MAC},${DOCKER_IP_NET_3}
345349
EOD
346350
351+
# Create the networks file.
352+
# It's a newline-delimited list of CIDRs.
353+
echo "10.169.10.0/24" > /etc/vmware/workload-networks.cfg
354+
echo "10.169.20.0/24" >> /etc/vmware/workload-networks.cfg
355+
347356
# Run the program with a populated config file and expect no errors.
348357
/var/lib/vmware/routetablectl.sh up
349358
@@ -352,8 +361,12 @@ grep $'2\trtctl_frontend' /etc/iproute2/rt_tables
352361
grep $'3\trtctl_workload' /etc/iproute2/rt_tables
353362
354363
# Assert the expected IP rules exist.
355-
ip rule | grep rtctl_frontend
356-
ip rule | grep rtctl_workload
364+
ip rule show table rtctl_frontend
365+
ip rule show table rtctl_workload
366+
367+
# Assert that rules for our workload networks exist.
368+
ip rule show table rtctl_workload | grep "10.169.10.0 /24"
369+
ip rule show table rtctl_workload | grep "10.169.20.0 /24"
357370
358371
# Assert the expected default gateways exist.
359372
ip route show table rtctl_frontend | grep default
@@ -366,14 +379,18 @@ ip route show table rtctl_workload | grep default
366379
! grep $'2\trtctl_frontend' /etc/iproute2/rt_tables
367380
! grep $'3\trtctl_workload' /etc/iproute2/rt_tables
368381
369-
# Assert the expected IP rules DO NOT exist.
382+
# Assert the expected IP rules DO NOT exist. This also applies to workload networks.
370383
! ip rule | grep rtctl_frontend'
371384
! ip rule | grep rtctl_workload'
372385
373386
# Assert the expected default gateways DO NOT exist.
374387
! ip route show table rtctl_frontend 2>/dev/null
375388
! ip route show table rtctl_workload 2>/dev/null
376389
390+
# Assert that rules for our workload networks DO NOT exist.
391+
! ip rule show table rtctl_workload | grep "10.169.10.0 /24" 2>/dev/null
392+
! ip rule show table rtctl_workload | grep "10.169.20.0 /24" 2>/dev/null
393+
377394
# Truncate the config file.
378395
printf '' >/etc/vmware/route-tables.cfg
379396
@@ -384,18 +401,19 @@ printf '' >/etc/vmware/route-tables.cfg
384401
sleep 1
385402
386403
# Update the config file and assert the appropriate actions occur.
387-
echo "2,frontend,${DOCKER_NET_2_MAC},${DOCKER_NET_2_CIDR},${DOCKER_NET_2_GATEWAY}" >/etc/vmware/route-tables.cfg
404+
echo "2,workload,${DOCKER_NET_3_MAC},${DOCKER_NET_3_CIDR},${DOCKER_NET_3_GATEWAY}" >/etc/vmware/route-tables.cfg
388405
sleep 2
389-
grep $'2\trtctl_frontend' /etc/iproute2/rt_tables
390-
ip rule | grep rtctl_frontend
391-
ip route show table rtctl_frontend | grep default
406+
grep $'2\trtctl_workload' /etc/iproute2/rt_tables
407+
ip rule | grep rtctl_workload
408+
ip route show table rtctl_workload | grep default
392409
393410
# Update the config file and assert the appropriate actions occur.
394-
echo "3,workload,${DOCKER_NET_3_MAC},${DOCKER_NET_3_CIDR},${DOCKER_NET_3_GATEWAY}" >/etc/vmware/route-tables.cfg
411+
echo "3,frontend,${DOCKER_NET_2_MAC},${DOCKER_NET_2_CIDR},${DOCKER_NET_2_GATEWAY}" >> /etc/vmware/route-tables.cfg
395412
sleep 2
396-
grep $'3\trtctl_workload' /etc/iproute2/rt_tables
397-
ip rule | grep rtctl_workload
398-
ip route show table rtctl_workload | grep default
413+
grep $'3\trtctl_frontend' /etc/iproute2/rt_tables
414+
ip rule | grep rtctl_frontend
415+
ip route show table rtctl_frontend | grep default
416+
399417
EOF
400418

401419
# Copy the test script to the container.

0 commit comments

Comments
 (0)