@@ -198,14 +198,16 @@ EOF
198198install_cni_conf () {
199199 local cni_conf_path=$1
200200
201- local tmp_data=' '
202- local conf_data=' '
203- if [ -e " ${cni_conf_path} " ]; then
204- # Add the linkerd-cni plugin to the existing list
205- tmp_data=$( cat " ${TMP_CONF} " )
206- conf_data=$( jq --argjson CNI_TMP_CONF_DATA " ${tmp_data} " -f /linkerd/filter.jq " ${cni_conf_path} " )
207- echo " ${conf_data} " > ${TMP_CONF}
208- fi
201+ # Add the linkerd-cni plugin to the existing list.
202+ local tmp_data
203+ local conf_data
204+ tmp_data=$( cat " $TMP_CONF " )
205+ conf_data=$( jq --argjson CNI_TMP_CONF_DATA " $tmp_data " -f /linkerd/filter.jq " $cni_conf_path " || true)
206+
207+ # Ensure that CNI config file did not disappear during processing.
208+ [ -n " $conf_data " ] || return 0
209+
210+ echo " $conf_data " > " $TMP_CONF "
209211
210212 # If the old config filename ends with .conf, rename it to .conflist, because it has changed to be a list
211213 filename=${cni_conf_path##*/ }
@@ -218,6 +220,23 @@ install_cni_conf() {
218220 cni_conf_path=" ${cni_conf_path} list"
219221 fi
220222
223+ # Store SHA of each patched file in global `CNI_CONF_SHA` variable.
224+ #
225+ # This must happen in a non-concurrent access context!
226+ #
227+ # The below logic assumes that the `CNI_CONF_SHA` variable is already a
228+ # valid JSON object. So this variable must be initialized with '{}'!
229+ #
230+ # E.g. (pretty-printed; actual variable stores compact JSON object)
231+ #
232+ # {
233+ # "/etc/cni/net.d/05-foo.conflist": "b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c",
234+ # "/etc/cni/net.d/10-bar.conflist": "7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730"
235+ # }
236+ local new_sha
237+ new_sha=$( (sha256sum " $TMP_CONF " || true) | awk ' {print $1}' )
238+ CNI_CONF_SHA=$( jq -c --arg f " $cni_conf_path " --arg sha " $new_sha " ' . * {$f: $sha}' <<< " $CNI_CONF_SHA" )
239+
221240 # Move the temporary CNI config into place.
222241 mv " ${TMP_CONF} " " ${cni_conf_path} " || exit_with_error ' Failed to mv files.'
223242 [ -n " $old_file_path " ] && rm -f " ${old_file_path} " && log " Removing unwanted .conf file"
@@ -226,57 +245,50 @@ install_cni_conf() {
226245}
227246
228247# Sync() is responsible for reacting to file system changes. It is used in
229- # conjunction with inotify events; sync() is called with the name of the file
230- # that has changed, the event type (which can be either 'CREATE', 'DELETE',
231- # 'MOVED_TO' or 'MODIFY', and the previously observed SHA of the configuration
232- # file.
248+ # conjunction with inotify events; sync() is called with the event type (which
249+ # can be either 'CREATE', 'MOVED_TO' or 'MODIFY'), and the name of the file that
250+ # has changed.
233251#
234- # Based on the changed file and event type, sync() might re-install the CNI
235- # plugin's configuration file.
252+ # Based on the changed file, sync() might re-install the CNI configuration file.
236253sync () {
237- local filename=$1
238- local ev=$2
239- local filepath=" ${HOST_CNI_NET} /$filename "
240-
241- local prev_sha=$3
242-
243- local config_file_count
244- local new_sha
245- if [ " $ev " = ' CREATE' ] || [ " $ev " = ' MOVED_TO' ] || [ " $ev " = ' MODIFY' ]; then
246- # When the event type is 'CREATE', 'MOVED_TO' or 'MODIFY', we check the
247- # previously observed SHA (updated with each file watch) and compare it
248- # against the new file's SHA. If they differ, it means something has
249- # changed.
250- new_sha=$( sha256sum " ${filepath} " | while read -r s _; do echo " $s " ; done)
251- if [ " $new_sha " != " $prev_sha " ]; then
252- # Create but don't rm old one since we don't know if this will be configured
253- # to run as _the_ cni plugin.
254- log " New/changed file [$filename ] detected; re-installing"
255- create_kubeconfig
256- create_cni_conf
257- install_cni_conf " $filepath "
258- else
259- # If the SHA hasn't changed or we get an unrecognised event, ignore it.
260- # When the SHA is the same, we can get into infinite loops whereby a file has
261- # been created and after re-install the watch keeps triggering CREATE events
262- # that never end.
263- log " Ignoring event: $ev $filepath ; no real changes detected"
264- fi
254+ local ev=$1
255+ local file=${2// \/\/ / \/ } # replace "//" with "/"
256+
257+ [[ " $file " =~ .* .(conflist| conf)$ ]] || return 0
258+
259+ log " Detected event: $ev $file "
260+
261+ # Retrieve previous SHA of detected file (if any) and compute current SHA.
262+ local previous_sha
263+ local current_sha
264+ previous_sha=$( jq -r --arg f " $file " ' .[$f] | select(.)' <<< " $CNI_CONF_SHA" )
265+ current_sha=$( (sha256sum " $file " || true) | awk ' {print $1}' )
266+
267+ # If the SHA hasn't changed or the detected file has disappeared, ignore it.
268+ # When the SHA is the same, we can get into infinite loops whereby a file
269+ # has been created and after re-install the watch keeps triggering MOVED_TO
270+ # events that never end.
271+ # If the `current_sha` variable is blank then the detected CNI config file has
272+ # disappeared and no further action is required.
273+ # There exists an unhandled (highly improbable) edge case where a CNI plugin
274+ # creates a config file and then _immediately_ removes it again _while_ we are
275+ # in the process of patching it. If this happens, we may create a patched CNI
276+ # config file that should *not* exist.
277+ if [ -n " $current_sha " ] && [ " $current_sha " != " $previous_sha " ]; then
278+ log " New/changed file [$file ] detected; re-installing"
279+ create_kubeconfig
280+ create_cni_conf
281+ install_cni_conf " $file "
282+ else
283+ log " Ignoring event: $ev $file ; no real changes detected or file disappeared"
265284 fi
266285}
267286
268287# monitor_cni_config starts a watch on the host's CNI config directory
269288monitor_cni_config () {
270289 inotifywait -m " ${HOST_CNI_NET} " -e create,moved_to,modify |
271290 while read -r directory action filename; do
272- if [[ " $filename " =~ .* .(conflist| conf)$ ]]; then
273- log " Detected change in $directory : $action $filename "
274- sync " $filename " " $action " " $cni_conf_sha "
275- # calculate file SHA to use in the next iteration
276- if [[ -e " $directory /$filename " ]]; then
277- cni_conf_sha=" $( sha256sum " $directory /$filename " | while read -r s _; do echo " $s " ; done)"
278- fi
279- fi
291+ sync " $action " " $directory /$filename "
280292 done
281293}
282294
@@ -318,6 +330,14 @@ rm -f "${DEFAULT_CNI_CONF_PATH}"
318330
319331install_cni_bin
320332
333+ # The CNI config monitor must be set up _before_ we start patching existing CNI
334+ # config files!
335+ # Otherwise, new CNI config files can be created just _after_ the initial round
336+ # of patching and just _before_ we set up the `inotifywait` loop to detect new
337+ # CNI config files.
338+ CNI_CONF_SHA=' {}'
339+ monitor_cni_config &
340+
321341# Append our config to any existing config file (*.conflist or *.conf)
322342config_files=$( find " ${HOST_CNI_NET} " -maxdepth 1 -type f \( -iname ' *conflist' -o -iname ' *conf' \) )
323343if [ -z " $config_files " ]; then
@@ -329,23 +349,16 @@ else
329349 else
330350 find " ${HOST_CNI_NET} " -maxdepth 1 -type f \( -iname ' *conflist' -o -iname ' *conf' \) -print0 |
331351 while read -r -d $' \0' file; do
332- log " Installing CNI configuration for $file "
333- create_kubeconfig
334- create_cni_conf
335- install_cni_conf " $file "
352+ log " Trigger CNI config detection for $file "
353+ tmp_file=" $( mktemp -u /tmp/linkerd-cni.patch-candidate.XXXXXX) "
354+ cp -fp " $file " " $tmp_file "
355+ # The following will trigger the `sync()` function via filesystem event.
356+ # This requires `monitor_cni_config()` to be up and running!
357+ mv " $tmp_file " " $file " || exit_with_error ' Failed to mv files.'
336358 done
337359 fi
338360fi
339361
340- # Compute SHA for first config file found; this will be updated after every iteration.
341- # First config file is likely to be chosen as the de facto CNI config by the
342- # host.
343- conf=" $( find " ${HOST_CNI_NET} " -maxdepth 1 -type f \( -iname ' *conflist' -o -iname ' *conf' \) | sort | head -n 1) "
344- cni_conf_sha=" "
345- if [[ -n " $conf " ]]; then
346- cni_conf_sha=" $( sha256sum " $conf " | while read -r s _; do echo " $s " ; done)"
347- fi
348-
349362# Watch in bg so we can receive interrupt signals through 'trap'. From 'man
350363# bash':
351364# "If bash is waiting for a command to complete and receives a signal
354367# builtin, the reception of a signal for which a trap has been set will cause
355368# the wait builtin to return immediately with an exit status greater than 128,
356369# immediately after which the trap is executed."
357- monitor_cni_config &
358370monitor_service_account_token &
359371# uses -n so that we exit when the first background job exits (when there's an error)
360372wait -n
0 commit comments