Skip to content

Commit 9f929d0

Browse files
committed
support for capacity tracking + distributed provisioning
This fakes capacity by pretending to have linear storage for different kinds and subtracting the size of existing volumes from that. A test deployment with some example storage classes, an example app with a generic ephemeral inline volume, and storage capacity tracking enabled is provided for use on a Kubernetes cluster where these alpha features are enabled. When that feature is not enabled in the cluster, also the driver deployment is done without storage capacity enabled. This then serves as a test that distributed provisioning works. prow.sh can be used to test this new deployment, for example with: CSI_PROW_SANITY_POD=csi-hostpath-socat-0 \ CSI_PROW_SANITY_CONTAINER=socat \ CSI_PROW_DEPLOYMENT=kubernetes-distributed \ CSI_PROW_KUBERNETES_VERSION=1.19.0 \ ./.prow.sh
1 parent e15a550 commit 9f929d0

16 files changed

+754
-51
lines changed

cmd/hostpathplugin/main.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ var (
3636
ephemeral = flag.Bool("ephemeral", false, "publish volumes in ephemeral mode even if kubelet did not ask for it (only needed for Kubernetes 1.15)")
3737
maxVolumesPerNode = flag.Int64("maxvolumespernode", 0, "limit of volumes per node")
3838
showVersion = flag.Bool("version", false, "Show version.")
39+
capacity = func() hostpath.Capacity {
40+
c := hostpath.Capacity{}
41+
flag.Var(c, "capacity", "Simulate storage capacity. The parameter is <kind>=<quantity> where <kind> is the value of a 'kind' storage class parameter and <quantity> is the total amount of bytes for that kind. The flag may be used multiple times to configure different kinds.")
42+
return c
43+
}()
3944
// Set by the build process
4045
version = ""
4146
)
@@ -53,12 +58,7 @@ func main() {
5358
fmt.Fprintln(os.Stderr, "Deprecation warning: The ephemeral flag is deprecated and should only be used when deploying on Kubernetes 1.15. It will be removed in the future.")
5459
}
5560

56-
handle()
57-
os.Exit(0)
58-
}
59-
60-
func handle() {
61-
driver, err := hostpath.NewHostPathDriver(*driverName, *nodeID, *endpoint, *ephemeral, *maxVolumesPerNode, version)
61+
driver, err := hostpath.NewHostPathDriver(*driverName, *nodeID, *endpoint, *ephemeral, *maxVolumesPerNode, version, capacity)
6262
if err != nil {
6363
fmt.Printf("Failed to initialize driver: %s", err.Error())
6464
os.Exit(1)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
This deployment is meant for Kubernetes clusters with
2+
CSIStorageCapacity enabled. It deploys the hostpath driver on each
3+
node, using distributed provisioning, and configures it so that it has
4+
10Gi of "fast" storage and 100Gi of "slow" storage.
5+
6+
The "kind" storage class parameter can selected between the two. If
7+
not set, an arbitrary kind with enough capacity is picked.
8+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# This example Pod definition demonstrates
2+
# how to use generic ephemeral inline volumes
3+
# with a hostpath storage class.
4+
kind: Pod
5+
apiVersion: v1
6+
metadata:
7+
name: my-csi-app-inline-volume
8+
spec:
9+
containers:
10+
- name: my-frontend
11+
image: k8s.gcr.io/pause
12+
volumeMounts:
13+
- mountPath: "/data"
14+
name: my-csi-volume
15+
volumes:
16+
- name: my-csi-volume
17+
ephemeral:
18+
volumeClaimTemplate:
19+
spec:
20+
accessModes:
21+
- ReadWriteOnce
22+
resources:
23+
requests:
24+
storage: 4Gi
25+
storageClassName: csi-hostpath-fast
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#!/usr/bin/env bash
2+
3+
# This script captures the steps required to successfully
4+
# deploy the hostpath plugin driver. This should be considered
5+
# authoritative and all updates for this process should be
6+
# done here and referenced elsewhere.
7+
8+
# The script assumes that kubectl is available on the OS path
9+
# where it is executed.
10+
11+
set -e
12+
set -o pipefail
13+
14+
BASE_DIR=$(dirname "$0")
15+
16+
# If set, the following env variables override image registry and/or tag for each of the images.
17+
# They are named after the image name, with hyphen replaced by underscore and in upper case.
18+
#
19+
# - CSI_ATTACHER_REGISTRY
20+
# - CSI_ATTACHER_TAG
21+
# - CSI_NODE_DRIVER_REGISTRAR_REGISTRY
22+
# - CSI_NODE_DRIVER_REGISTRAR_TAG
23+
# - CSI_PROVISIONER_REGISTRY
24+
# - CSI_PROVISIONER_TAG
25+
# - CSI_SNAPSHOTTER_REGISTRY
26+
# - CSI_SNAPSHOTTER_TAG
27+
# - HOSTPATHPLUGIN_REGISTRY
28+
# - HOSTPATHPLUGIN_TAG
29+
#
30+
# Alternatively, it is possible to override all registries or tags with:
31+
# - IMAGE_REGISTRY
32+
# - IMAGE_TAG
33+
# These are used as fallback when the more specific variables are unset or empty.
34+
#
35+
# IMAGE_TAG=canary is ignored for images that are blacklisted in the
36+
# deployment's optional canary-blacklist.txt file. This is meant for
37+
# images which have known API breakages and thus cannot work in those
38+
# deployments anymore. That text file must have the name of the blacklisted
39+
# image on a line by itself, other lines are ignored. Example:
40+
#
41+
# # The following canary images are known to be incompatible with this
42+
# # deployment:
43+
# csi-snapshotter
44+
#
45+
# Beware that the .yaml files do not have "imagePullPolicy: Always". That means that
46+
# also the "canary" images will only be pulled once. This is good for testing
47+
# (starting a pod multiple times will always run with the same canary image), but
48+
# implies that refreshing that image has to be done manually.
49+
#
50+
# As a special case, 'none' as registry removes the registry name.
51+
52+
# The default is to use the RBAC rules that match the image that is
53+
# being used, also in the case that the image gets overridden. This
54+
# way if there are breaking changes in the RBAC rules, the deployment
55+
# will continue to work.
56+
#
57+
# However, such breaking changes should be rare and only occur when updating
58+
# to a new major version of a sidecar. Nonetheless, to allow testing the scenario
59+
# where the image gets overridden but not the RBAC rules, updating the RBAC
60+
# rules can be disabled.
61+
: ${UPDATE_RBAC_RULES:=true}
62+
function rbac_version () {
63+
yaml="$1"
64+
image="$2"
65+
update_rbac="$3"
66+
67+
# get version from `image: quay.io/k8scsi/csi-attacher:v1.0.1`, ignoring comments
68+
version="$(sed -e 's/ *#.*$//' "$yaml" | grep "image:.*$image" | sed -e 's/ *#.*//' -e 's/.*://')"
69+
70+
if $update_rbac; then
71+
# apply overrides
72+
varname=$(echo $image | tr - _ | tr a-z A-Z)
73+
eval version=\${${varname}_TAG:-\${IMAGE_TAG:-\$version}}
74+
fi
75+
76+
# When using canary images, we have to assume that the
77+
# canary images were built from the corresponding branch.
78+
case "$version" in canary) version=master;;
79+
*-canary) version="$(echo "$version" | sed -e 's/\(.*\)-canary/release-\1/')";;
80+
esac
81+
82+
echo "$version"
83+
}
84+
85+
# version_gt returns true if arg1 is greater than arg2.
86+
#
87+
# This function expects versions to be one of the following formats:
88+
# X.Y.Z, release-X.Y.Z, vX.Y.Z
89+
#
90+
# where X,Y, and Z are any number.
91+
#
92+
# Partial versions (1.2, release-1.2) work as well.
93+
# The follow substrings are stripped before version comparison:
94+
# - "v"
95+
# - "release-"
96+
#
97+
# Usage:
98+
# version_gt release-1.3 v1.2.0 (returns true)
99+
# version_gt v1.1.1 v1.2.0 (returns false)
100+
# version_gt 1.1.1 v1.2.0 (returns false)
101+
# version_gt 1.3.1 v1.2.0 (returns true)
102+
# version_gt 1.1.1 release-1.2.0 (returns false)
103+
# version_gt 1.2.0 1.2.2 (returns false)
104+
function version_gt() {
105+
versions=$(for ver in "$@"; do ver=${ver#release-}; ver=${ver#kubernetes-}; echo ${ver#v}; done)
106+
greaterVersion=${1#"release-"};
107+
greaterVersion=${greaterVersion#"kubernetes-"};
108+
greaterVersion=${greaterVersion#"v"};
109+
test "$(printf '%s' "$versions" | sort -V | head -n 1)" != "$greaterVersion"
110+
}
111+
112+
113+
CSI_PROVISIONER_RBAC_YAML="https://raw.githubusercontent.com/kubernetes-csi/external-provisioner/$(rbac_version "${BASE_DIR}/hostpath/csi-hostpath-plugin.yaml" csi-provisioner false)/deploy/kubernetes/rbac.yaml"
114+
: ${CSI_PROVISIONER_RBAC:=https://raw.githubusercontent.com/kubernetes-csi/external-provisioner/$(rbac_version "${BASE_DIR}/hostpath/csi-hostpath-plugin.yaml" csi-provisioner "${UPDATE_RBAC_RULES}")/deploy/kubernetes/rbac.yaml}
115+
116+
# Some images are not affected by *_REGISTRY/*_TAG and IMAGE_* variables.
117+
# The default is to update unless explicitly excluded.
118+
update_image () {
119+
case "$1" in socat) return 1;; esac
120+
}
121+
122+
run () {
123+
echo "$@" >&2
124+
"$@"
125+
}
126+
127+
# rbac rules
128+
echo "applying RBAC rules"
129+
for component in CSI_PROVISIONER; do
130+
eval current="\${${component}_RBAC}"
131+
eval original="\${${component}_RBAC_YAML}"
132+
if [ "$current" != "$original" ]; then
133+
echo "Using non-default RBAC rules for $component. Changes from $original to $current are:"
134+
diff -c <(wget --quiet -O - "$original") <(if [[ "$current" =~ ^http ]]; then wget --quiet -O - "$current"; else cat "$current"; fi) || true
135+
fi
136+
run kubectl apply -f "${current}"
137+
done
138+
139+
if kubectl get csistoragecapacities 2>&1 | grep "the server doesn't have a resource type"; then
140+
have_csistoragecapacity=false
141+
else
142+
have_csistoragecapacity=true
143+
fi
144+
145+
# deploy hostpath plugin and registrar sidecar
146+
echo "deploying hostpath components"
147+
for i in $(ls ${BASE_DIR}/hostpath/*.yaml | sort); do
148+
echo " $i"
149+
modified="$(cat "$i" | while IFS= read -r line; do
150+
nocomments="$(echo "$line" | sed -e 's/ *#.*$//')"
151+
if echo "$nocomments" | grep -q '^[[:space:]]*image:[[:space:]]*'; then
152+
# Split 'image: quay.io/k8scsi/csi-attacher:v1.0.1'
153+
# into image (quay.io/k8scsi/csi-attacher:v1.0.1),
154+
# registry (quay.io/k8scsi),
155+
# name (csi-attacher),
156+
# tag (v1.0.1).
157+
image=$(echo "$nocomments" | sed -e 's;.*image:[[:space:]]*;;')
158+
registry=$(echo "$image" | sed -e 's;\(.*\)/.*;\1;')
159+
name=$(echo "$image" | sed -e 's;.*/\([^:]*\).*;\1;')
160+
tag=$(echo "$image" | sed -e 's;.*:;;')
161+
162+
# Variables are with underscores and upper case.
163+
varname=$(echo $name | tr - _ | tr a-z A-Z)
164+
165+
# Now replace registry and/or tag, if set as env variables.
166+
# If not set, the replacement is the same as the original value.
167+
# Only do this for the images which are meant to be configurable.
168+
if update_image "$name"; then
169+
prefix=$(eval echo \${${varname}_REGISTRY:-${IMAGE_REGISTRY:-${registry}}}/ | sed -e 's;none/;;')
170+
if [ "$IMAGE_TAG" = "canary" ] &&
171+
[ -f ${BASE_DIR}/canary-blacklist.txt ] &&
172+
grep -q "^$name\$" ${BASE_DIR}/canary-blacklist.txt; then
173+
# Ignore IMAGE_TAG=canary for this particular image because its
174+
# canary image is blacklisted in the deployment blacklist.
175+
suffix=$(eval echo :\${${varname}_TAG:-${tag}})
176+
else
177+
suffix=$(eval echo :\${${varname}_TAG:-${IMAGE_TAG:-${tag}}})
178+
fi
179+
line="$(echo "$nocomments" | sed -e "s;$image;${prefix}${name}${suffix};")"
180+
fi
181+
echo " using $line" >&2
182+
fi
183+
if ! $have_csistoragecapacity; then
184+
line="$(echo "$line" | grep -v -e 'storageCapacity: true' -e '--enable-capacity')"
185+
fi
186+
echo "$line"
187+
done)"
188+
if ! echo "$modified" | kubectl apply -f -; then
189+
echo "modified version of $i:"
190+
echo "$modified"
191+
exit 1
192+
fi
193+
done
194+
195+
wait_for_daemonset () {
196+
retries=10
197+
while [ $retries -ge 0 ]; do
198+
ready=$(kubectl get -n $1 daemonset $2 -o jsonpath="{.status.numberReady}")
199+
required=$(kubectl get -n $1 daemonset $2 -o jsonpath="{.status.desiredNumberScheduled}")
200+
if [ $ready -gt 0 ] && [ $ready -eq $required ]; then
201+
return 0
202+
fi
203+
retries=$((retries - 1))
204+
sleep 3
205+
done
206+
return 1
207+
}
208+
209+
210+
# Wait until the DaemonSet is running on all nodes.
211+
if ! wait_for_daemonset default csi-hostpathplugin; then
212+
echo "driver not ready"
213+
kubectl describe daemonsets/csi-hostpathplugin
214+
exit 1
215+
fi
216+
217+
# Create a test driver configuration in the place where the prow job
218+
# expects it?
219+
if [ "${CSI_PROW_TEST_DRIVER}" ]; then
220+
cp "${BASE_DIR}/test-driver.yaml" "${CSI_PROW_TEST_DRIVER}"
221+
fi
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: storage.k8s.io/v1
2+
kind: CSIDriver
3+
metadata:
4+
name: hostpath.csi.k8s.io
5+
spec:
6+
# Supports persistent and ephemeral inline volumes.
7+
volumeLifecycleModes:
8+
- Persistent
9+
- Ephemeral
10+
# To determine at runtime which mode a volume uses, pod info and its
11+
# "csi.storage.k8s.io/ephemeral" entry are needed.
12+
podInfoOnMount: true
13+
# No attacher needed.
14+
attachRequired: false
15+
# alpha: opt into capacity-aware scheduling
16+
storageCapacity: true

0 commit comments

Comments
 (0)