blob: 1c168f3b98e3fa804247009c0c7dee4c7ff50c06 [file] [log] [blame]
Victor Ryzhenkinef34a022018-06-22 19:36:13 +04001/**
2 * Update kuberentes cluster
3 *
4 * Expected parameters:
5 * 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_IMAGE Target kubernetes version. May be null in case of reclass-system rollout
8 * KUBERNETES_PAUSE_IMAGE Kubernetes pause image should have same version as hyperkube. May be null in case of reclass-system rollout
9 * TARGET_UPDATES Comma separated list of nodes to update (Valid values are ctl,cmp)
10 * CTL_TARGET Salt targeted kubernetes CTL nodes (ex. I@kubernetes:master). Kubernetes control plane
11 * CMP_TARGET Salt targeted compute nodes (ex. cmp* and 'I@kubernetes:pool') Kubernetes computes
12 * PER_NODE Target nodes will be managed one by one (bool)
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +040013 * SIMPLE_UPGRADE Use previous version of upgrade without conron/drain abilities
14 * UPGRADE_DOCKER Upgrade docker component
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +040015 * 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.
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +020019 * 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_IMAGE Target calico/ctl image. May be null in case of reclass-system rollout.
22 * KUBERNETES_CALICO_CNI_IMAGE Target calico/cni image. May be null in case of reclass-system rollout.
23 * KUBERNETES_CALICO_KUBE_CONTROLLERS_IMAGE Target calico/kube-controllers image. May be null in case of reclass-system rollout.
24 * 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 +040025 *
26**/
Aleksei Kasatkin9ce11842018-11-23 14:27:33 +010027import groovy.json.JsonSlurper
28
Victor Ryzhenkinef34a022018-06-22 19:36:13 +040029def common = new com.mirantis.mk.Common()
30def salt = new com.mirantis.mk.Salt()
31def python = new com.mirantis.mk.Python()
Victor Ryzhenkin723bd062018-12-11 17:09:06 +040032def test = new com.mirantis.mk.Test()
Victor Ryzhenkinef34a022018-06-22 19:36:13 +040033
34def updates = TARGET_UPDATES.tokenize(",").collect{it -> it.trim()}
35def pepperEnv = "pepperEnv"
36
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +020037def POOL = "I@kubernetes:pool"
38def calicoImagesValid = false
39
Aleksei Kasatkin1f4f5ba2018-11-20 18:30:36 +010040ETCD_ENDPOINTS = ""
41
Victor Ryzhenkinef34a022018-06-22 19:36:13 +040042def overrideKubernetesImage(pepperEnv) {
43 def salt = new com.mirantis.mk.Salt()
44
45 def k8sSaltOverrides = """
46 kubernetes_hyperkube_image: ${KUBERNETES_HYPERKUBE_IMAGE}
47 kubernetes_pause_image: ${KUBERNETES_PAUSE_IMAGE}
48 """
49 stage("Override kubernetes images to target version") {
50 salt.setSaltOverrides(pepperEnv, k8sSaltOverrides)
51 }
52}
53
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +020054def overrideCalicoImages(pepperEnv) {
55 def salt = new com.mirantis.mk.Salt()
56
57 def calicoSaltOverrides = """
58 kubernetes_calico_image: ${KUBERNETES_CALICO_IMAGE}
59 kubernetes_calico_calicoctl_image: ${KUBERNETES_CALICO_CALICOCTL_IMAGE}
60 kubernetes_calico_cni_image: ${KUBERNETES_CALICO_CNI_IMAGE}
61 kubernetes_calico_kube_controllers_image: ${KUBERNETES_CALICO_KUBE_CONTROLLERS_IMAGE}
62 """
63 stage("Override calico images to target version") {
64 salt.setSaltOverrides(pepperEnv, calicoSaltOverrides)
65 }
66}
67
68def downloadCalicoUpgrader(pepperEnv, target) {
69 def salt = new com.mirantis.mk.Salt()
70
71 stage("Downloading calico-upgrade utility") {
72 salt.cmdRun(pepperEnv, target, "rm -f ./calico-upgrade")
73 salt.cmdRun(pepperEnv, target, "wget https://github.com/projectcalico/calico-upgrade/releases/download/${CALICO_UPGRADE_VERSION}/calico-upgrade")
74 salt.cmdRun(pepperEnv, target, "chmod +x ./calico-upgrade")
75 }
76}
77
78def pullCalicoImages(pepperEnv, target) {
79 def salt = new com.mirantis.mk.Salt()
80
81 stage("Pulling updated Calico docker images") {
82 salt.cmdRun(pepperEnv, target, "docker pull ${KUBERNETES_CALICO_IMAGE}")
83 salt.cmdRun(pepperEnv, target, "docker pull ${KUBERNETES_CALICO_CALICOCTL_IMAGE}")
84 salt.cmdRun(pepperEnv, target, "docker pull ${KUBERNETES_CALICO_CNI_IMAGE}")
85 salt.cmdRun(pepperEnv, target, "docker pull ${KUBERNETES_CALICO_KUBE_CONTROLLERS_IMAGE}")
86 }
87}
88
Victor Ryzhenkinef34a022018-06-22 19:36:13 +040089def performKubernetesComputeUpdate(pepperEnv, target) {
90 def salt = new com.mirantis.mk.Salt()
91
92 stage("Execute Kubernetes compute update on ${target}") {
93 salt.enforceState(pepperEnv, target, 'kubernetes.pool')
94 salt.runSaltProcessStep(pepperEnv, target, 'service.restart', ['kubelet'])
95 }
96}
97
98def performKubernetesControlUpdate(pepperEnv, target) {
99 def salt = new com.mirantis.mk.Salt()
100
101 stage("Execute Kubernetes control plane update on ${target}") {
102 salt.enforceStateWithExclude(pepperEnv, target, "kubernetes", "kubernetes.master.setup")
103 // Restart kubelet
104 salt.runSaltProcessStep(pepperEnv, target, 'service.restart', ['kubelet'])
105 }
106}
107
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200108def startCalicoUpgrade(pepperEnv, target) {
109 def salt = new com.mirantis.mk.Salt()
110
111 stage("Starting upgrade using calico-upgrade: migrate etcd schema and lock Calico") {
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200112 def cmd = "export APIV1_ETCD_ENDPOINTS=${ETCD_ENDPOINTS} && " +
113 "export APIV1_ETCD_CA_CERT_FILE=/var/lib/etcd/ca.pem && " +
114 "export APIV1_ETCD_CERT_FILE=/var/lib/etcd/etcd-client.crt && " +
115 "export APIV1_ETCD_KEY_FILE=/var/lib/etcd/etcd-client.key && " +
116 "export ETCD_ENDPOINTS=${ETCD_ENDPOINTS} && " +
117 "export ETCD_CA_CERT_FILE=/var/lib/etcd/ca.pem && " +
118 "export ETCD_CERT_FILE=/var/lib/etcd/etcd-client.crt && " +
119 "export ETCD_KEY_FILE=/var/lib/etcd/etcd-client.key && " +
120 "rm /root/upg_complete -f && " +
121 "./calico-upgrade start --no-prompts --ignore-v3-data > upgrade-start.log && " +
122 "until [ -f /root/upg_complete ]; do sleep 0.1; done && " +
123 "./calico-upgrade complete --no-prompts > upgrade-complete.log && " +
124 "rm /root/upg_complete -f"
125 // "saltArgs = ['async']" doesn't work, so we have to run "cmd.run --async"
126 salt.cmdRun(pepperEnv, "I@salt:master", "salt -C '${target}' cmd.run '${cmd}' --async")
127 salt.cmdRun(pepperEnv, target, "until [ -f /root/upgrade-start.log ]; do sleep 0.1; done")
128 }
129}
130
131def completeCalicoUpgrade(pepperEnv, target) {
132 def salt = new com.mirantis.mk.Salt()
133
134 stage("Complete upgrade using calico-upgrade: unlock Calico") {
135 salt.cmdRun(pepperEnv, target, "echo 'true' > /root/upg_complete")
136 salt.cmdRun(pepperEnv, target, "while [ -f /root/upg_complete ]; do sleep 0.1; done")
137 salt.cmdRun(pepperEnv, target, "cat /root/upgrade-start.log")
138 salt.cmdRun(pepperEnv, target, "cat /root/upgrade-complete.log")
139 }
140}
141
Aleksei Kasatkind9d682e2018-12-12 14:51:59 +0100142def performCalicoConfigurationUpdateAndServicesRestart(pepperEnv, target, ctl_node) {
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200143 def salt = new com.mirantis.mk.Salt()
144
145 stage("Performing Calico configuration update and services restart") {
Aleksei Kasatkind9d682e2018-12-12 14:51:59 +0100146 if (containerDenabled(pepperEnv, ctl_node)) {
147 salt.enforceState(pepperEnv, target, "kubernetes.pool")
148 } else {
149 salt.enforceState(pepperEnv, target, "kubernetes.pool.calico")
150 }
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200151 salt.runSaltProcessStep(pepperEnv, target, 'service.restart', ['kubelet'])
152 }
153}
154
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400155def cordonNode(pepperEnv, target) {
156 def salt = new com.mirantis.mk.Salt()
157 def originalTarget = "I@kubernetes:master and not ${target}"
158
159 stage("Cordoning ${target} kubernetes node") {
160 def nodeShortName = target.tokenize(".")[0]
161 salt.cmdRun(pepperEnv, originalTarget, "kubectl cordon ${nodeShortName}", true, 1)
162 }
163}
164
165def uncordonNode(pepperEnv, target) {
166 def salt = new com.mirantis.mk.Salt()
167 def originalTarget = "I@kubernetes:master and not ${target}"
168
169 stage("Uncordoning ${target} kubernetes node") {
170 def nodeShortName = target.tokenize(".")[0]
171 salt.cmdRun(pepperEnv, originalTarget, "kubectl uncordon ${nodeShortName}", true, 1)
172 }
173}
174
175def drainNode(pepperEnv, target) {
176 def salt = new com.mirantis.mk.Salt()
177 def originalTarget = "I@kubernetes:master and not ${target}"
178
179 stage("Draining ${target} kubernetes node") {
180 def nodeShortName = target.tokenize(".")[0]
181 salt.cmdRun(pepperEnv, originalTarget, "kubectl drain --force --ignore-daemonsets --grace-period 100 --timeout 300s --delete-local-data ${nodeShortName}", true, 1)
182 }
183}
184
185def regenerateCerts(pepperEnv, target) {
186 def salt = new com.mirantis.mk.Salt()
187
188 stage("Regenerate certs for ${target}") {
189 salt.enforceState(pepperEnv, target, 'salt.minion.cert')
190 }
191}
192
Victor Ryzhenkinae909182018-10-02 17:49:18 +0400193def updateAddons(pepperEnv, target) {
194 def salt = new com.mirantis.mk.Salt()
195
196 stage("Upgrading Addons at ${target}") {
Victor Ryzhenkin40625bc2018-10-04 16:15:27 +0400197 salt.enforceState(pepperEnv, target, "kubernetes.master.kube-addons")
Victor Ryzhenkinfd9677f2018-10-16 16:14:40 +0400198 }
199}
200
201def updateAddonManager(pepperEnv, target) {
202 def salt = new com.mirantis.mk.Salt()
203
204 stage("Upgrading AddonManager at ${target}") {
Victor Ryzhenkinae909182018-10-02 17:49:18 +0400205 salt.enforceState(pepperEnv, target, "kubernetes.master.setup")
206 }
207}
208
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400209def upgradeDocker(pepperEnv, target) {
210 def salt = new com.mirantis.mk.Salt()
211
212 stage("Upgrading docker at ${target}") {
213 salt.enforceState(pepperEnv, target, 'docker.host')
214 }
215}
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400216
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400217def runConformance(pepperEnv, target, k8s_api, image) {
218 def salt = new com.mirantis.mk.Salt()
219 def containerName = 'conformance_tests'
220 output_file = image.replaceAll('/', '-') + '.output'
221 def output_file_full_path = "/tmp/" + image.replaceAll('/', '-') + '.output'
222 def artifacts_dir = '_artifacts/'
223 salt.cmdRun(pepperEnv, target, "docker rm -f ${containerName}", false)
224 salt.cmdRun(pepperEnv, target, "docker run -d --name ${containerName} --net=host -e API_SERVER=${k8s_api} ${image}")
225 sleep(10)
226
227 print("Waiting for tests to run...")
228 salt.runSaltProcessStep(pepperEnv, target, 'cmd.run', ["docker wait ${containerName}"], null, false)
229
230 print("Writing test results to output file...")
231 salt.runSaltProcessStep(pepperEnv, target, 'cmd.run', ["docker logs -t ${containerName} > ${output_file_full_path}"])
232 print("Conformance test output saved in " + output_file_full_path)
233
234 // collect output
235 sh "mkdir -p ${artifacts_dir}"
236 file_content = salt.getFileContent(pepperEnv, target, '/tmp/' + output_file)
237 writeFile file: "${artifacts_dir}${output_file}", text: file_content
238 sh "cat ${artifacts_dir}${output_file}"
239 try {
240 sh "cat ${artifacts_dir}${output_file} | grep 'Test Suite Failed' && exit 1 || exit 0"
241 } catch (Throwable e) {
242 print("Conformance tests failed. Please check output")
243 currentBuild.result = "FAILURE"
244 currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
245 throw e
246 }
247}
248
249def buildImageURL(pepperEnv, target, mcp_repo) {
250 def salt = new com.mirantis.mk.Salt()
251 def raw_version = salt.cmdRun(pepperEnv, target, "kubectl version --short -o json")['return'][0].values()[0].replaceAll('Salt command execution success','')
252 print("Kubernetes version: " + raw_version)
253 def serialized_version = readJSON text: raw_version
254 def short_version = (serialized_version.serverVersion.gitVersion =~ /([v])(\d+\.)(\d+\.)(\d+\-)(\d+)/)[0][0]
255 print("Kubernetes short version: " + short_version)
256 def conformance_image = mcp_repo + "/mirantis/kubernetes/k8s-conformance:" + short_version
257 return conformance_image
258}
259
260def executeConformance(pepperEnv, target, k8s_api, mcp_repo) {
261 stage("Running conformance tests") {
262 def image = buildImageURL(pepperEnv, target, mcp_repo)
263 print("Using image: " + image)
264 runConformance(pepperEnv, target, k8s_api, image)
265 }
266}
267
Victor Ryzhenkin723bd062018-12-11 17:09:06 +0400268def containerDinstalled(pepperEnv, target) {
269 def salt = new com.mirantis.mk.Salt()
270 return salt.cmdRun(pepperEnv, target, "containerd --version 2>1 1>/dev/null && echo 'true' || echo 'false'"
271 )['return'][0].values()[0].replaceAll('Salt command execution success','').trim().toBoolean()
272}
273
274def containerDenabled(pepperEnv, target) {
275 def salt = new com.mirantis.mk.Salt()
Victor Ryzhenkin71ecdf42018-12-11 22:22:50 +0400276 return salt.getPillar(pepperEnv, target, "kubernetes:common:containerd:enabled"
Victor Ryzhenkin723bd062018-12-11 17:09:06 +0400277 )["return"][0].values()[0].toBoolean()
278}
279
280def conformancePodDefExists(pepperEnv, target) {
281 def salt = new com.mirantis.mk.Salt()
282 return salt.cmdRun(pepperEnv, target, "test -e /srv/kubernetes/conformance.yml && echo 'true' || echo 'false'"
283 )['return'][0].values()[0].replaceAll('Salt command execution success','').trim().toBoolean()
284}
285
Aleksei Kasatkin5ccea272018-12-06 17:34:58 +0100286def calicoEnabled(pepperEnv, target) {
287 def salt = new com.mirantis.mk.Salt()
288 return salt.getPillar(pepperEnv, target, "kubernetes:pool:network:calico:enabled"
289 )["return"][0].values()[0].toBoolean()
290}
291
292def checkCalicoClusterState(pepperEnv, target) {
293 def common = new com.mirantis.mk.Common()
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200294 def salt = new com.mirantis.mk.Salt()
295
Aleksei Kasatkin5ccea272018-12-06 17:34:58 +0100296 stage("Checking Calico cluster state after upgrade") {
297 // check Calico cluster and cli clients versions
298 def checkVer = [
299 "Client Version:": [verStr: "", dif: false, wrong: false],
300 "Cluster Version:": [verStr: "", dif: false, wrong: false]
301 ]
302 def checkVerPassed = true
303 def versionResults = salt.cmdRun(pepperEnv, target, "calicoctl version | grep -i version")['return'][0]
304 versionResults.each { k, v ->
305 // println("Node:\n${k}\nResult:\n${v}")
306 for (verLine in v.split("\n")) {
307 for (verType in checkVer.keySet()) {
308 if (verLine.contains(verType)) {
309 def verRec = checkVer[verType]
310 ver = (verLine - verType).trim()
311 if (!verRec.verStr) {
312 verRec.verStr = ver
313 }
314 if (verRec.verStr != ver) {
315 verRec.dif = true
316 checkVerPassed = false
317 }
318 version = ver.tokenize(".")
319 if ((version.size() < 3) || (version[0] != "v3")) {
320 verRec.wrong = true
321 checkVerPassed = false
322 }
323 checkVer[verType] = verRec
324 }
325 }
326 }
327 }
328 if (checkVerPassed) {
329 common.infoMsg("Calico version verification passed")
330 }
331 else {
332 def warningMsg = "Calico version verification failed.\n"
333 checkVer.each { k, rec ->
334 if (rec.dif) {
335 warningMsg += "${k} versions are different across nodes.\n"
336 }
337 if (rec.wrong) {
338 warningMsg += "${k} (some) versions are wrong - should be v3.x.\n"
339 }
340 }
341 common.warningMsg(warningMsg)
342 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
343 }
344
345 // check Calico nodes' statuses
346 def nodeStatusResults = salt.cmdRun(pepperEnv, target, "calicoctl node status")['return'][0]
347 def nodesRunning = true
348 def peersNotFound = []
349 def peersNotOnline = []
350 nodeStatusResults.each { k, v ->
351 // println("Node:\n${k}\nResult:\n${v}")
352 if (!v.contains("Calico process is running")) {
353 nodesRunning = false
354 def warningMsg = "Node ${k}: Calico node is not running."
355 common.warningMsg(warningMsg)
356 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
357 }
358 def nodePeersFound = false
359 def nodePeersOnline = true
360 for (nodeLine in v.split("\n")) {
361 if (nodeLine.contains("|") && (!nodeLine.contains("STATE"))) {
362 def col = nodeLine.tokenize("|").collect{it.trim()}
363 if (col.size() == 5) {
364 nodePeersFound = true
365 if ((col[2] != "up") || (col[4] != "Established")) {
366 def warningMsg = "Node ${k}: BGP peer '${col[0]}' is out of reach. Peer state: '${col[2]}', connection info: '${col[4]}'."
367 common.warningMsg(warningMsg)
368 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
369 nodePeersOnline = false
370 }
371 }
372 }
373 }
374 if (!nodePeersFound) {
375 peersNotFound += k
376 }
377 if (!nodePeersOnline) {
378 peersNotOnline += k
379 }
380 }
381 if (nodesRunning) {
382 common.infoMsg("All the Calico nodes are running")
383 }
384 if (peersNotFound) {
385 def warningMsg = "BGP peers not found for the node(s): " + peersNotFound.join(', ') + "."
386 common.warningMsg(warningMsg)
387 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
388 } else {
389 common.infoMsg("BGP peers were found for all the nodes")
390 }
391 if (!peersNotOnline) {
392 common.infoMsg("All reported BGP peers are reachable")
393 }
394
395 // check that 'calico-kube-controllers' is running
396 // one CTL node will be used to get pod's state using kubectl
397 def ctl_node = salt.getMinionsSorted(pepperEnv, CTL_TARGET)[0]
398 def kubeCtrlResult = salt.cmdRun(
399 pepperEnv, ctl_node, "kubectl get pod -n kube-system --selector=k8s-app=calico-kube-controllers"
400 )['return'][0].values()[0].toString()
401 if (kubeCtrlResult.contains("calico-kube-controllers")) {
402 for (line in kubeCtrlResult.split("\n")) {
403 if (line.contains("calico-kube-controllers")) {
404 col = line.tokenize(" ")
405 if ((col[1] != "1/1") || (col[2] != "Running")) {
406 def warningMsg = "Calico kube-controllers pod is not running properly."
407 common.warningMsg(warningMsg)
408 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
409 }
410 else {
411 common.infoMsg("Calico kube-controllers pod is running.")
412 }
413 break
414 }
415 }
416 } else {
417 def warningMsg = "Calico kube-controllers pod was not scheduled."
418 common.warningMsg(warningMsg)
419 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
420 }
Aleksei Kasatkin1f4f5ba2018-11-20 18:30:36 +0100421 }
422}
423
424def checkCalicoUpgradePossibility(pepperEnv, target) {
425 def salt = new com.mirantis.mk.Salt()
426
427 stage("Verification of Calico upgrade possibility") {
428 // check Calico version
429 def versionResult = salt.cmdRun(
430 pepperEnv, target, "calicoctl version | grep 'Cluster Version'"
431 )['return'][0].values()[0].split("\n")[0].trim()
432 versionStr = (versionResult - "Cluster Version:").trim()
433 version = versionStr.tokenize(".")
434 if ((version.size() < 3) || (version[0] != "v2") || (version[1] != "6") || (version[2].toInteger() < 5)) {
435 error(
436 "Current Calico ${versionStr} cannot be upgraded to v3.x. " +
437 "Calico v2.6.x starting from v2.6.5 can be upgraded. " +
438 "For earlier versions, please update to v2.6.5 first."
439 )
440 }
441 print("Calico version was determined: ${versionStr}")
442
443 // check Calico is switched on
444 def readinessResult = salt.cmdRun(
445 pepperEnv, target, ". /var/lib/etcd/configenv && etcdctl get /calico/v1/Ready"
446 )['return'][0].values()[0].split("\n")[0].trim()
447 print("Calico readiness check result: ${readinessResult}")
448 if (readinessResult != "true") {
449 // try set it to true
450 readinessResult = salt.cmdRun(
451 pepperEnv, target, ". /var/lib/etcd/configenv && etcdctl set /calico/v1/Ready true"
452 )['return'][0].values()[0].split("\n")[0].trim()
453 print("Calico readiness result 2nd attempt: ${readinessResult}")
454 if (readinessResult != "true") {
455 error("Calico is not ready. '/calico/v1/Ready': '${readinessResult}'")
456 }
457 }
458
459 // Calico data upgrade dry-run
460 def cmd = "export APIV1_ETCD_ENDPOINTS=${ETCD_ENDPOINTS} && " +
461 "export APIV1_ETCD_CA_CERT_FILE=/var/lib/etcd/ca.pem && " +
462 "export APIV1_ETCD_CERT_FILE=/var/lib/etcd/etcd-client.crt && " +
463 "export APIV1_ETCD_KEY_FILE=/var/lib/etcd/etcd-client.key && " +
464 "export ETCD_ENDPOINTS=${ETCD_ENDPOINTS} && " +
465 "export ETCD_CA_CERT_FILE=/var/lib/etcd/ca.pem && " +
466 "export ETCD_CERT_FILE=/var/lib/etcd/etcd-client.crt && " +
467 "export ETCD_KEY_FILE=/var/lib/etcd/etcd-client.key && " +
468 "./calico-upgrade dry-run --ignore-v3-data"
469 def dryRunResult = salt.cmdRun(pepperEnv, target, cmd)['return'][0].values()[0]
470 // check dry-run result
471 def validationSuccessStr = "Successfully validated v1 to v3 conversion"
472 if (!dryRunResult.contains(validationSuccessStr)) {
473 error("Calico data upgrade dry-run has failed")
474 }
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200475 }
476}
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400477
Aleksei Kasatkin9ce11842018-11-23 14:27:33 +0100478def checkCalicoPolicySetting(pepperEnv, target) {
479 def common = new com.mirantis.mk.Common()
480 def salt = new com.mirantis.mk.Salt()
481
482 stage("Checking of Calico network policy setting") {
483 // check Calico policy enabled
484 def cniPolicy = false
485 def addonsPolicy = false
486 def kubeCtrlRunning = false
487
488 // check CNI config
489 def cniCfgResult = salt.cmdRun(
490 pepperEnv, target, "cat /etc/cni/net.d/10-calico.conf"
491 )['return'][0].values()[0].toString()
492 def cniCfg = new JsonSlurper().parseText(cniCfgResult)
493 if (cniCfg.get("policy") != null) {
494 if (cniCfg["policy"].get("type") == "k8s") {
495 cniPolicy = true
496 } else {
497 common.warningMsg("Calico policy type is unknown or not set.")
498 }
499 }
500
501 // check k8s addons
502 def addonsResult = salt.cmdRun(
503 pepperEnv, target, "ls /etc/kubernetes/addons"
504 )['return'][0].values()[0].toString()
505 if (addonsResult.contains("calico_policy")) {
506 addonsPolicy = true
507 }
508
509 // check kube-controllers is running
510 def kubeCtrlResult = salt.cmdRun(
511 pepperEnv, target, "kubectl get pod -n kube-system --selector=k8s-app=calico-kube-controllers"
512 )['return'][0].values()[0].toString()
513 if (kubeCtrlResult.contains("Running")) {
514 kubeCtrlRunning = true
515 }
516
517 // It's safe to enable Calico policy any time, but it may be unsafe to disable it.
518 // So, no need to disable Calico policy for v3.x if it's not in use currently.
519 // But if Calico policy is in use already, it should be enabled after upgrade as well.
520
521 // check for consistency
522 if ((cniPolicy != addonsPolicy) || (addonsPolicy != kubeCtrlRunning)) {
523 caution = "ATTENTION. Calico policy setting cannot be determined reliably (enabled in CNI config: ${cniPolicy}, " +
524 "presence in k8s addons: ${addonsPolicy}, kube-controllers is running: ${kubeCtrlRunning})."
525 currentBuild.description += "<br><b>${caution}</b><br><br>"
526 common.warningMsg(caution)
527 } else {
528 common.infoMsg("Current Calico policy state is detected as: ${cniPolicy}")
529 if (cniPolicy) {
530 // Calico policy is in use. Check policy setting for v3.x.
531 common.infoMsg("Calico policy is in use. It should be enabled for v3.x as well.")
532 def saltPolicyResult = salt.getPillar(
533 pepperEnv, target, "kubernetes:pool:network:calico:policy"
534 )["return"][0].values()[0].toString()
535
536 common.infoMsg("kubernetes.pool.network.calico.policy: ${saltPolicyResult}")
537 if (saltPolicyResult.toLowerCase().contains("true")) {
538 common.infoMsg("Calico policy setting for v3.x is detected as: true")
539 } else {
540 caution = "ATTENTION. Currently, Calico is running with policy switched on. " +
541 "Calico policy setting for v3.x is not set to true. " +
542 "After upgrade is completed, Calico policy will be switched off. " +
543 "You will need to switch it on manually if required."
544 currentBuild.description += "<br><b>${caution}</b><br><br>"
545 common.warningMsg(caution)
546 }
547 }
548 }
549
550 if (addonsPolicy) {
551 // Remove v2.6.x policy-related addons on masters to not interfere with v3.x kube-controllers
552 salt.cmdRun(pepperEnv, CTL_TARGET, "rm -rf /etc/kubernetes/addons/calico_policy")
553 }
554 }
555}
556
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400557timeout(time: 12, unit: 'HOURS') {
558 node() {
559 try {
560
561 stage("Setup virtualenv for Pepper") {
562 python.setupPepperVirtualenv(pepperEnv, SALT_MASTER_URL, SALT_MASTER_CREDENTIALS)
563 }
564
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400565 if (CONFORMANCE_RUN_BEFORE.toBoolean()) {
566 def target = CTL_TARGET
567 def mcp_repo = ARTIFACTORY_URL
568 def k8s_api = TEST_K8S_API_SERVER
569 firstTarget = salt.getFirstMinion(pepperEnv, target)
Victor Ryzhenkin723bd062018-12-11 17:09:06 +0400570 def containerd_enabled = containerDenabled(pepperEnv, firstTarget)
571 def containerd_installed = containerDinstalled(pepperEnv, firstTarget)
572 def conformance_pod_ready = conformancePodDefExists(pepperEnv, firstTarget)
573 if (containerd_enabled && containerd_installed && conformance_pod_ready) {
574 def config = ['master': pepperEnv,
575 'target': firstTarget,
576 'junitResults': false,
577 'autodetect': true]
578 test.executeConformance(config)
579 } else {
580 executeConformance(pepperEnv, firstTarget, k8s_api, mcp_repo)
581 }
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400582 }
583
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400584 if ((common.validInputParam('KUBERNETES_HYPERKUBE_IMAGE')) && (common.validInputParam('KUBERNETES_PAUSE_IMAGE'))) {
585 overrideKubernetesImage(pepperEnv)
586 }
587
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200588 if ((common.validInputParam('KUBERNETES_CALICO_IMAGE'))
589 && (common.validInputParam('KUBERNETES_CALICO_CALICOCTL_IMAGE'))
590 && (common.validInputParam('KUBERNETES_CALICO_CNI_IMAGE'))
591 && (common.validInputParam('KUBERNETES_CALICO_KUBE_CONTROLLERS_IMAGE'))
592 ) {
593 calicoImagesValid = true
594 overrideCalicoImages(pepperEnv)
595 }
596
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400597 /*
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200598 * Execute Calico upgrade if needed (only for v2 to v3 upgrade).
599 * This part causes workloads operations downtime.
600 * It is only required for Calico v2.x to v3.x upgrade when etcd is in use for Calico
601 * as Calico etcd schema has different formats for Calico v2.x and Calico v3.x.
602 */
603 if (UPGRADE_CALICO_V2_TO_V3.toBoolean()) {
604 // one CTL node will be used for running upgrade of Calico etcd schema
605 def ctl_node = salt.getMinionsSorted(pepperEnv, CTL_TARGET)[0]
606
Aleksei Kasatkin1f4f5ba2018-11-20 18:30:36 +0100607 // get ETCD_ENDPOINTS in use by Calico
608 def ep_str = salt.cmdRun(pepperEnv, ctl_node, "cat /etc/calico/calicoctl.cfg | grep etcdEndpoints")['return'][0].values()[0]
609 ETCD_ENDPOINTS = ep_str.split("\n")[0].tokenize(' ')[1]
610 print("ETCD_ENDPOINTS in use by Calico: '${ETCD_ENDPOINTS}'")
611
612 // download calico-upgrade utility
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200613 downloadCalicoUpgrader(pepperEnv, ctl_node)
Aleksei Kasatkin1f4f5ba2018-11-20 18:30:36 +0100614
615 // check the possibility of upgrading of Calico
616 checkCalicoUpgradePossibility(pepperEnv, ctl_node)
617
618 // prepare for upgrade. when done in advance, this will decrease downtime during upgrade
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200619 if (calicoImagesValid) {
620 pullCalicoImages(pepperEnv, POOL)
621 }
622
Aleksei Kasatkin9ce11842018-11-23 14:27:33 +0100623 // check and adjust Calico policy setting
624 checkCalicoPolicySetting(pepperEnv, ctl_node)
625
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200626 // this sequence implies workloads operations downtime
627 startCalicoUpgrade(pepperEnv, ctl_node)
Aleksei Kasatkind9d682e2018-12-12 14:51:59 +0100628 performCalicoConfigurationUpdateAndServicesRestart(pepperEnv, POOL, ctl_node)
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200629 completeCalicoUpgrade(pepperEnv, ctl_node)
Aleksei Kasatkin5ccea272018-12-06 17:34:58 +0100630 // no downtime is expected after this point
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200631 }
632
633 /*
634 * Execute k8s update
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400635 */
636 if (updates.contains("ctl")) {
637 def target = CTL_TARGET
638
639 if (PER_NODE.toBoolean()) {
640 def targetHosts = salt.getMinionsSorted(pepperEnv, target)
641
642 for (t in targetHosts) {
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400643 if (SIMPLE_UPGRADE.toBoolean()) {
644 performKubernetesControlUpdate(pepperEnv, t)
645 } else {
646 cordonNode(pepperEnv, t)
647 drainNode(pepperEnv, t)
648 regenerateCerts(pepperEnv, t)
649 if (UPGRADE_DOCKER.toBoolean()) {
650 upgradeDocker(pepperEnv, t)
651 }
652 performKubernetesControlUpdate(pepperEnv, t)
Victor Ryzhenkinfd9677f2018-10-16 16:14:40 +0400653 updateAddonManager(pepperEnv, t)
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400654 uncordonNode(pepperEnv, t)
655 }
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400656 }
657 } else {
658 performKubernetesControlUpdate(pepperEnv, target)
659 }
Victor Ryzhenkinfd9677f2018-10-16 16:14:40 +0400660 if (!SIMPLE_UPGRADE.toBoolean()) {
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200661 // Addons upgrade should be performed after all nodes will be upgraded
Victor Ryzhenkinfd9677f2018-10-16 16:14:40 +0400662 updateAddons(pepperEnv, target)
663 // Wait for 90 sec for addons reconciling
664 sleep(90)
665 }
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400666 }
667
668 if (updates.contains("cmp")) {
669 def target = CMP_TARGET
670
671 if (PER_NODE.toBoolean()) {
672 def targetHosts = salt.getMinionsSorted(pepperEnv, target)
673
674 for (t in targetHosts) {
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400675 if (SIMPLE_UPGRADE.toBoolean()) {
676 performKubernetesComputeUpdate(pepperEnv, t)
677 } else {
678 cordonNode(pepperEnv, t)
679 drainNode(pepperEnv, t)
680 regenerateCerts(pepperEnv, t)
681 if (UPGRADE_DOCKER.toBoolean()) {
682 upgradeDocker(pepperEnv, t)
683 }
684 performKubernetesComputeUpdate(pepperEnv, t)
685 uncordonNode(pepperEnv, t)
686 }
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400687 }
688 } else {
689 performKubernetesComputeUpdate(pepperEnv, target)
690 }
691 }
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400692
Aleksei Kasatkin5ccea272018-12-06 17:34:58 +0100693 def ctl_node = salt.getMinionsSorted(pepperEnv, CTL_TARGET)[0]
694 if (calicoEnabled(pepperEnv, ctl_node)) {
695 checkCalicoClusterState(pepperEnv, POOL)
696 }
697
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400698 if (CONFORMANCE_RUN_AFTER.toBoolean()) {
699 def target = CTL_TARGET
700 def mcp_repo = ARTIFACTORY_URL
701 def k8s_api = TEST_K8S_API_SERVER
702 firstTarget = salt.getFirstMinion(pepperEnv, target)
Victor Ryzhenkin723bd062018-12-11 17:09:06 +0400703 def containerd_enabled = containerDenabled(pepperEnv, firstTarget)
704 def containerd_installed = containerDinstalled(pepperEnv, firstTarget)
705 def conformance_pod_ready = conformancePodDefExists(pepperEnv, firstTarget)
706 if (containerd_enabled && containerd_installed && conformance_pod_ready) {
707 def config = ['master': pepperEnv,
708 'target': firstTarget,
709 'junitResults': false,
710 'autodetect': true]
711 test.executeConformance(config)
712 } else {
713 executeConformance(pepperEnv, firstTarget, k8s_api, mcp_repo)
714 }
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400715 }
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400716 } catch (Throwable e) {
717 // If there was an error or exception thrown, the build failed
718 currentBuild.result = "FAILURE"
719 currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
720 throw e
721 }
722 }
723}