From 8df3a9cae81f145d5ca4903b5e5ccf961029c468 Mon Sep 17 00:00:00 2001 From: Marek Biskup Date: Wed, 1 Jul 2015 15:57:39 +0200 Subject: [PATCH] namespaces in addon update --- .../salt/kube-addons/kube-addon-update.sh | 138 +++++++++++------- test/e2e/addon_update.go | 56 +++---- 2 files changed, 114 insertions(+), 80 deletions(-) diff --git a/cluster/saltbase/salt/kube-addons/kube-addon-update.sh b/cluster/saltbase/salt/kube-addons/kube-addon-update.sh index dec1e7ce438..a3c3f29504d 100755 --- a/cluster/saltbase/salt/kube-addons/kube-addon-update.sh +++ b/cluster/saltbase/salt/kube-addons/kube-addon-update.sh @@ -113,9 +113,11 @@ except Exception, ex: } # $1 yaml file path -function get-object-name-from-file() { +# returns a string of the form / (we call it nsnames) +function get-object-nsname-from-file() { # prints to stdout, so log cannot be used #WARNING: only yaml is supported + #addons that do not specify a namespace are assumed to be in "default". cat $1 | python -c ''' try: import pipes,sys,yaml @@ -126,7 +128,10 @@ try: # Otherwise we are ignoring them (the update will not work anyway) print "ERROR" else: - print y["metadata"]["name"] + try: + print "%s/%s" % (y["metadata"]["namespace"], y["metadata"]["name"]) + except Exception, ex: + print "default/%s" % y["metadata"]["name"] except Exception, ex: print "ERROR" ''' @@ -136,7 +141,7 @@ except Exception, ex: # $2 addon type (e.g. ReplicationController) # echoes the string with paths to files containing addon for the given type # works only for yaml files (!) (ignores json files) -function get-addons-from-disk() { +function get-addon-paths-from-disk() { # prints to stdout, so log cannot be used local -r addon_dir=$1 local -r obj_type=$2 @@ -184,9 +189,10 @@ function run-until-success() { } # $1 object type -function get-addons-from-server() { +# returns a list of / pairs (nsnames) +function get-addon-nsnames-from-server() { local -r obj_type=$1 - "${KUBECTL}" get "${obj_type}" -o template -t "{{range.items}}{{.metadata.name}} {{end}}" --api-version=v1beta3 -l kubernetes.io/cluster-service=true + "${KUBECTL}" get "${obj_type}" --all-namespaces -o template -t "{{range.items}}{{.metadata.namespace}}/{{.metadata.name}} {{end}}" --api-version=v1beta3 -l kubernetes.io/cluster-service=true } # returns the characters after the last separator (including) @@ -228,38 +234,52 @@ function get-basename() { function stop-object() { local -r obj_type=$1 - local -r obj_name=$2 - log INFO "Stopping ${obj_type} ${obj_name}" - run-until-success "${KUBECTL} stop ${obj_type} ${obj_name}" ${NUM_TRIES} ${DELAY_AFTER_ERROR_SEC} + local -r namespace=$2 + local -r obj_name=$3 + log INFO "Stopping ${obj_type} ${namespace}/${obj_name}" + + run-until-success "${KUBECTL} stop --namespace=${namespace} ${obj_type} ${obj_name}" ${NUM_TRIES} ${DELAY_AFTER_ERROR_SEC} } function create-object() { local -r obj_type=$1 local -r file_path=$2 - log INFO "Creating new ${obj_type} from file ${file_path}" + + local nsname_from_file + nsname_from_file=$(get-object-nsname-from-file ${file_path}) + if [[ "${nsname_from_file}" == "ERROR" ]]; then + log INFO "Cannot read object name from ${file_path}. Ignoring" + return 1 + fi + IFS='/' read namespace obj_name <<< "${nsname_from_file}" + + log INFO "Creating new ${obj_type} from file ${file_path} in namespace ${namespace}, name: ${obj_name}" # this will keep on failing if the ${file_path} disappeared in the meantime. # Do not use too many retries. - run-until-success "${KUBECTL} create -f ${file_path}" ${NUM_TRIES} ${DELAY_AFTER_ERROR_SEC} + run-until-success "${KUBECTL} create --namespace=${namespace} -f ${file_path}" ${NUM_TRIES} ${DELAY_AFTER_ERROR_SEC} } function update-object() { local -r obj_type=$1 - local -r obj_name=$2 - local -r file_path=$3 - log INFO "updating the ${obj_type} ${obj_name} with the new definition ${file_path}" - stop-object ${obj_type} ${obj_name} + local -r namespace=$2 + local -r obj_name=$3 + local -r file_path=$4 + log INFO "updating the ${obj_type} ${namespace}/${obj_name} with the new definition ${file_path}" + stop-object ${obj_type} ${namespace} ${obj_name} create-object ${obj_type} ${file_path} } # deletes the objects from the server # $1 object type -# $2 a list of object names +# $2 a list of object nsnames function stop-objects() { local -r obj_type=$1 - local -r obj_names=$2 + local -r obj_nsnames=$2 + local namespace local obj_name - for obj_name in ${obj_names}; do - stop-object ${obj_type} ${obj_names} & + for nsname in ${obj_nsnames}; do + IFS='/' read namespace obj_name <<< "${nsname}" + stop-object ${obj_type} ${namespace} ${obj_name} & done } @@ -284,22 +304,28 @@ function create-objects() { # updates objects # $1 object type # $2 a list of update specifications -# each update specification is a ';' separated pair: ; +# each update specification is a ';' separated pair: ; function update-objects() { local -r obj_type=$1 # ignored local -r update_spec=$2 local objdesc + local nsname + local obj_name + local namespace + for objdesc in ${update_spec}; do - IFS=';' read -a array <<< ${objdesc} - update-object ${obj_type} ${array[0]} ${array[1]} & + IFS=';' read nsname file_path <<< "${objdesc}" + IFS='/' read namespace obj_name <<< "${nsname}" + + update-object ${obj_type} ${namespace} ${obj_name} ${file_path} & done } # Global variables set by function match-objects. -for_delete="" # a list of object names to be deleted -for_update="" # a list of pairs ; for objects that should be updated -for_ignore="" # a list of object nanes that can be ignored -new_files="" # a list of file paths that weren't matched by any existing objects (these objects must be created now) +nsnames_for_delete="" # a list of object nsnames to be deleted +for_update="" # a list of pairs ; for objects that should be updated +nsnames_for_ignore="" # a list of object nsnames that will be ignored +new_files="" # a list of file paths that weren't matched by any existing objects (these objects must be created now) # $1 path to files with objects @@ -311,32 +337,36 @@ function match-objects() { local -r separator=$3 # output variables (globals) - for_delete="" + nsnames_for_delete="" for_update="" - for_ignore="" + nsnames_for_ignore="" new_files="" - addon_names_on_server=$(get-addons-from-server "${obj_type}") - addon_paths_in_files=$(get-addons-from-disk "${addon_dir}" "${obj_type}") + addon_nsnames_on_server=$(get-addon-nsnames-from-server "${obj_type}") + addon_paths_in_files=$(get-addon-paths-from-disk "${addon_dir}" "${obj_type}") - log DB2 "addon_names_on_server=${addon_names_on_server}" + log DB2 "addon_nsnames_on_server=${addon_nsnames_on_server}" log DB2 "addon_paths_in_files=${addon_paths_in_files}" local matched_files="" - local basename_on_server="" - local name_on_server="" + local basensname_on_server="" + local nsname_on_server="" local suffix_on_server="" - local name_from_file="" + local nsname_from_file="" local suffix_from_file="" local found=0 local addon_path="" - for name_on_server in ${addon_names_on_server}; do - basename_on_server=$(get-basename ${name_on_server} ${separator}) - suffix_on_server="$(get-suffix ${name_on_server} ${separator})" + # objects that were moved between namespaces will have different nsname + # because the namespace is included. So they will be treated + # like different objects and not updated but deleted and created again + # (in the current version update is also delete+create, so it does not matter) + for nsname_on_server in ${addon_nsnames_on_server}; do + basensname_on_server=$(get-basename ${nsname_on_server} ${separator}) + suffix_on_server="$(get-suffix ${nsname_on_server} ${separator})" - log DB3 "Found existing addon ${name_on_server}, basename=${basename_on_server}" + log DB3 "Found existing addon ${nsname_on_server}, basename=${basensname_on_server}" # check if the addon is present in the directory and decide # what to do with it @@ -344,31 +374,31 @@ function match-objects() { # again. But for small number of addons it doesn't matter so much. found=0 for addon_path in ${addon_paths_in_files}; do - name_from_file=$(get-object-name-from-file ${addon_path}) - if [[ "${name_from_file}" == "ERROR" ]]; then + nsname_from_file=$(get-object-nsname-from-file ${addon_path}) + if [[ "${nsname_from_file}" == "ERROR" ]]; then log INFO "Cannot read object name from ${addon_path}. Ignoring" continue else - log DB2 "Found object name '${name_from_file}' in file ${addon_path}" + log DB2 "Found object name '${nsname_from_file}' in file ${addon_path}" fi - suffix_from_file="$(get-suffix ${name_from_file} ${separator})" + suffix_from_file="$(get-suffix ${nsname_from_file} ${separator})" - log DB3 "matching: ${basename_on_server}${suffix_from_file} == ${name_from_file}" - if [[ "${basename_on_server}${suffix_from_file}" == "${name_from_file}" ]]; then - log DB3 "matched existing ${obj_type} ${name_on_server} to file ${addon_path}; suffix_on_server=${suffix_on_server}, suffix_from_file=${suffix_from_file}" + log DB3 "matching: ${basensname_on_server}${suffix_from_file} == ${nsname_from_file}" + if [[ "${basensname_on_server}${suffix_from_file}" == "${nsname_from_file}" ]]; then + log DB3 "matched existing ${obj_type} ${nsname_on_server} to file ${addon_path}; suffix_on_server=${suffix_on_server}, suffix_from_file=${suffix_from_file}" found=1 matched_files="${matched_files} ${addon_path}" if [[ "${suffix_on_server}" == "${suffix_from_file}" ]]; then - for_ignore="${for_ignore} ${name_from_file}" + nsnames_for_ignore="${nsnames_for_ignore} ${nsname_from_file}" else - for_update="${for_update} ${name_on_server};${addon_path}" + for_update="${for_update} ${nsname_on_server};${addon_path}" fi break fi done if [[ ${found} -eq 0 ]]; then - log DB2 "No definition file found for replication controller ${name_on_server}. Scheduling for deletion" - for_delete="${for_delete} ${name_on_server}" + log DB2 "No definition file found for replication controller ${nsname_on_server}. Scheduling for deletion" + nsnames_for_delete="${nsnames_for_delete} ${nsname_on_server}" fi done @@ -395,12 +425,12 @@ function reconcile-objects() { local -r separator=$3 # name separator match-objects ${addon_path} ${obj_type} ${separator} - log DBG "${obj_type}: for_delete=${for_delete}" + log DBG "${obj_type}: nsnames_for_delete=${nsnames_for_delete}" log DBG "${obj_type}: for_update=${for_update}" - log DBG "${obj_type}: for_ignore=${for_ignore}" + log DBG "${obj_type}: nsnames_for_ignore=${nsnames_for_ignore}" log DBG "${obj_type}: new_files=${new_files}" - stop-objects "${obj_type}" "${for_delete}" + stop-objects "${obj_type}" "${nsnames_for_delete}" # wait for jobs below is a protection against changing the basename # of a replication controllerm without changing the selector. # If we don't wait, the new rc may be created before the old one is deleted @@ -414,9 +444,9 @@ function reconcile-objects() { create-objects "${obj_type}" "${new_files}" update-objects "${obj_type}" "${for_update}" - local obj - for obj in ${for_ignore}; do - log DB2 "The ${obj_type} ${obj} is already up to date" + local nsname + for nsname in ${nsnames_for_ignore}; do + log DB2 "The ${obj_type} ${nsname} is already up to date" done wait-for-jobs diff --git a/test/e2e/addon_update.go b/test/e2e/addon_update.go index 2c2710be114..b7d40b48050 100644 --- a/test/e2e/addon_update.go +++ b/test/e2e/addon_update.go @@ -39,7 +39,7 @@ apiVersion: v1 kind: ReplicationController metadata: name: addon-test-v1 - namespace: default + namespace: %s labels: k8s-app: addon-test version: v1 @@ -69,7 +69,7 @@ apiVersion: v1 kind: ReplicationController metadata: name: addon-test-v2 - namespace: default + namespace: %s labels: k8s-app: addon-test version: v2 @@ -99,7 +99,7 @@ apiVersion: v1 kind: Service metadata: name: addon-test - namespace: default + namespace: %s labels: k8s-app: addon-test kubernetes.io/cluster-service: "true" @@ -118,7 +118,7 @@ apiVersion: v1 kind: Service metadata: name: addon-test-updated - namespace: default + namespace: %s labels: k8s-app: addon-test kubernetes.io/cluster-service: "true" @@ -138,7 +138,7 @@ apiVersion: v1 kind: ReplicationController metadata: name: invalid-addon-test-v1 - namespace: default + namespace: %s labels: k8s-app: invalid-addon-test version: v1 @@ -167,7 +167,7 @@ apiVersion: v1 kind: Service metadata: name: ivalid-addon-test - namespace: default + namespace: %s labels: k8s-app: invalid-addon-test kubernetes.io/name: invalid-addon-test @@ -181,8 +181,8 @@ spec: ` var addonTestPollInterval = 3 * time.Second -var addonTestPollTimeout = 1 * time.Minute -var addonNamespace = api.NamespaceDefault // addons are in the default namespace +var addonTestPollTimeout = 5 * time.Minute +var defaultNsName = api.NamespaceDefault type stringPair struct { data, fileName string @@ -260,12 +260,12 @@ var _ = Describe("Addon update", func() { svcInvalid := "invalid-addon-service-v1.yaml" var remoteFiles []stringPair = []stringPair{ - {addon_controller_v1, rcv1}, - {addon_controller_v2, rcv2}, - {addon_service_v1, svcv1}, - {addon_service_v2, svcv2}, - {invalid_addon_controller_v1, rcInvalid}, - {invalid_addon_service_v1, svcInvalid}, + {fmt.Sprintf(addon_controller_v1, defaultNsName), rcv1}, + {fmt.Sprintf(addon_controller_v2, namespace.Name), rcv2}, + {fmt.Sprintf(addon_service_v1, namespace.Name), svcv1}, + {fmt.Sprintf(addon_service_v2, namespace.Name), svcv2}, + {fmt.Sprintf(invalid_addon_controller_v1, namespace.Name), rcInvalid}, + {fmt.Sprintf(invalid_addon_service_v1, defaultNsName), svcInvalid}, } for _, p := range remoteFiles { @@ -293,8 +293,8 @@ var _ = Describe("Addon update", func() { sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, rcv1, destinationDir, rcv1)) sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, svcv1, destinationDir, svcv1)) - waitForServiceInAddonTest(c, "addon-test", true) - waitForReplicationControllerInAddonTest(c, "addon-test-v1", true) + waitForServiceInAddonTest(c, namespace.Name, "addon-test", true) + waitForReplicationControllerInAddonTest(c, defaultNsName, "addon-test-v1", true) By("update manifests") sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, rcv2, destinationDir, rcv2)) @@ -307,34 +307,38 @@ var _ = Describe("Addon update", func() { * But it is ok - as long as we don't have rolling update, the result will be the same */ - waitForServiceInAddonTest(c, "addon-test-updated", true) - waitForReplicationControllerInAddonTest(c, "addon-test-v2", true) + waitForServiceInAddonTest(c, namespace.Name, "addon-test-updated", true) + waitForReplicationControllerInAddonTest(c, namespace.Name, "addon-test-v2", true) - waitForServiceInAddonTest(c, "addon-test", false) - waitForReplicationControllerInAddonTest(c, "addon-test-v1", false) + waitForServiceInAddonTest(c, namespace.Name, "addon-test", false) + waitForReplicationControllerInAddonTest(c, defaultNsName, "addon-test-v1", false) By("remove manifests") sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, rcv2)) sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, svcv2)) - waitForServiceInAddonTest(c, "addon-test-updated", false) - waitForReplicationControllerInAddonTest(c, "invalid-addon-test-v1", false) + waitForServiceInAddonTest(c, namespace.Name, "addon-test-updated", false) + waitForReplicationControllerInAddonTest(c, namespace.Name, "addon-test-v2", false) By("verify invalid API addons weren't created") - _, err = c.ReplicationControllers(addonNamespace).Get("invalid-addon-test-v1") + _, err = c.ReplicationControllers(namespace.Name).Get("invalid-addon-test-v1") Expect(err).To(HaveOccurred()) - _, err = c.Services(addonNamespace).Get("ivalid-addon-test") + _, err = c.ReplicationControllers(defaultNsName).Get("invalid-addon-test-v1") + Expect(err).To(HaveOccurred()) + _, err = c.Services(namespace.Name).Get("ivalid-addon-test") + Expect(err).To(HaveOccurred()) + _, err = c.Services(defaultNsName).Get("ivalid-addon-test") Expect(err).To(HaveOccurred()) // invalid addons will be deleted by the deferred function }) }) -func waitForServiceInAddonTest(c *client.Client, name string, exist bool) { +func waitForServiceInAddonTest(c *client.Client, addonNamespace, name string, exist bool) { expectNoError(waitForService(c, addonNamespace, name, exist, addonTestPollInterval, addonTestPollTimeout)) } -func waitForReplicationControllerInAddonTest(c *client.Client, name string, exist bool) { +func waitForReplicationControllerInAddonTest(c *client.Client, addonNamespace, name string, exist bool) { expectNoError(waitForReplicationController(c, addonNamespace, name, exist, addonTestPollInterval, addonTestPollTimeout)) }