blob: bd963eb8c9ea911e2c0942d20c7acc967f6b0d6a [file] [log] [blame]
Victor Ryzhenkinef34a022018-06-22 19:36:13 +04001/**
2 * Update kuberentes cluster
3 *
4 * Expected parameters:
Victor Ryzhenkin3401ee62019-01-18 06:34:26 +04005 * SALT_MASTER_CREDENTIALS Credentials to the Salt API.
6 * SALT_MASTER_URL Full Salt API address [https://10.10.10.1:8000].
7 * KUBERNETES_HYPERKUBE_SOURCE Versioned hyperkube binary to update control plane from. Should be null if update rolling via reclass-system level
8 * KUBERNETES_HYPERKUBE_SOURCE_HASH Versioned hyperkube binary to update control plane from. Should be null if update rolling via reclass-system level
9 * KUBERNETES_PAUSE_IMAGE Kubernetes pause image should have same version as hyperkube. May be null in case of reclass-system rollout
10 * TARGET_UPDATES Comma separated list of nodes to update (Valid values are ctl,cmp)
11 * CTL_TARGET Salt targeted kubernetes CTL nodes (ex. I@kubernetes:master). Kubernetes control plane
12 * CMP_TARGET Salt targeted compute nodes (ex. cmp* and 'I@kubernetes:pool') Kubernetes computes
13 * PER_NODE Target nodes will be managed one by one (bool)
14 * SIMPLE_UPGRADE Use previous version of upgrade without conron/drain abilities
15 * CONFORMANCE_RUN_AFTER Run Kubernetes conformance tests after update
16 * CONFORMANCE_RUN_BEFORE Run Kubernetes conformance tests before update
17 * TEST_K8S_API_SERVER Kubernetes API server address for test execution
18 * ARTIFACTORY_URL Artifactory URL where docker images located. Needed to correctly fetch conformance images.
19 * UPGRADE_CALICO_V2_TO_V3 Perform Calico upgrade from v2 to v3.
20 * KUBERNETES_CALICO_IMAGE Target calico/node image. May be null in case of reclass-system rollout.
21 * KUBERNETES_CALICO_CALICOCTL_SOURCE Versioned calico/ctl binary. Should be null if update rolling via reclass-system level
22 * KUBERNETES_CALICO_CALICOCTL_SOURCE_HASH Calico/ctl binary md5 hash. Should be null if update rolling via reclass-system level
23 * KUBERNETES_CALICO_CNI_SOURCE Versioned calico/cni binary. Should be null if update rolling via reclass-system level
24 * KUBERNETES_CALICO_CNI_SOURCE_HASH Сalico/cni binary hash. Should be null if update rolling via reclass-system level
25 * KUBERNETES_CALICO_BIRDCL_SOURCE Versioned calico/bird binary. Should be null if update rolling via reclass-system level
26 * KUBERNETES_CALICO_BIRDCL_SOURCE_HASH Сalico/bird binary hash. Should be null if update rolling via reclass-system level
27 * KUBERNETES_CALICO_CNI_IPAM_SOURCE Versioned calico/ipam binary. Should be null if update rolling via reclass-system level
28 * KUBERNETES_CALICO_CNI_IPAM_SOURCE_HASH Сalico/ipam binary hash. Should be null if update rolling via reclass-system level
29 * KUBERNETES_CALICO_KUBE_CONTROLLERS_IMAGE Target calico/kube-controllers image. May be null in case of reclass-system rollout.
30 * CALICO_UPGRADE_VERSION Version of "calico-upgrade" utility to be used ("v1.0.5" for Calico v3.1.3 target).
Victor Ryzhenkinef34a022018-06-22 19:36:13 +040031 *
32**/
Aleksei Kasatkin9ce11842018-11-23 14:27:33 +010033import groovy.json.JsonSlurper
34
Victor Ryzhenkinef34a022018-06-22 19:36:13 +040035def common = new com.mirantis.mk.Common()
36def salt = new com.mirantis.mk.Salt()
37def python = new com.mirantis.mk.Python()
Victor Ryzhenkin723bd062018-12-11 17:09:06 +040038def test = new com.mirantis.mk.Test()
Victor Ryzhenkinef34a022018-06-22 19:36:13 +040039
40def updates = TARGET_UPDATES.tokenize(",").collect{it -> it.trim()}
41def pepperEnv = "pepperEnv"
42
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +020043def POOL = "I@kubernetes:pool"
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +020044
Aleksei Kasatkin1f4f5ba2018-11-20 18:30:36 +010045ETCD_ENDPOINTS = ""
46
Victor Ryzhenkinef34a022018-06-22 19:36:13 +040047def overrideKubernetesImage(pepperEnv) {
48 def salt = new com.mirantis.mk.Salt()
49
50 def k8sSaltOverrides = """
Victor Ryzhenkin3401ee62019-01-18 06:34:26 +040051 kubernetes_hyperkube_source: ${KUBERNETES_HYPERKUBE_SOURCE}
52 kubernetes_hyperkube_source_hash: ${KUBERNETES_HYPERKUBE_SOURCE_HASH}
Victor Ryzhenkinef34a022018-06-22 19:36:13 +040053 kubernetes_pause_image: ${KUBERNETES_PAUSE_IMAGE}
54 """
55 stage("Override kubernetes images to target version") {
56 salt.setSaltOverrides(pepperEnv, k8sSaltOverrides)
57 }
58}
59
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +020060def overrideCalicoImages(pepperEnv) {
61 def salt = new com.mirantis.mk.Salt()
62
63 def calicoSaltOverrides = """
64 kubernetes_calico_image: ${KUBERNETES_CALICO_IMAGE}
Victor Ryzhenkin3401ee62019-01-18 06:34:26 +040065 kubernetes_calico_calicoctl_source: ${KUBERNETES_CALICO_CALICOCTL_SOURCE}
66 kubernetes_calico_calicoctl_source_hash: ${KUBERNETES_CALICO_CALICOCTL_SOURCE_HASH}
67 kubernetes_calico_birdcl_source: ${KUBERNETES_CALICO_BIRDCL_SOURCE}
68 kubernetes_calico_birdcl_source_hash: ${KUBERNETES_CALICO_BIRDCL_SOURCE_HASH}
69 kubernetes_calico_cni_source: ${KUBERNETES_CALICO_CNI_SOURCE}
70 kubernetes_calico_cni_source_hash: ${KUBERNETES_CALICO_CNI_SOURCE_HASH}
71 kubernetes_calico_cni_ipam_source: ${KUBERNETES_CALICO_CNI_IPAM_SOURCE}
72 kubernetes_calico_cni_ipam_source_hash: ${KUBERNETES_CALICO_CNI_IPAM_SOURCE_HASH}
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +020073 kubernetes_calico_kube_controllers_image: ${KUBERNETES_CALICO_KUBE_CONTROLLERS_IMAGE}
74 """
75 stage("Override calico images to target version") {
76 salt.setSaltOverrides(pepperEnv, calicoSaltOverrides)
77 }
78}
79
80def downloadCalicoUpgrader(pepperEnv, target) {
81 def salt = new com.mirantis.mk.Salt()
82
83 stage("Downloading calico-upgrade utility") {
84 salt.cmdRun(pepperEnv, target, "rm -f ./calico-upgrade")
85 salt.cmdRun(pepperEnv, target, "wget https://github.com/projectcalico/calico-upgrade/releases/download/${CALICO_UPGRADE_VERSION}/calico-upgrade")
86 salt.cmdRun(pepperEnv, target, "chmod +x ./calico-upgrade")
87 }
88}
89
Victor Ryzhenkinef34a022018-06-22 19:36:13 +040090def performKubernetesComputeUpdate(pepperEnv, target) {
91 def salt = new com.mirantis.mk.Salt()
92
93 stage("Execute Kubernetes compute update on ${target}") {
94 salt.enforceState(pepperEnv, target, 'kubernetes.pool')
95 salt.runSaltProcessStep(pepperEnv, target, 'service.restart', ['kubelet'])
96 }
97}
98
99def performKubernetesControlUpdate(pepperEnv, target) {
100 def salt = new com.mirantis.mk.Salt()
101
102 stage("Execute Kubernetes control plane update on ${target}") {
Victor Ryzhenkinc2024132019-01-23 05:39:34 +0400103 salt.enforceStateWithExclude(pepperEnv, target, "kubernetes", "kubernetes.master.setup,kubernetes.master.kube-addons")
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400104 // Restart kubelet
105 salt.runSaltProcessStep(pepperEnv, target, 'service.restart', ['kubelet'])
106 }
107}
108
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200109def startCalicoUpgrade(pepperEnv, target) {
110 def salt = new com.mirantis.mk.Salt()
111
112 stage("Starting upgrade using calico-upgrade: migrate etcd schema and lock Calico") {
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200113 def cmd = "export APIV1_ETCD_ENDPOINTS=${ETCD_ENDPOINTS} && " +
114 "export APIV1_ETCD_CA_CERT_FILE=/var/lib/etcd/ca.pem && " +
115 "export APIV1_ETCD_CERT_FILE=/var/lib/etcd/etcd-client.crt && " +
116 "export APIV1_ETCD_KEY_FILE=/var/lib/etcd/etcd-client.key && " +
117 "export ETCD_ENDPOINTS=${ETCD_ENDPOINTS} && " +
118 "export ETCD_CA_CERT_FILE=/var/lib/etcd/ca.pem && " +
119 "export ETCD_CERT_FILE=/var/lib/etcd/etcd-client.crt && " +
120 "export ETCD_KEY_FILE=/var/lib/etcd/etcd-client.key && " +
121 "rm /root/upg_complete -f && " +
122 "./calico-upgrade start --no-prompts --ignore-v3-data > upgrade-start.log && " +
123 "until [ -f /root/upg_complete ]; do sleep 0.1; done && " +
124 "./calico-upgrade complete --no-prompts > upgrade-complete.log && " +
125 "rm /root/upg_complete -f"
126 // "saltArgs = ['async']" doesn't work, so we have to run "cmd.run --async"
127 salt.cmdRun(pepperEnv, "I@salt:master", "salt -C '${target}' cmd.run '${cmd}' --async")
128 salt.cmdRun(pepperEnv, target, "until [ -f /root/upgrade-start.log ]; do sleep 0.1; done")
129 }
130}
131
132def completeCalicoUpgrade(pepperEnv, target) {
133 def salt = new com.mirantis.mk.Salt()
134
135 stage("Complete upgrade using calico-upgrade: unlock Calico") {
136 salt.cmdRun(pepperEnv, target, "echo 'true' > /root/upg_complete")
137 salt.cmdRun(pepperEnv, target, "while [ -f /root/upg_complete ]; do sleep 0.1; done")
138 salt.cmdRun(pepperEnv, target, "cat /root/upgrade-start.log")
139 salt.cmdRun(pepperEnv, target, "cat /root/upgrade-complete.log")
140 }
141}
142
Aleksei Kasatkind9d682e2018-12-12 14:51:59 +0100143def performCalicoConfigurationUpdateAndServicesRestart(pepperEnv, target, ctl_node) {
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200144 def salt = new com.mirantis.mk.Salt()
145
146 stage("Performing Calico configuration update and services restart") {
Aleksei Kasatkind9d682e2018-12-12 14:51:59 +0100147 if (containerDenabled(pepperEnv, ctl_node)) {
148 salt.enforceState(pepperEnv, target, "kubernetes.pool")
149 } else {
150 salt.enforceState(pepperEnv, target, "kubernetes.pool.calico")
151 }
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200152 salt.runSaltProcessStep(pepperEnv, target, 'service.restart', ['kubelet'])
153 }
154}
155
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400156def cordonNode(pepperEnv, target) {
157 def salt = new com.mirantis.mk.Salt()
158 def originalTarget = "I@kubernetes:master and not ${target}"
159
160 stage("Cordoning ${target} kubernetes node") {
161 def nodeShortName = target.tokenize(".")[0]
162 salt.cmdRun(pepperEnv, originalTarget, "kubectl cordon ${nodeShortName}", true, 1)
163 }
164}
165
166def uncordonNode(pepperEnv, target) {
167 def salt = new com.mirantis.mk.Salt()
168 def originalTarget = "I@kubernetes:master and not ${target}"
169
170 stage("Uncordoning ${target} kubernetes node") {
171 def nodeShortName = target.tokenize(".")[0]
172 salt.cmdRun(pepperEnv, originalTarget, "kubectl uncordon ${nodeShortName}", true, 1)
173 }
174}
175
176def drainNode(pepperEnv, target) {
177 def salt = new com.mirantis.mk.Salt()
178 def originalTarget = "I@kubernetes:master and not ${target}"
179
180 stage("Draining ${target} kubernetes node") {
181 def nodeShortName = target.tokenize(".")[0]
182 salt.cmdRun(pepperEnv, originalTarget, "kubectl drain --force --ignore-daemonsets --grace-period 100 --timeout 300s --delete-local-data ${nodeShortName}", true, 1)
183 }
184}
185
186def regenerateCerts(pepperEnv, target) {
187 def salt = new com.mirantis.mk.Salt()
188
189 stage("Regenerate certs for ${target}") {
190 salt.enforceState(pepperEnv, target, 'salt.minion.cert')
191 }
192}
193
Victor Ryzhenkinae909182018-10-02 17:49:18 +0400194def updateAddons(pepperEnv, target) {
195 def salt = new com.mirantis.mk.Salt()
196
197 stage("Upgrading Addons at ${target}") {
Victor Ryzhenkin40625bc2018-10-04 16:15:27 +0400198 salt.enforceState(pepperEnv, target, "kubernetes.master.kube-addons")
Victor Ryzhenkinfd9677f2018-10-16 16:14:40 +0400199 }
200}
201
202def updateAddonManager(pepperEnv, target) {
203 def salt = new com.mirantis.mk.Salt()
204
205 stage("Upgrading AddonManager at ${target}") {
Victor Ryzhenkinae909182018-10-02 17:49:18 +0400206 salt.enforceState(pepperEnv, target, "kubernetes.master.setup")
207 }
208}
209
Victor Ryzhenkinc2024132019-01-23 05:39:34 +0400210def buildDaemonsetMap(pepperEnv, target) {
211 def salt = new com.mirantis.mk.Salt()
212 def daemonset_lists
213 daemonset_lists = salt.cmdRun(pepperEnv, target, "kubectl get ds --all-namespaces | tail -n+2 | awk '{print \$2, \$1}'"
214 )['return'][0].values()[0].replaceAll('Salt command execution success','').tokenize("\n")
215 def daemonset_map = []
216 for (ds in daemonset_lists) {
217 a = ds.tokenize(" ")
218 daemonset_map << a
219 }
220 print("Built daemonset map")
221 print(daemonset_map)
222 return daemonset_map
223}
224
225def purgeDaemonsetPods(pepperEnv, target, daemonSetMap) {
226 def salt = new com.mirantis.mk.Salt()
227 def originalTarget = "I@kubernetes:master and not ${target}"
228 def nodeShortName = target.tokenize(".")[0]
229 firstTarget = salt.getFirstMinion(pepperEnv, originalTarget)
230
231 if (daemonSetMap) {
232 stage("Purging daemonset-managed pods on ${target}") {
233 for (ds in daemonSetMap) {
234 print("Purging "+ ds[0] +" inside "+ ds[1] +" namespace")
235 salt.cmdRun(pepperEnv, firstTarget, "kubectl get po -n ${ds[1]} -o wide | grep ${nodeShortName}" +
236 " | grep ${ds[0]} | awk '{print \$1}' | xargs --no-run-if-empty kubectl delete po -n ${ds[1]} --grace-period=0 --force")
237 }
238 }
239 }
240}
241
242def isNodeReady(pepperEnv, target) {
243 def salt = new com.mirantis.mk.Salt()
244 def originalTarget = "I@kubernetes:master and not ${target}"
245 def nodeShortName = target.tokenize(".")[0]
246 firstTarget = salt.getFirstMinion(pepperEnv, originalTarget)
247
248 status = salt.cmdRun(pepperEnv, firstTarget, "kubectl get no | grep ${nodeShortName} | awk '{print \$2}'"
249 )['return'][0].values()[0].replaceAll('Salt command execution success',''
250 ).replaceAll(',SchedulingDisabled','').trim()
251
252 if (status == "Ready") {
253 return true
254 } else {
255 return false
256 }
257}
258
259def rebootKubernetesNode(pepperEnv, target, times=15, delay=10) {
260 def common = new com.mirantis.mk.Common()
261 def debian = new com.mirantis.mk.Debian()
262
263 stage("Rebooting ${target}") {
264 debian.osReboot(pepperEnv, target)
265 common.retry(times, delay) {
266 if(!isNodeReady(pepperEnv, target)) {
267 error("Node still not in Ready state...")
268 }
269 }
270 }
271}
272
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400273def upgradeDocker(pepperEnv, target) {
274 def salt = new com.mirantis.mk.Salt()
275
276 stage("Upgrading docker at ${target}") {
277 salt.enforceState(pepperEnv, target, 'docker.host')
278 }
279}
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400280
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400281def runConformance(pepperEnv, target, k8s_api, image) {
282 def salt = new com.mirantis.mk.Salt()
283 def containerName = 'conformance_tests'
284 output_file = image.replaceAll('/', '-') + '.output'
285 def output_file_full_path = "/tmp/" + image.replaceAll('/', '-') + '.output'
286 def artifacts_dir = '_artifacts/'
287 salt.cmdRun(pepperEnv, target, "docker rm -f ${containerName}", false)
288 salt.cmdRun(pepperEnv, target, "docker run -d --name ${containerName} --net=host -e API_SERVER=${k8s_api} ${image}")
289 sleep(10)
290
291 print("Waiting for tests to run...")
292 salt.runSaltProcessStep(pepperEnv, target, 'cmd.run', ["docker wait ${containerName}"], null, false)
293
294 print("Writing test results to output file...")
295 salt.runSaltProcessStep(pepperEnv, target, 'cmd.run', ["docker logs -t ${containerName} > ${output_file_full_path}"])
296 print("Conformance test output saved in " + output_file_full_path)
297
298 // collect output
299 sh "mkdir -p ${artifacts_dir}"
300 file_content = salt.getFileContent(pepperEnv, target, '/tmp/' + output_file)
301 writeFile file: "${artifacts_dir}${output_file}", text: file_content
302 sh "cat ${artifacts_dir}${output_file}"
303 try {
304 sh "cat ${artifacts_dir}${output_file} | grep 'Test Suite Failed' && exit 1 || exit 0"
305 } catch (Throwable e) {
306 print("Conformance tests failed. Please check output")
307 currentBuild.result = "FAILURE"
308 currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
309 throw e
310 }
311}
312
313def buildImageURL(pepperEnv, target, mcp_repo) {
314 def salt = new com.mirantis.mk.Salt()
315 def raw_version = salt.cmdRun(pepperEnv, target, "kubectl version --short -o json")['return'][0].values()[0].replaceAll('Salt command execution success','')
316 print("Kubernetes version: " + raw_version)
317 def serialized_version = readJSON text: raw_version
318 def short_version = (serialized_version.serverVersion.gitVersion =~ /([v])(\d+\.)(\d+\.)(\d+\-)(\d+)/)[0][0]
319 print("Kubernetes short version: " + short_version)
320 def conformance_image = mcp_repo + "/mirantis/kubernetes/k8s-conformance:" + short_version
321 return conformance_image
322}
323
324def executeConformance(pepperEnv, target, k8s_api, mcp_repo) {
325 stage("Running conformance tests") {
326 def image = buildImageURL(pepperEnv, target, mcp_repo)
327 print("Using image: " + image)
328 runConformance(pepperEnv, target, k8s_api, image)
329 }
330}
331
Victor Ryzhenkin723bd062018-12-11 17:09:06 +0400332def containerDinstalled(pepperEnv, target) {
333 def salt = new com.mirantis.mk.Salt()
334 return salt.cmdRun(pepperEnv, target, "containerd --version 2>1 1>/dev/null && echo 'true' || echo 'false'"
335 )['return'][0].values()[0].replaceAll('Salt command execution success','').trim().toBoolean()
336}
337
338def containerDenabled(pepperEnv, target) {
339 def salt = new com.mirantis.mk.Salt()
Victor Ryzhenkin71ecdf42018-12-11 22:22:50 +0400340 return salt.getPillar(pepperEnv, target, "kubernetes:common:containerd:enabled"
Victor Ryzhenkin723bd062018-12-11 17:09:06 +0400341 )["return"][0].values()[0].toBoolean()
342}
343
344def conformancePodDefExists(pepperEnv, target) {
345 def salt = new com.mirantis.mk.Salt()
346 return salt.cmdRun(pepperEnv, target, "test -e /srv/kubernetes/conformance.yml && echo 'true' || echo 'false'"
347 )['return'][0].values()[0].replaceAll('Salt command execution success','').trim().toBoolean()
348}
349
Victor Ryzhenkin3401ee62019-01-18 06:34:26 +0400350def printVersionInfo(pepperEnv, target) {
351 def salt = new com.mirantis.mk.Salt()
352 def common = new com.mirantis.mk.Common()
353
354 stage("Gather version and runtime information") {
355 common.infoMsg("Version and runtime info:")
356 salt.cmdRun(pepperEnv, target, "kubectl get no -o wide")
357 common.infoMsg("Cluster health info:")
358 salt.cmdRun(pepperEnv, target, "kubectl get cs")
359 common.infoMsg("ETCD health info:")
Victor Ryzhenkin3029c8b2019-01-18 22:17:57 +0400360 salt.cmdRun(pepperEnv, target, ". /var/lib/etcd/configenv && etcdctl cluster-health")
Victor Ryzhenkin3401ee62019-01-18 06:34:26 +0400361 common.infoMsg("Calico peers info:")
362 salt.cmdRun(pepperEnv, target, "calicoctl node status")
363 }
364}
365
Aleksei Kasatkin5ccea272018-12-06 17:34:58 +0100366def calicoEnabled(pepperEnv, target) {
367 def salt = new com.mirantis.mk.Salt()
368 return salt.getPillar(pepperEnv, target, "kubernetes:pool:network:calico:enabled"
369 )["return"][0].values()[0].toBoolean()
370}
371
372def checkCalicoClusterState(pepperEnv, target) {
373 def common = new com.mirantis.mk.Common()
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200374 def salt = new com.mirantis.mk.Salt()
375
Aleksei Kasatkin5ccea272018-12-06 17:34:58 +0100376 stage("Checking Calico cluster state after upgrade") {
377 // check Calico cluster and cli clients versions
378 def checkVer = [
379 "Client Version:": [verStr: "", dif: false, wrong: false],
380 "Cluster Version:": [verStr: "", dif: false, wrong: false]
381 ]
382 def checkVerPassed = true
383 def versionResults = salt.cmdRun(pepperEnv, target, "calicoctl version | grep -i version")['return'][0]
384 versionResults.each { k, v ->
385 // println("Node:\n${k}\nResult:\n${v}")
386 for (verLine in v.split("\n")) {
387 for (verType in checkVer.keySet()) {
388 if (verLine.contains(verType)) {
389 def verRec = checkVer[verType]
390 ver = (verLine - verType).trim()
391 if (!verRec.verStr) {
392 verRec.verStr = ver
393 }
394 if (verRec.verStr != ver) {
395 verRec.dif = true
396 checkVerPassed = false
397 }
398 version = ver.tokenize(".")
399 if ((version.size() < 3) || (version[0] != "v3")) {
400 verRec.wrong = true
401 checkVerPassed = false
402 }
403 checkVer[verType] = verRec
404 }
405 }
406 }
407 }
408 if (checkVerPassed) {
409 common.infoMsg("Calico version verification passed")
410 }
411 else {
412 def warningMsg = "Calico version verification failed.\n"
413 checkVer.each { k, rec ->
414 if (rec.dif) {
415 warningMsg += "${k} versions are different across nodes.\n"
416 }
417 if (rec.wrong) {
418 warningMsg += "${k} (some) versions are wrong - should be v3.x.\n"
419 }
420 }
421 common.warningMsg(warningMsg)
422 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
423 }
424
425 // check Calico nodes' statuses
426 def nodeStatusResults = salt.cmdRun(pepperEnv, target, "calicoctl node status")['return'][0]
427 def nodesRunning = true
428 def peersNotFound = []
429 def peersNotOnline = []
430 nodeStatusResults.each { k, v ->
431 // println("Node:\n${k}\nResult:\n${v}")
432 if (!v.contains("Calico process is running")) {
433 nodesRunning = false
434 def warningMsg = "Node ${k}: Calico node is not running."
435 common.warningMsg(warningMsg)
436 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
437 }
438 def nodePeersFound = false
439 def nodePeersOnline = true
440 for (nodeLine in v.split("\n")) {
441 if (nodeLine.contains("|") && (!nodeLine.contains("STATE"))) {
442 def col = nodeLine.tokenize("|").collect{it.trim()}
443 if (col.size() == 5) {
444 nodePeersFound = true
445 if ((col[2] != "up") || (col[4] != "Established")) {
446 def warningMsg = "Node ${k}: BGP peer '${col[0]}' is out of reach. Peer state: '${col[2]}', connection info: '${col[4]}'."
447 common.warningMsg(warningMsg)
448 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
449 nodePeersOnline = false
450 }
451 }
452 }
453 }
454 if (!nodePeersFound) {
455 peersNotFound += k
456 }
457 if (!nodePeersOnline) {
458 peersNotOnline += k
459 }
460 }
461 if (nodesRunning) {
462 common.infoMsg("All the Calico nodes are running")
463 }
464 if (peersNotFound) {
465 def warningMsg = "BGP peers not found for the node(s): " + peersNotFound.join(', ') + "."
466 common.warningMsg(warningMsg)
467 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
468 } else {
469 common.infoMsg("BGP peers were found for all the nodes")
470 }
471 if (!peersNotOnline) {
472 common.infoMsg("All reported BGP peers are reachable")
473 }
474
475 // check that 'calico-kube-controllers' is running
476 // one CTL node will be used to get pod's state using kubectl
477 def ctl_node = salt.getMinionsSorted(pepperEnv, CTL_TARGET)[0]
478 def kubeCtrlResult = salt.cmdRun(
479 pepperEnv, ctl_node, "kubectl get pod -n kube-system --selector=k8s-app=calico-kube-controllers"
480 )['return'][0].values()[0].toString()
481 if (kubeCtrlResult.contains("calico-kube-controllers")) {
482 for (line in kubeCtrlResult.split("\n")) {
483 if (line.contains("calico-kube-controllers")) {
484 col = line.tokenize(" ")
485 if ((col[1] != "1/1") || (col[2] != "Running")) {
486 def warningMsg = "Calico kube-controllers pod is not running properly."
487 common.warningMsg(warningMsg)
488 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
489 }
490 else {
491 common.infoMsg("Calico kube-controllers pod is running.")
492 }
493 break
494 }
495 }
496 } else {
497 def warningMsg = "Calico kube-controllers pod was not scheduled."
498 common.warningMsg(warningMsg)
499 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
500 }
Aleksei Kasatkin1f4f5ba2018-11-20 18:30:36 +0100501 }
502}
503
504def checkCalicoUpgradePossibility(pepperEnv, target) {
505 def salt = new com.mirantis.mk.Salt()
506
507 stage("Verification of Calico upgrade possibility") {
508 // check Calico version
509 def versionResult = salt.cmdRun(
510 pepperEnv, target, "calicoctl version | grep 'Cluster Version'"
511 )['return'][0].values()[0].split("\n")[0].trim()
512 versionStr = (versionResult - "Cluster Version:").trim()
513 version = versionStr.tokenize(".")
514 if ((version.size() < 3) || (version[0] != "v2") || (version[1] != "6") || (version[2].toInteger() < 5)) {
515 error(
516 "Current Calico ${versionStr} cannot be upgraded to v3.x. " +
517 "Calico v2.6.x starting from v2.6.5 can be upgraded. " +
518 "For earlier versions, please update to v2.6.5 first."
519 )
520 }
521 print("Calico version was determined: ${versionStr}")
522
523 // check Calico is switched on
524 def readinessResult = salt.cmdRun(
525 pepperEnv, target, ". /var/lib/etcd/configenv && etcdctl get /calico/v1/Ready"
526 )['return'][0].values()[0].split("\n")[0].trim()
527 print("Calico readiness check result: ${readinessResult}")
528 if (readinessResult != "true") {
529 // try set it to true
530 readinessResult = salt.cmdRun(
531 pepperEnv, target, ". /var/lib/etcd/configenv && etcdctl set /calico/v1/Ready true"
532 )['return'][0].values()[0].split("\n")[0].trim()
533 print("Calico readiness result 2nd attempt: ${readinessResult}")
534 if (readinessResult != "true") {
535 error("Calico is not ready. '/calico/v1/Ready': '${readinessResult}'")
536 }
537 }
538
539 // Calico data upgrade dry-run
540 def cmd = "export APIV1_ETCD_ENDPOINTS=${ETCD_ENDPOINTS} && " +
541 "export APIV1_ETCD_CA_CERT_FILE=/var/lib/etcd/ca.pem && " +
542 "export APIV1_ETCD_CERT_FILE=/var/lib/etcd/etcd-client.crt && " +
543 "export APIV1_ETCD_KEY_FILE=/var/lib/etcd/etcd-client.key && " +
544 "export ETCD_ENDPOINTS=${ETCD_ENDPOINTS} && " +
545 "export ETCD_CA_CERT_FILE=/var/lib/etcd/ca.pem && " +
546 "export ETCD_CERT_FILE=/var/lib/etcd/etcd-client.crt && " +
547 "export ETCD_KEY_FILE=/var/lib/etcd/etcd-client.key && " +
548 "./calico-upgrade dry-run --ignore-v3-data"
549 def dryRunResult = salt.cmdRun(pepperEnv, target, cmd)['return'][0].values()[0]
550 // check dry-run result
551 def validationSuccessStr = "Successfully validated v1 to v3 conversion"
552 if (!dryRunResult.contains(validationSuccessStr)) {
553 error("Calico data upgrade dry-run has failed")
554 }
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200555 }
556}
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400557
Aleksei Kasatkin9ce11842018-11-23 14:27:33 +0100558def checkCalicoPolicySetting(pepperEnv, target) {
559 def common = new com.mirantis.mk.Common()
560 def salt = new com.mirantis.mk.Salt()
561
562 stage("Checking of Calico network policy setting") {
563 // check Calico policy enabled
564 def cniPolicy = false
565 def addonsPolicy = false
566 def kubeCtrlRunning = false
567
568 // check CNI config
569 def cniCfgResult = salt.cmdRun(
570 pepperEnv, target, "cat /etc/cni/net.d/10-calico.conf"
571 )['return'][0].values()[0].toString()
572 def cniCfg = new JsonSlurper().parseText(cniCfgResult)
573 if (cniCfg.get("policy") != null) {
574 if (cniCfg["policy"].get("type") == "k8s") {
575 cniPolicy = true
576 } else {
577 common.warningMsg("Calico policy type is unknown or not set.")
578 }
579 }
580
581 // check k8s addons
582 def addonsResult = salt.cmdRun(
583 pepperEnv, target, "ls /etc/kubernetes/addons"
584 )['return'][0].values()[0].toString()
585 if (addonsResult.contains("calico_policy")) {
586 addonsPolicy = true
587 }
588
589 // check kube-controllers is running
590 def kubeCtrlResult = salt.cmdRun(
591 pepperEnv, target, "kubectl get pod -n kube-system --selector=k8s-app=calico-kube-controllers"
592 )['return'][0].values()[0].toString()
593 if (kubeCtrlResult.contains("Running")) {
594 kubeCtrlRunning = true
595 }
596
597 // It's safe to enable Calico policy any time, but it may be unsafe to disable it.
598 // So, no need to disable Calico policy for v3.x if it's not in use currently.
599 // But if Calico policy is in use already, it should be enabled after upgrade as well.
600
601 // check for consistency
602 if ((cniPolicy != addonsPolicy) || (addonsPolicy != kubeCtrlRunning)) {
603 caution = "ATTENTION. Calico policy setting cannot be determined reliably (enabled in CNI config: ${cniPolicy}, " +
604 "presence in k8s addons: ${addonsPolicy}, kube-controllers is running: ${kubeCtrlRunning})."
605 currentBuild.description += "<br><b>${caution}</b><br><br>"
606 common.warningMsg(caution)
607 } else {
608 common.infoMsg("Current Calico policy state is detected as: ${cniPolicy}")
609 if (cniPolicy) {
610 // Calico policy is in use. Check policy setting for v3.x.
611 common.infoMsg("Calico policy is in use. It should be enabled for v3.x as well.")
612 def saltPolicyResult = salt.getPillar(
613 pepperEnv, target, "kubernetes:pool:network:calico:policy"
614 )["return"][0].values()[0].toString()
615
616 common.infoMsg("kubernetes.pool.network.calico.policy: ${saltPolicyResult}")
617 if (saltPolicyResult.toLowerCase().contains("true")) {
618 common.infoMsg("Calico policy setting for v3.x is detected as: true")
619 } else {
620 caution = "ATTENTION. Currently, Calico is running with policy switched on. " +
621 "Calico policy setting for v3.x is not set to true. " +
622 "After upgrade is completed, Calico policy will be switched off. " +
623 "You will need to switch it on manually if required."
624 currentBuild.description += "<br><b>${caution}</b><br><br>"
625 common.warningMsg(caution)
626 }
627 }
628 }
629
630 if (addonsPolicy) {
631 // Remove v2.6.x policy-related addons on masters to not interfere with v3.x kube-controllers
632 salt.cmdRun(pepperEnv, CTL_TARGET, "rm -rf /etc/kubernetes/addons/calico_policy")
633 }
634 }
635}
636
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400637timeout(time: 12, unit: 'HOURS') {
638 node() {
639 try {
640
641 stage("Setup virtualenv for Pepper") {
642 python.setupPepperVirtualenv(pepperEnv, SALT_MASTER_URL, SALT_MASTER_CREDENTIALS)
643 }
644
Victor Ryzhenkinc2024132019-01-23 05:39:34 +0400645 def ctl_node = salt.getMinionsSorted(pepperEnv, CTL_TARGET)[0]
646 def daemonsetMap = buildDaemonsetMap(pepperEnv, ctl_node)
647
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400648 if (CONFORMANCE_RUN_BEFORE.toBoolean()) {
649 def target = CTL_TARGET
650 def mcp_repo = ARTIFACTORY_URL
651 def k8s_api = TEST_K8S_API_SERVER
652 firstTarget = salt.getFirstMinion(pepperEnv, target)
Victor Ryzhenkin723bd062018-12-11 17:09:06 +0400653 def containerd_enabled = containerDenabled(pepperEnv, firstTarget)
654 def containerd_installed = containerDinstalled(pepperEnv, firstTarget)
655 def conformance_pod_ready = conformancePodDefExists(pepperEnv, firstTarget)
656 if (containerd_enabled && containerd_installed && conformance_pod_ready) {
657 def config = ['master': pepperEnv,
658 'target': firstTarget,
659 'junitResults': false,
660 'autodetect': true]
661 test.executeConformance(config)
662 } else {
663 executeConformance(pepperEnv, firstTarget, k8s_api, mcp_repo)
664 }
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400665 }
666
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400667 if ((common.validInputParam('KUBERNETES_HYPERKUBE_IMAGE')) && (common.validInputParam('KUBERNETES_PAUSE_IMAGE'))) {
668 overrideKubernetesImage(pepperEnv)
669 }
670
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200671 if ((common.validInputParam('KUBERNETES_CALICO_IMAGE'))
Victor Ryzhenkin3401ee62019-01-18 06:34:26 +0400672 && (common.validInputParam('KUBERNETES_CALICO_CALICOCTL_SOURCE'))
673 && (common.validInputParam('KUBERNETES_CALICO_CALICOCTL_SOURCE_HASH'))
674 && (common.validInputParam('KUBERNETES_CALICO_CNI_SOURCE'))
675 && (common.validInputParam('KUBERNETES_CALICO_CNI_SOURCE_HASH'))
676 && (common.validInputParam('KUBERNETES_CALICO_BIRDCL_SOURCE'))
677 && (common.validInputParam('KUBERNETES_CALICO_BIRDCL_SOURCE_HASH'))
678 && (common.validInputParam('KUBERNETES_CALICO_CNI_IPAM_SOURCE'))
679 && (common.validInputParam('KUBERNETES_CALICO_CNI_IPAM_SOURCE_HASH'))
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200680 && (common.validInputParam('KUBERNETES_CALICO_KUBE_CONTROLLERS_IMAGE'))
681 ) {
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200682 overrideCalicoImages(pepperEnv)
683 }
684
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400685 /*
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200686 * Execute Calico upgrade if needed (only for v2 to v3 upgrade).
687 * This part causes workloads operations downtime.
688 * It is only required for Calico v2.x to v3.x upgrade when etcd is in use for Calico
689 * as Calico etcd schema has different formats for Calico v2.x and Calico v3.x.
690 */
691 if (UPGRADE_CALICO_V2_TO_V3.toBoolean()) {
Aleksei Kasatkin1f4f5ba2018-11-20 18:30:36 +0100692 // get ETCD_ENDPOINTS in use by Calico
693 def ep_str = salt.cmdRun(pepperEnv, ctl_node, "cat /etc/calico/calicoctl.cfg | grep etcdEndpoints")['return'][0].values()[0]
694 ETCD_ENDPOINTS = ep_str.split("\n")[0].tokenize(' ')[1]
695 print("ETCD_ENDPOINTS in use by Calico: '${ETCD_ENDPOINTS}'")
696
697 // download calico-upgrade utility
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200698 downloadCalicoUpgrader(pepperEnv, ctl_node)
Aleksei Kasatkin1f4f5ba2018-11-20 18:30:36 +0100699
700 // check the possibility of upgrading of Calico
701 checkCalicoUpgradePossibility(pepperEnv, ctl_node)
702
Aleksei Kasatkin9ce11842018-11-23 14:27:33 +0100703 // check and adjust Calico policy setting
704 checkCalicoPolicySetting(pepperEnv, ctl_node)
705
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200706 // this sequence implies workloads operations downtime
707 startCalicoUpgrade(pepperEnv, ctl_node)
Aleksei Kasatkind9d682e2018-12-12 14:51:59 +0100708 performCalicoConfigurationUpdateAndServicesRestart(pepperEnv, POOL, ctl_node)
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200709 completeCalicoUpgrade(pepperEnv, ctl_node)
Aleksei Kasatkin5ccea272018-12-06 17:34:58 +0100710 // no downtime is expected after this point
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200711 }
712
713 /*
714 * Execute k8s update
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400715 */
716 if (updates.contains("ctl")) {
717 def target = CTL_TARGET
718
719 if (PER_NODE.toBoolean()) {
720 def targetHosts = salt.getMinionsSorted(pepperEnv, target)
721
722 for (t in targetHosts) {
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400723 if (SIMPLE_UPGRADE.toBoolean()) {
724 performKubernetesControlUpdate(pepperEnv, t)
725 } else {
726 cordonNode(pepperEnv, t)
727 drainNode(pepperEnv, t)
728 regenerateCerts(pepperEnv, t)
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400729 performKubernetesControlUpdate(pepperEnv, t)
Victor Ryzhenkinfd9677f2018-10-16 16:14:40 +0400730 updateAddonManager(pepperEnv, t)
Victor Ryzhenkinc2024132019-01-23 05:39:34 +0400731 if (daemonsetMap) {
732 purgeDaemonsetPods(pepperEnv, t, daemonsetMap)
733 rebootKubernetesNode(pepperEnv, t)
734 }
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400735 uncordonNode(pepperEnv, t)
736 }
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400737 }
738 } else {
739 performKubernetesControlUpdate(pepperEnv, target)
740 }
Victor Ryzhenkinfd9677f2018-10-16 16:14:40 +0400741 if (!SIMPLE_UPGRADE.toBoolean()) {
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200742 // Addons upgrade should be performed after all nodes will be upgraded
Victor Ryzhenkinfd9677f2018-10-16 16:14:40 +0400743 updateAddons(pepperEnv, target)
744 // Wait for 90 sec for addons reconciling
745 sleep(90)
746 }
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400747 }
748
749 if (updates.contains("cmp")) {
750 def target = CMP_TARGET
751
752 if (PER_NODE.toBoolean()) {
753 def targetHosts = salt.getMinionsSorted(pepperEnv, target)
754
755 for (t in targetHosts) {
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400756 if (SIMPLE_UPGRADE.toBoolean()) {
757 performKubernetesComputeUpdate(pepperEnv, t)
758 } else {
759 cordonNode(pepperEnv, t)
760 drainNode(pepperEnv, t)
761 regenerateCerts(pepperEnv, t)
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400762 performKubernetesComputeUpdate(pepperEnv, t)
Victor Ryzhenkinc2024132019-01-23 05:39:34 +0400763 if (daemonsetMap) {
764 purgeDaemonsetPods(pepperEnv, t, daemonsetMap)
765 rebootKubernetesNode(pepperEnv, t)
766 }
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400767 uncordonNode(pepperEnv, t)
768 }
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400769 }
770 } else {
771 performKubernetesComputeUpdate(pepperEnv, target)
772 }
773 }
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400774
Aleksei Kasatkin5ccea272018-12-06 17:34:58 +0100775 if (calicoEnabled(pepperEnv, ctl_node)) {
776 checkCalicoClusterState(pepperEnv, POOL)
777 }
Victor Ryzhenkin3401ee62019-01-18 06:34:26 +0400778 printVersionInfo(pepperEnv, ctl_node)
Aleksei Kasatkin5ccea272018-12-06 17:34:58 +0100779
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400780 if (CONFORMANCE_RUN_AFTER.toBoolean()) {
781 def target = CTL_TARGET
782 def mcp_repo = ARTIFACTORY_URL
783 def k8s_api = TEST_K8S_API_SERVER
784 firstTarget = salt.getFirstMinion(pepperEnv, target)
Victor Ryzhenkin723bd062018-12-11 17:09:06 +0400785 def containerd_enabled = containerDenabled(pepperEnv, firstTarget)
786 def containerd_installed = containerDinstalled(pepperEnv, firstTarget)
787 def conformance_pod_ready = conformancePodDefExists(pepperEnv, firstTarget)
788 if (containerd_enabled && containerd_installed && conformance_pod_ready) {
789 def config = ['master': pepperEnv,
790 'target': firstTarget,
791 'junitResults': false,
792 'autodetect': true]
793 test.executeConformance(config)
794 } else {
795 executeConformance(pepperEnv, firstTarget, k8s_api, mcp_repo)
796 }
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400797 }
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400798 } catch (Throwable e) {
799 // If there was an error or exception thrown, the build failed
800 currentBuild.result = "FAILURE"
801 currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
802 throw e
803 }
804 }
Victor Ryzhenkinc2024132019-01-23 05:39:34 +0400805}