blob: 6d9c8e0938b4c51585da00ccec6bb852fa1b9908 [file] [log] [blame]
Victor Ryzhenkinef34a022018-06-22 19:36:13 +04001/**
2 * Update kuberentes cluster
3 *
4 * Expected parameters:
Victor Ryzhenkineb543bf2019-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 Ryzhenkineb543bf2019-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 Ryzhenkineb543bf2019-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}") {
103 salt.enforceStateWithExclude(pepperEnv, target, "kubernetes", "kubernetes.master.setup")
104 // 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 Ryzhenkin42e4b382018-09-11 17:57:56 +0400210def upgradeDocker(pepperEnv, target) {
211 def salt = new com.mirantis.mk.Salt()
212
213 stage("Upgrading docker at ${target}") {
214 salt.enforceState(pepperEnv, target, 'docker.host')
215 }
216}
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400217
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400218def runConformance(pepperEnv, target, k8s_api, image) {
219 def salt = new com.mirantis.mk.Salt()
220 def containerName = 'conformance_tests'
221 output_file = image.replaceAll('/', '-') + '.output'
222 def output_file_full_path = "/tmp/" + image.replaceAll('/', '-') + '.output'
223 def artifacts_dir = '_artifacts/'
224 salt.cmdRun(pepperEnv, target, "docker rm -f ${containerName}", false)
225 salt.cmdRun(pepperEnv, target, "docker run -d --name ${containerName} --net=host -e API_SERVER=${k8s_api} ${image}")
226 sleep(10)
227
228 print("Waiting for tests to run...")
229 salt.runSaltProcessStep(pepperEnv, target, 'cmd.run', ["docker wait ${containerName}"], null, false)
230
231 print("Writing test results to output file...")
232 salt.runSaltProcessStep(pepperEnv, target, 'cmd.run', ["docker logs -t ${containerName} > ${output_file_full_path}"])
233 print("Conformance test output saved in " + output_file_full_path)
234
235 // collect output
236 sh "mkdir -p ${artifacts_dir}"
237 file_content = salt.getFileContent(pepperEnv, target, '/tmp/' + output_file)
238 writeFile file: "${artifacts_dir}${output_file}", text: file_content
239 sh "cat ${artifacts_dir}${output_file}"
240 try {
241 sh "cat ${artifacts_dir}${output_file} | grep 'Test Suite Failed' && exit 1 || exit 0"
242 } catch (Throwable e) {
243 print("Conformance tests failed. Please check output")
244 currentBuild.result = "FAILURE"
245 currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
246 throw e
247 }
248}
249
250def buildImageURL(pepperEnv, target, mcp_repo) {
251 def salt = new com.mirantis.mk.Salt()
252 def raw_version = salt.cmdRun(pepperEnv, target, "kubectl version --short -o json")['return'][0].values()[0].replaceAll('Salt command execution success','')
253 print("Kubernetes version: " + raw_version)
254 def serialized_version = readJSON text: raw_version
255 def short_version = (serialized_version.serverVersion.gitVersion =~ /([v])(\d+\.)(\d+\.)(\d+\-)(\d+)/)[0][0]
256 print("Kubernetes short version: " + short_version)
257 def conformance_image = mcp_repo + "/mirantis/kubernetes/k8s-conformance:" + short_version
258 return conformance_image
259}
260
261def executeConformance(pepperEnv, target, k8s_api, mcp_repo) {
262 stage("Running conformance tests") {
263 def image = buildImageURL(pepperEnv, target, mcp_repo)
264 print("Using image: " + image)
265 runConformance(pepperEnv, target, k8s_api, image)
266 }
267}
268
Victor Ryzhenkin723bd062018-12-11 17:09:06 +0400269def containerDinstalled(pepperEnv, target) {
270 def salt = new com.mirantis.mk.Salt()
271 return salt.cmdRun(pepperEnv, target, "containerd --version 2>1 1>/dev/null && echo 'true' || echo 'false'"
272 )['return'][0].values()[0].replaceAll('Salt command execution success','').trim().toBoolean()
273}
274
275def containerDenabled(pepperEnv, target) {
276 def salt = new com.mirantis.mk.Salt()
Victor Ryzhenkin71ecdf42018-12-11 22:22:50 +0400277 return salt.getPillar(pepperEnv, target, "kubernetes:common:containerd:enabled"
Victor Ryzhenkin723bd062018-12-11 17:09:06 +0400278 )["return"][0].values()[0].toBoolean()
279}
280
281def conformancePodDefExists(pepperEnv, target) {
282 def salt = new com.mirantis.mk.Salt()
283 return salt.cmdRun(pepperEnv, target, "test -e /srv/kubernetes/conformance.yml && echo 'true' || echo 'false'"
284 )['return'][0].values()[0].replaceAll('Salt command execution success','').trim().toBoolean()
285}
286
Victor Ryzhenkineb543bf2019-01-18 06:34:26 +0400287def printVersionInfo(pepperEnv, target) {
288 def salt = new com.mirantis.mk.Salt()
289 def common = new com.mirantis.mk.Common()
290
291 stage("Gather version and runtime information") {
292 common.infoMsg("Version and runtime info:")
293 salt.cmdRun(pepperEnv, target, "kubectl get no -o wide")
294 common.infoMsg("Cluster health info:")
295 salt.cmdRun(pepperEnv, target, "kubectl get cs")
296 common.infoMsg("ETCD health info:")
297 salt.cmdRun(pepperEnv, target, "source /var/lib/etcd/configenv && etcdctl cluster-health")
298 common.infoMsg("Calico peers info:")
299 salt.cmdRun(pepperEnv, target, "calicoctl node status")
300 }
301}
302
Aleksei Kasatkin5ccea272018-12-06 17:34:58 +0100303def calicoEnabled(pepperEnv, target) {
304 def salt = new com.mirantis.mk.Salt()
305 return salt.getPillar(pepperEnv, target, "kubernetes:pool:network:calico:enabled"
306 )["return"][0].values()[0].toBoolean()
307}
308
309def checkCalicoClusterState(pepperEnv, target) {
310 def common = new com.mirantis.mk.Common()
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200311 def salt = new com.mirantis.mk.Salt()
312
Aleksei Kasatkin5ccea272018-12-06 17:34:58 +0100313 stage("Checking Calico cluster state after upgrade") {
314 // check Calico cluster and cli clients versions
315 def checkVer = [
316 "Client Version:": [verStr: "", dif: false, wrong: false],
317 "Cluster Version:": [verStr: "", dif: false, wrong: false]
318 ]
319 def checkVerPassed = true
320 def versionResults = salt.cmdRun(pepperEnv, target, "calicoctl version | grep -i version")['return'][0]
321 versionResults.each { k, v ->
322 // println("Node:\n${k}\nResult:\n${v}")
323 for (verLine in v.split("\n")) {
324 for (verType in checkVer.keySet()) {
325 if (verLine.contains(verType)) {
326 def verRec = checkVer[verType]
327 ver = (verLine - verType).trim()
328 if (!verRec.verStr) {
329 verRec.verStr = ver
330 }
331 if (verRec.verStr != ver) {
332 verRec.dif = true
333 checkVerPassed = false
334 }
335 version = ver.tokenize(".")
336 if ((version.size() < 3) || (version[0] != "v3")) {
337 verRec.wrong = true
338 checkVerPassed = false
339 }
340 checkVer[verType] = verRec
341 }
342 }
343 }
344 }
345 if (checkVerPassed) {
346 common.infoMsg("Calico version verification passed")
347 }
348 else {
349 def warningMsg = "Calico version verification failed.\n"
350 checkVer.each { k, rec ->
351 if (rec.dif) {
352 warningMsg += "${k} versions are different across nodes.\n"
353 }
354 if (rec.wrong) {
355 warningMsg += "${k} (some) versions are wrong - should be v3.x.\n"
356 }
357 }
358 common.warningMsg(warningMsg)
359 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
360 }
361
362 // check Calico nodes' statuses
363 def nodeStatusResults = salt.cmdRun(pepperEnv, target, "calicoctl node status")['return'][0]
364 def nodesRunning = true
365 def peersNotFound = []
366 def peersNotOnline = []
367 nodeStatusResults.each { k, v ->
368 // println("Node:\n${k}\nResult:\n${v}")
369 if (!v.contains("Calico process is running")) {
370 nodesRunning = false
371 def warningMsg = "Node ${k}: Calico node is not running."
372 common.warningMsg(warningMsg)
373 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
374 }
375 def nodePeersFound = false
376 def nodePeersOnline = true
377 for (nodeLine in v.split("\n")) {
378 if (nodeLine.contains("|") && (!nodeLine.contains("STATE"))) {
379 def col = nodeLine.tokenize("|").collect{it.trim()}
380 if (col.size() == 5) {
381 nodePeersFound = true
382 if ((col[2] != "up") || (col[4] != "Established")) {
383 def warningMsg = "Node ${k}: BGP peer '${col[0]}' is out of reach. Peer state: '${col[2]}', connection info: '${col[4]}'."
384 common.warningMsg(warningMsg)
385 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
386 nodePeersOnline = false
387 }
388 }
389 }
390 }
391 if (!nodePeersFound) {
392 peersNotFound += k
393 }
394 if (!nodePeersOnline) {
395 peersNotOnline += k
396 }
397 }
398 if (nodesRunning) {
399 common.infoMsg("All the Calico nodes are running")
400 }
401 if (peersNotFound) {
402 def warningMsg = "BGP peers not found for the node(s): " + peersNotFound.join(', ') + "."
403 common.warningMsg(warningMsg)
404 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
405 } else {
406 common.infoMsg("BGP peers were found for all the nodes")
407 }
408 if (!peersNotOnline) {
409 common.infoMsg("All reported BGP peers are reachable")
410 }
411
412 // check that 'calico-kube-controllers' is running
413 // one CTL node will be used to get pod's state using kubectl
414 def ctl_node = salt.getMinionsSorted(pepperEnv, CTL_TARGET)[0]
415 def kubeCtrlResult = salt.cmdRun(
416 pepperEnv, ctl_node, "kubectl get pod -n kube-system --selector=k8s-app=calico-kube-controllers"
417 )['return'][0].values()[0].toString()
418 if (kubeCtrlResult.contains("calico-kube-controllers")) {
419 for (line in kubeCtrlResult.split("\n")) {
420 if (line.contains("calico-kube-controllers")) {
421 col = line.tokenize(" ")
422 if ((col[1] != "1/1") || (col[2] != "Running")) {
423 def warningMsg = "Calico kube-controllers pod is not running properly."
424 common.warningMsg(warningMsg)
425 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
426 }
427 else {
428 common.infoMsg("Calico kube-controllers pod is running.")
429 }
430 break
431 }
432 }
433 } else {
434 def warningMsg = "Calico kube-controllers pod was not scheduled."
435 common.warningMsg(warningMsg)
436 currentBuild.description += "<br><b>${warningMsg}</b><br><br>"
437 }
Aleksei Kasatkin1f4f5ba2018-11-20 18:30:36 +0100438 }
439}
440
441def checkCalicoUpgradePossibility(pepperEnv, target) {
442 def salt = new com.mirantis.mk.Salt()
443
444 stage("Verification of Calico upgrade possibility") {
445 // check Calico version
446 def versionResult = salt.cmdRun(
447 pepperEnv, target, "calicoctl version | grep 'Cluster Version'"
448 )['return'][0].values()[0].split("\n")[0].trim()
449 versionStr = (versionResult - "Cluster Version:").trim()
450 version = versionStr.tokenize(".")
451 if ((version.size() < 3) || (version[0] != "v2") || (version[1] != "6") || (version[2].toInteger() < 5)) {
452 error(
453 "Current Calico ${versionStr} cannot be upgraded to v3.x. " +
454 "Calico v2.6.x starting from v2.6.5 can be upgraded. " +
455 "For earlier versions, please update to v2.6.5 first."
456 )
457 }
458 print("Calico version was determined: ${versionStr}")
459
460 // check Calico is switched on
461 def readinessResult = salt.cmdRun(
462 pepperEnv, target, ". /var/lib/etcd/configenv && etcdctl get /calico/v1/Ready"
463 )['return'][0].values()[0].split("\n")[0].trim()
464 print("Calico readiness check result: ${readinessResult}")
465 if (readinessResult != "true") {
466 // try set it to true
467 readinessResult = salt.cmdRun(
468 pepperEnv, target, ". /var/lib/etcd/configenv && etcdctl set /calico/v1/Ready true"
469 )['return'][0].values()[0].split("\n")[0].trim()
470 print("Calico readiness result 2nd attempt: ${readinessResult}")
471 if (readinessResult != "true") {
472 error("Calico is not ready. '/calico/v1/Ready': '${readinessResult}'")
473 }
474 }
475
476 // Calico data upgrade dry-run
477 def cmd = "export APIV1_ETCD_ENDPOINTS=${ETCD_ENDPOINTS} && " +
478 "export APIV1_ETCD_CA_CERT_FILE=/var/lib/etcd/ca.pem && " +
479 "export APIV1_ETCD_CERT_FILE=/var/lib/etcd/etcd-client.crt && " +
480 "export APIV1_ETCD_KEY_FILE=/var/lib/etcd/etcd-client.key && " +
481 "export ETCD_ENDPOINTS=${ETCD_ENDPOINTS} && " +
482 "export ETCD_CA_CERT_FILE=/var/lib/etcd/ca.pem && " +
483 "export ETCD_CERT_FILE=/var/lib/etcd/etcd-client.crt && " +
484 "export ETCD_KEY_FILE=/var/lib/etcd/etcd-client.key && " +
485 "./calico-upgrade dry-run --ignore-v3-data"
486 def dryRunResult = salt.cmdRun(pepperEnv, target, cmd)['return'][0].values()[0]
487 // check dry-run result
488 def validationSuccessStr = "Successfully validated v1 to v3 conversion"
489 if (!dryRunResult.contains(validationSuccessStr)) {
490 error("Calico data upgrade dry-run has failed")
491 }
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200492 }
493}
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400494
Aleksei Kasatkin9ce11842018-11-23 14:27:33 +0100495def checkCalicoPolicySetting(pepperEnv, target) {
496 def common = new com.mirantis.mk.Common()
497 def salt = new com.mirantis.mk.Salt()
498
499 stage("Checking of Calico network policy setting") {
500 // check Calico policy enabled
501 def cniPolicy = false
502 def addonsPolicy = false
503 def kubeCtrlRunning = false
504
505 // check CNI config
506 def cniCfgResult = salt.cmdRun(
507 pepperEnv, target, "cat /etc/cni/net.d/10-calico.conf"
508 )['return'][0].values()[0].toString()
509 def cniCfg = new JsonSlurper().parseText(cniCfgResult)
510 if (cniCfg.get("policy") != null) {
511 if (cniCfg["policy"].get("type") == "k8s") {
512 cniPolicy = true
513 } else {
514 common.warningMsg("Calico policy type is unknown or not set.")
515 }
516 }
517
518 // check k8s addons
519 def addonsResult = salt.cmdRun(
520 pepperEnv, target, "ls /etc/kubernetes/addons"
521 )['return'][0].values()[0].toString()
522 if (addonsResult.contains("calico_policy")) {
523 addonsPolicy = true
524 }
525
526 // check kube-controllers is running
527 def kubeCtrlResult = salt.cmdRun(
528 pepperEnv, target, "kubectl get pod -n kube-system --selector=k8s-app=calico-kube-controllers"
529 )['return'][0].values()[0].toString()
530 if (kubeCtrlResult.contains("Running")) {
531 kubeCtrlRunning = true
532 }
533
534 // It's safe to enable Calico policy any time, but it may be unsafe to disable it.
535 // So, no need to disable Calico policy for v3.x if it's not in use currently.
536 // But if Calico policy is in use already, it should be enabled after upgrade as well.
537
538 // check for consistency
539 if ((cniPolicy != addonsPolicy) || (addonsPolicy != kubeCtrlRunning)) {
540 caution = "ATTENTION. Calico policy setting cannot be determined reliably (enabled in CNI config: ${cniPolicy}, " +
541 "presence in k8s addons: ${addonsPolicy}, kube-controllers is running: ${kubeCtrlRunning})."
542 currentBuild.description += "<br><b>${caution}</b><br><br>"
543 common.warningMsg(caution)
544 } else {
545 common.infoMsg("Current Calico policy state is detected as: ${cniPolicy}")
546 if (cniPolicy) {
547 // Calico policy is in use. Check policy setting for v3.x.
548 common.infoMsg("Calico policy is in use. It should be enabled for v3.x as well.")
549 def saltPolicyResult = salt.getPillar(
550 pepperEnv, target, "kubernetes:pool:network:calico:policy"
551 )["return"][0].values()[0].toString()
552
553 common.infoMsg("kubernetes.pool.network.calico.policy: ${saltPolicyResult}")
554 if (saltPolicyResult.toLowerCase().contains("true")) {
555 common.infoMsg("Calico policy setting for v3.x is detected as: true")
556 } else {
557 caution = "ATTENTION. Currently, Calico is running with policy switched on. " +
558 "Calico policy setting for v3.x is not set to true. " +
559 "After upgrade is completed, Calico policy will be switched off. " +
560 "You will need to switch it on manually if required."
561 currentBuild.description += "<br><b>${caution}</b><br><br>"
562 common.warningMsg(caution)
563 }
564 }
565 }
566
567 if (addonsPolicy) {
568 // Remove v2.6.x policy-related addons on masters to not interfere with v3.x kube-controllers
569 salt.cmdRun(pepperEnv, CTL_TARGET, "rm -rf /etc/kubernetes/addons/calico_policy")
570 }
571 }
572}
573
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400574timeout(time: 12, unit: 'HOURS') {
575 node() {
576 try {
577
578 stage("Setup virtualenv for Pepper") {
579 python.setupPepperVirtualenv(pepperEnv, SALT_MASTER_URL, SALT_MASTER_CREDENTIALS)
580 }
581
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400582 if (CONFORMANCE_RUN_BEFORE.toBoolean()) {
583 def target = CTL_TARGET
584 def mcp_repo = ARTIFACTORY_URL
585 def k8s_api = TEST_K8S_API_SERVER
586 firstTarget = salt.getFirstMinion(pepperEnv, target)
Victor Ryzhenkin723bd062018-12-11 17:09:06 +0400587 def containerd_enabled = containerDenabled(pepperEnv, firstTarget)
588 def containerd_installed = containerDinstalled(pepperEnv, firstTarget)
589 def conformance_pod_ready = conformancePodDefExists(pepperEnv, firstTarget)
590 if (containerd_enabled && containerd_installed && conformance_pod_ready) {
591 def config = ['master': pepperEnv,
592 'target': firstTarget,
593 'junitResults': false,
594 'autodetect': true]
595 test.executeConformance(config)
596 } else {
597 executeConformance(pepperEnv, firstTarget, k8s_api, mcp_repo)
598 }
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400599 }
600
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400601 if ((common.validInputParam('KUBERNETES_HYPERKUBE_IMAGE')) && (common.validInputParam('KUBERNETES_PAUSE_IMAGE'))) {
602 overrideKubernetesImage(pepperEnv)
603 }
604
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200605 if ((common.validInputParam('KUBERNETES_CALICO_IMAGE'))
Victor Ryzhenkineb543bf2019-01-18 06:34:26 +0400606 && (common.validInputParam('KUBERNETES_CALICO_CALICOCTL_SOURCE'))
607 && (common.validInputParam('KUBERNETES_CALICO_CALICOCTL_SOURCE_HASH'))
608 && (common.validInputParam('KUBERNETES_CALICO_CNI_SOURCE'))
609 && (common.validInputParam('KUBERNETES_CALICO_CNI_SOURCE_HASH'))
610 && (common.validInputParam('KUBERNETES_CALICO_BIRDCL_SOURCE'))
611 && (common.validInputParam('KUBERNETES_CALICO_BIRDCL_SOURCE_HASH'))
612 && (common.validInputParam('KUBERNETES_CALICO_CNI_IPAM_SOURCE'))
613 && (common.validInputParam('KUBERNETES_CALICO_CNI_IPAM_SOURCE_HASH'))
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200614 && (common.validInputParam('KUBERNETES_CALICO_KUBE_CONTROLLERS_IMAGE'))
615 ) {
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200616 overrideCalicoImages(pepperEnv)
617 }
618
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400619 /*
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200620 * Execute Calico upgrade if needed (only for v2 to v3 upgrade).
621 * This part causes workloads operations downtime.
622 * It is only required for Calico v2.x to v3.x upgrade when etcd is in use for Calico
623 * as Calico etcd schema has different formats for Calico v2.x and Calico v3.x.
624 */
625 if (UPGRADE_CALICO_V2_TO_V3.toBoolean()) {
626 // one CTL node will be used for running upgrade of Calico etcd schema
627 def ctl_node = salt.getMinionsSorted(pepperEnv, CTL_TARGET)[0]
628
Aleksei Kasatkin1f4f5ba2018-11-20 18:30:36 +0100629 // get ETCD_ENDPOINTS in use by Calico
630 def ep_str = salt.cmdRun(pepperEnv, ctl_node, "cat /etc/calico/calicoctl.cfg | grep etcdEndpoints")['return'][0].values()[0]
631 ETCD_ENDPOINTS = ep_str.split("\n")[0].tokenize(' ')[1]
632 print("ETCD_ENDPOINTS in use by Calico: '${ETCD_ENDPOINTS}'")
633
634 // download calico-upgrade utility
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200635 downloadCalicoUpgrader(pepperEnv, ctl_node)
Aleksei Kasatkin1f4f5ba2018-11-20 18:30:36 +0100636
637 // check the possibility of upgrading of Calico
638 checkCalicoUpgradePossibility(pepperEnv, ctl_node)
639
Aleksei Kasatkin9ce11842018-11-23 14:27:33 +0100640 // check and adjust Calico policy setting
641 checkCalicoPolicySetting(pepperEnv, ctl_node)
642
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200643 // this sequence implies workloads operations downtime
644 startCalicoUpgrade(pepperEnv, ctl_node)
Aleksei Kasatkind9d682e2018-12-12 14:51:59 +0100645 performCalicoConfigurationUpdateAndServicesRestart(pepperEnv, POOL, ctl_node)
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200646 completeCalicoUpgrade(pepperEnv, ctl_node)
Aleksei Kasatkin5ccea272018-12-06 17:34:58 +0100647 // no downtime is expected after this point
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200648 }
649
650 /*
651 * Execute k8s update
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400652 */
653 if (updates.contains("ctl")) {
654 def target = CTL_TARGET
655
656 if (PER_NODE.toBoolean()) {
657 def targetHosts = salt.getMinionsSorted(pepperEnv, target)
658
659 for (t in targetHosts) {
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400660 if (SIMPLE_UPGRADE.toBoolean()) {
661 performKubernetesControlUpdate(pepperEnv, t)
662 } else {
663 cordonNode(pepperEnv, t)
664 drainNode(pepperEnv, t)
665 regenerateCerts(pepperEnv, t)
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400666 performKubernetesControlUpdate(pepperEnv, t)
Victor Ryzhenkinfd9677f2018-10-16 16:14:40 +0400667 updateAddonManager(pepperEnv, t)
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400668 uncordonNode(pepperEnv, t)
669 }
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400670 }
671 } else {
672 performKubernetesControlUpdate(pepperEnv, target)
673 }
Victor Ryzhenkinfd9677f2018-10-16 16:14:40 +0400674 if (!SIMPLE_UPGRADE.toBoolean()) {
Aleksei Kasatkinff9d5b52018-10-26 11:47:46 +0200675 // Addons upgrade should be performed after all nodes will be upgraded
Victor Ryzhenkinfd9677f2018-10-16 16:14:40 +0400676 updateAddons(pepperEnv, target)
677 // Wait for 90 sec for addons reconciling
678 sleep(90)
679 }
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400680 }
681
682 if (updates.contains("cmp")) {
683 def target = CMP_TARGET
684
685 if (PER_NODE.toBoolean()) {
686 def targetHosts = salt.getMinionsSorted(pepperEnv, target)
687
688 for (t in targetHosts) {
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400689 if (SIMPLE_UPGRADE.toBoolean()) {
690 performKubernetesComputeUpdate(pepperEnv, t)
691 } else {
692 cordonNode(pepperEnv, t)
693 drainNode(pepperEnv, t)
694 regenerateCerts(pepperEnv, t)
Victor Ryzhenkin42e4b382018-09-11 17:57:56 +0400695 performKubernetesComputeUpdate(pepperEnv, t)
696 uncordonNode(pepperEnv, t)
697 }
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400698 }
699 } else {
700 performKubernetesComputeUpdate(pepperEnv, target)
701 }
702 }
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400703
Aleksei Kasatkin5ccea272018-12-06 17:34:58 +0100704 def ctl_node = salt.getMinionsSorted(pepperEnv, CTL_TARGET)[0]
705 if (calicoEnabled(pepperEnv, ctl_node)) {
706 checkCalicoClusterState(pepperEnv, POOL)
707 }
Victor Ryzhenkineb543bf2019-01-18 06:34:26 +0400708 printVersionInfo(pepperEnv, ctl_node)
Aleksei Kasatkin5ccea272018-12-06 17:34:58 +0100709
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400710 if (CONFORMANCE_RUN_AFTER.toBoolean()) {
711 def target = CTL_TARGET
712 def mcp_repo = ARTIFACTORY_URL
713 def k8s_api = TEST_K8S_API_SERVER
714 firstTarget = salt.getFirstMinion(pepperEnv, target)
Victor Ryzhenkin723bd062018-12-11 17:09:06 +0400715 def containerd_enabled = containerDenabled(pepperEnv, firstTarget)
716 def containerd_installed = containerDinstalled(pepperEnv, firstTarget)
717 def conformance_pod_ready = conformancePodDefExists(pepperEnv, firstTarget)
718 if (containerd_enabled && containerd_installed && conformance_pod_ready) {
719 def config = ['master': pepperEnv,
720 'target': firstTarget,
721 'junitResults': false,
722 'autodetect': true]
723 test.executeConformance(config)
724 } else {
725 executeConformance(pepperEnv, firstTarget, k8s_api, mcp_repo)
726 }
Victor Ryzhenkinae22a5a2018-10-12 15:52:27 +0400727 }
Victor Ryzhenkinef34a022018-06-22 19:36:13 +0400728 } catch (Throwable e) {
729 // If there was an error or exception thrown, the build failed
730 currentBuild.result = "FAILURE"
731 currentBuild.description = currentBuild.description ? e.message + " " + currentBuild.description : e.message
732 throw e
733 }
734 }
735}